backend/
main.rs

1use argon2::Argon2;
2use axum::{
3    Router,
4    http::{Method, header},
5};
6use clap::Parser;
7use config::{Config, ConfigBuilder};
8use diesel_async::pooled_connection::AsyncDieselConnectionManager;
9use diesel_async::pooled_connection::deadpool::Pool;
10use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
11use error::Error;
12use objects::MailClient;
13use std::{sync::Arc, time::SystemTime};
14use tower_http::cors::{AllowOrigin, CorsLayer};
15
16pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
17
18type Conn =
19    deadpool::managed::Object<AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>>;
20
21mod api;
22mod config;
23pub mod error;
24pub mod objects;
25pub mod schema;
26//mod socket;
27pub mod utils;
28mod wordlist;
29
30#[derive(Parser, Debug)]
31#[command(version, about, long_about = None)]
32struct Args {
33    #[arg(short, long, default_value_t = String::from("/etc/gorb/config.toml"))]
34    config: String,
35}
36
37#[derive(Clone)]
38pub struct AppState {
39    pub pool: deadpool::managed::Pool<
40        AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>,
41        Conn,
42    >,
43    pub cache_pool: redis::Client,
44    pub config: Config,
45    pub argon2: Argon2<'static>,
46    pub start_time: SystemTime,
47    pub bunny_storage: bunny_api_tokio::EdgeStorageClient,
48    pub mail_client: MailClient,
49}
50
51#[tokio::main]
52async fn main() -> Result<(), Error> {
53    tracing_subscriber::fmt::init();
54
55    let args = Args::parse();
56
57    let config = ConfigBuilder::load(args.config).await?.build();
58
59    let web = config.web.clone();
60
61    // create a new connection pool with the default config
62    let pool_config =
63        AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new(config.database.url());
64    let pool = Pool::builder(pool_config).build()?;
65
66    let cache_pool = redis::Client::open(config.cache_database.url())?;
67
68    let bunny = config.bunny.clone();
69
70    let bunny_storage =
71        bunny_api_tokio::EdgeStorageClient::new(bunny.api_key, bunny.endpoint, bunny.storage_zone)
72            .await?;
73
74    let mail = config.mail.clone();
75
76    let mail_client = MailClient::new(
77        mail.smtp.credentials(),
78        mail.smtp.server,
79        mail.address,
80        mail.tls,
81    )?;
82
83    let database_url = config.database.url();
84
85    tokio::task::spawn_blocking(move || {
86        use diesel::prelude::Connection;
87        use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
88
89        let mut conn =
90            AsyncConnectionWrapper::<diesel_async::AsyncPgConnection>::establish(&database_url)?;
91
92        conn.run_pending_migrations(MIGRATIONS)?;
93        Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
94    })
95    .await?
96    .unwrap();
97
98    /*
99    **Stored for later possible use**
100
101        CREATE TABLE IF NOT EXISTS emojis (
102            uuid uuid PRIMARY KEY NOT NULL,
103            name varchar(32) NOT NULL,
104            guild_uuid uuid REFERENCES guilds(uuid) ON DELETE SET NULL,
105            deleted boolean DEFAULT FALSE
106        );
107        CREATE TABLE IF NOT EXISTS message_reactions (
108            message_uuid uuid NOT NULL REFERENCES messages(uuid),
109            user_uuid uuid NOT NULL REFERENCES users(uuid),
110            emoji_uuid uuid NOT NULL REFERENCES emojis(uuid),
111            PRIMARY KEY (message_uuid, user_uuid, emoji_uuid)
112        )
113    */
114
115    let app_state = Arc::new(AppState {
116        pool,
117        cache_pool,
118        config,
119        // TODO: Possibly implement "pepper" into this (thinking it could generate one if it doesnt exist and store it on disk)
120        argon2: Argon2::default(),
121        start_time: SystemTime::now(),
122        bunny_storage,
123        mail_client,
124    });
125
126    let cors = CorsLayer::new()
127        // Allow any origin (equivalent to allowed_origin_fn returning true)
128        .allow_origin(AllowOrigin::predicate(|_origin, _request_head| true))
129        .allow_methods(vec![
130            Method::GET,
131            Method::POST,
132            Method::PUT,
133            Method::DELETE,
134            Method::HEAD,
135            Method::OPTIONS,
136            Method::CONNECT,
137            Method::PATCH,
138            Method::TRACE,
139        ])
140        .allow_headers(vec![
141            header::ACCEPT,
142            header::ACCEPT_LANGUAGE,
143            header::AUTHORIZATION,
144            header::CONTENT_LANGUAGE,
145            header::CONTENT_TYPE,
146            header::ORIGIN,
147            header::ACCEPT,
148            header::COOKIE,
149            "x-requested-with".parse().unwrap(),
150        ])
151        // Allow credentials
152        .allow_credentials(true);
153
154    /*let (socket_io, io) = SocketIo::builder()
155        .with_state(app_state.clone())
156        .build_layer();
157
158    io.ns("/", socket::on_connect);
159    */
160    // build our application with a route
161    let app = Router::new()
162        // `GET /` goes to `root`
163        .merge(api::router(
164            web.backend_url.path().trim_end_matches("/"),
165            app_state.clone(),
166        ))
167        .with_state(app_state)
168        //.layer(socket_io)
169        .layer(cors);
170
171    // run our app with hyper, listening globally on port 3000
172    let listener = tokio::net::TcpListener::bind(web.ip + ":" + &web.port.to_string()).await?;
173    axum::serve(listener, app).await?;
174
175    Ok(())
176}