1pub mod admin;
8pub mod encounters;
9pub mod error;
10mod export;
11pub mod hub;
12pub mod routes;
14pub mod services;
15pub mod store;
16pub mod sync;
17
18use std::sync::Arc;
19
20use axum::{Extension, Router};
21use edda_auth::server::TokenVerifier;
22use edda_server::{Limits, openapi::Conventions};
23use sqlx::{PgPool, postgres::PgListener};
24
25#[derive(utoipa::OpenApi)]
27#[openapi(
28 paths(
29 edda_server::health,
30 edda_server::ready,
31 routes::list,
32 routes::create,
33 routes::snapshot,
34 routes::log,
35 routes::command,
36 routes::websocket,
37 encounters::list,
38 encounters::create,
39 encounters::fetch,
40 encounters::replace,
41 encounters::remove,
42 ),
43 components(schemas(
44 edda_types::combat::ClientMessage,
45 edda_types::combat::ServerMessage,
46 )),
47 modifiers(&Conventions),
48 tags(
49 (name = "combats", description = "Live, event-sourced combats."),
50 (name = "encounters", description = "Planned encounters: the DM's only."),
51 )
52)]
53pub struct ApiDoc;
54
55#[derive(Clone, Default)]
58pub struct Config {
59 pub services: Option<services::ServicesConfig>,
62 pub limits: Limits,
64 pub listen_url: Option<String>,
68}
69
70impl std::fmt::Debug for Config {
72 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
73 f.debug_struct("Config")
74 .field("services", &self.services)
75 .field("limits", &self.limits)
76 .field("listens_directly", &self.listen_url.is_some())
77 .finish()
78 }
79}
80
81pub async fn connect(database_url: &str) -> Result<PgPool, sqlx::Error> {
83 edda_server::open_database(database_url, &sqlx::migrate!()).await
84}
85
86pub async fn app(
89 pool: PgPool,
90 verifier: TokenVerifier,
91 config: Config,
92) -> Result<Router, sqlx::Error> {
93 let ready = edda_server::readiness(pool.clone());
94 let store = store::Store::new(pool);
95 let (mut sheet_sync, mut monsters) = (None, None);
96 if let Some(config) = config.services {
97 let login = Arc::new(services::ServiceLogin::new(&config));
98 sheet_sync = config
99 .character_service
100 .map(|url| sync::SheetSync::new(store.clone(), login.clone(), url));
101 monsters = config
102 .monster_service
103 .map(|url| services::Monsters::new(login, url));
104 }
105 let listener = match &config.listen_url {
106 Some(url) => PgListener::connect(url).await?,
107 None => PgListener::connect_with(store.pool()).await?,
108 };
109 let hub = hub::Hub::start(store, listener, sheet_sync, monsters, config.limits).await?;
110 Ok(Router::new()
111 .merge(routes::router())
112 .merge(encounters::router())
113 .with_state(hub)
114 .layer(Extension(Arc::new(verifier)))
115 .merge(ready))
116}