1use axum::{Router, routing::get};
2use firebase_auth::FirebaseAuth;
3use socketioxide::SocketIo;
4use sqlx::postgres::PgPoolOptions;
5use tower::ServiceBuilder;
6use tower_http::cors::CorsLayer;
7use tracing::{error, info};
8
9mod app;
10mod auth;
11mod document;
12mod rpc;
13mod socket;
14mod user;
15
16fn web_port() -> String {
18 dotenvy::var("PORT").unwrap_or("8000".to_string())
19}
20
21fn automerge_io_port() -> String {
26 dotenvy::var("AUTOMERGE_IO_PORT").unwrap_or("3000".to_string())
27}
28
29#[tokio::main]
30async fn main() {
31 tracing_subscriber::fmt().with_max_level(tracing::Level::INFO).init();
32
33 let db = PgPoolOptions::new()
34 .max_connections(10)
35 .connect(&dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` should be set"))
36 .await
37 .expect("Failed to connect to database");
38
39 let (io_layer, io) = SocketIo::new_layer();
40
41 let state = app::AppState {
42 automerge_io: io,
43 db,
44 };
45
46 socket::setup_automerge_socket(state.clone());
47
48 let main_task = tokio::task::spawn(async {
49 let firebase_auth = FirebaseAuth::new(
50 &dotenvy::var("FIREBASE_PROJECT_ID").expect("`FIREBASE_PROJECT_ID` should be set"),
51 )
52 .await;
53
54 let port = web_port();
55 let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await.unwrap();
56
57 let router = rpc::router();
58 let (qubit_service, qubit_handle) = router.to_service(state);
59 let qubit_service = ServiceBuilder::new()
60 .map_request(move |mut req: hyper::Request<_>| {
61 match auth::authenticate_from_request(&firebase_auth, &req) {
62 Ok(Some(user)) => {
63 req.extensions_mut().insert(user);
64 }
65 Ok(None) => {}
66 Err(err) => {
67 error!("Authentication error: {}", err);
68 }
69 };
70 req
71 })
72 .service(qubit_service);
73
74 let app = Router::new()
75 .route("/", get(|| async { "Hello! The CatColab server is running" }))
76 .nest_service("/rpc", qubit_service)
77 .layer(CorsLayer::permissive());
78 info!("Web server listening at port {}", port);
79 axum::serve(listener, app).await.unwrap();
80
81 qubit_handle.stop().unwrap();
82 });
83
84 let automerge_io_task = tokio::task::spawn(async {
85 let port = automerge_io_port();
86 let listener = tokio::net::TcpListener::bind(format!("localhost:{}", port)).await.unwrap();
87 let app = Router::new().layer(io_layer);
88 info!("Automerge socket listening at port {}", port);
89 axum::serve(listener, app).await.unwrap();
90 });
91
92 let (res_main, res_io) = tokio::join!(main_task, automerge_io_task);
93 res_main.unwrap();
94 res_io.unwrap();
95}