rainbeam/
main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//! 🌈 Rainbeam!
#![doc = include_str!("../../../README.md")]
#![doc(issue_tracker_base_url = "https://github.com/swmff/rainbeam/issues")]
#![doc(html_favicon_url = "https://rainbeam.net/static/favicon.svg")]
#![doc(html_logo_url = "https://rainbeam.net/static/favicon.svg")]
use axum::routing::get_service;
use axum::Router;

use tower_http::trace::{self, TraceLayer};
use tracing::{info, Level};

use authbeam::{api as AuthApi, Database as AuthDatabase};
use databeam::config::Config as DataConf;
use rainbeam_shared::fs;
use pathbufd::{PathBufD, pathd};

pub use rb::database;
pub use rb::config;
pub use rb::model;
pub use rb::routing;

// mimalloc
#[cfg(feature = "mimalloc")]
use mimalloc::MiMalloc;

#[cfg(feature = "mimalloc")]
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

/// Main server process
#[tokio::main]
pub async fn main() {
    let mut config = config::Config::get_config();

    let here = PathBufD::current();
    let static_dir = here.join(".config").join("static");
    let well_known_dir = here.join(".config").join(".well-known");
    config.static_dir = static_dir.clone();

    tracing_subscriber::fmt()
        .with_target(false)
        .compact()
        .init();

    // make sure media dir is created
    // TODO: implement `.is_empty()` on `PathBufD`
    if !config.media_dir.to_string().is_empty() {
        fs::mkdir(&config.media_dir).expect("failed to create media dir");
        fs::mkdir(pathd!("{}/avatars", config.media_dir)).expect("failed to create avatars dir");
        fs::mkdir(pathd!("{}/banners", config.media_dir)).expect("failed to create banners dir");
    }

    // create databases
    let auth_database = AuthDatabase::new(
        DataConf::get_config().connection, // pull connection config from config file
        authbeam::ServerOptions {
            captcha: config.captcha.clone(),
            registration_enabled: config.registration_enabled,
            real_ip_header: config.real_ip_header.clone(),
            static_dir: config.static_dir.clone(),
            media_dir: config.media_dir.clone(),
            host: config.host.clone(),
            snowflake_server_id: config.snowflake_server_id.clone(),
            blocked_hosts: config.blocked_hosts.clone(),
        },
    )
    .await;
    auth_database.init().await;

    let database = database::Database::new(
        DataConf::get_config().connection,
        auth_database.clone(),
        config.clone(),
    )
    .await;
    database.init().await;

    // create app
    let app = Router::new()
        // api
        .nest_service("/api/v0/auth", AuthApi::routes(auth_database.clone()))
        .nest("/api/v0/util", routing::api::util::routes(database.clone()))
        .nest("/api/v1", routing::api::routes(database.clone()))
        .merge(routing::pages::routes(database.clone()).await)
        // pages
        // ...
        .nest_service(
            "/.well-known",
            get_service(tower_http::services::ServeDir::new(&well_known_dir)),
        )
        .layer(
            TraceLayer::new_for_http()
                .make_span_with(trace::DefaultMakeSpan::new().level(Level::INFO))
                .on_response(trace::DefaultOnResponse::new().level(Level::INFO)),
        );

    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", config.port))
        .await
        .unwrap();

    info!("🌈 Starting server at: http://localhost:{}!", config.port);
    axum::serve(listener, app).await.unwrap();
}