catcolab_backend/
main.rs

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
16/// Port for the web server providing the RPC API.
17fn web_port() -> String {
18    dotenvy::var("PORT").unwrap_or("8000".to_string())
19}
20
21/** Port for internal communication with the Automerge doc server.
22
23This port should *not* be open to the public.
24*/
25fn 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}