backend/
main.rs

1use actix_cors::Cors;
2use actix_web::{App, HttpServer, web};
3use argon2::Argon2;
4use clap::Parser;
5use diesel_async::pooled_connection::AsyncDieselConnectionManager;
6use diesel_async::pooled_connection::deadpool::Pool;
7use error::Error;
8use objects::MailClient;
9use simple_logger::SimpleLogger;
10use std::time::SystemTime;
11mod config;
12use config::{Config, ConfigBuilder};
13use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
14
15pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
16
17type Conn =
18    deadpool::managed::Object<AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>>;
19
20mod api;
21pub mod error;
22pub mod objects;
23pub mod schema;
24pub mod utils;
25
26#[derive(Parser, Debug)]
27#[command(version, about, long_about = None)]
28struct Args {
29    #[arg(short, long, default_value_t = String::from("/etc/gorb/config.toml"))]
30    config: String,
31}
32
33#[derive(Clone)]
34pub struct Data {
35    pub pool: deadpool::managed::Pool<
36        AsyncDieselConnectionManager<diesel_async::AsyncPgConnection>,
37        Conn,
38    >,
39    pub cache_pool: redis::Client,
40    pub config: Config,
41    pub argon2: Argon2<'static>,
42    pub start_time: SystemTime,
43    pub bunny_cdn: bunny_api_tokio::Client,
44    pub mail_client: MailClient,
45}
46
47#[tokio::main]
48async fn main() -> Result<(), Error> {
49    SimpleLogger::new()
50        .with_level(log::LevelFilter::Info)
51        .with_colors(true)
52        .env()
53        .init()
54        .unwrap();
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 mut bunny_cdn = bunny_api_tokio::Client::new("").await?;
69
70    let bunny = config.bunny.clone();
71
72    bunny_cdn
73        .storage
74        .init(bunny.api_key, bunny.endpoint, bunny.storage_zone)
75        .await?;
76
77    let mail = config.mail.clone();
78
79    let mail_client = MailClient::new(
80        mail.smtp.credentials(),
81        mail.smtp.server,
82        mail.address,
83        mail.tls,
84    )?;
85
86    let database_url = config.database.url();
87
88    tokio::task::spawn_blocking(move || {
89        use diesel::prelude::Connection;
90        use diesel_async::async_connection_wrapper::AsyncConnectionWrapper;
91
92        let mut conn =
93            AsyncConnectionWrapper::<diesel_async::AsyncPgConnection>::establish(&database_url)?;
94
95        conn.run_pending_migrations(MIGRATIONS)?;
96        Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
97    })
98    .await?
99    .unwrap();
100
101    /*
102    **Stored for later possible use**
103
104        CREATE TABLE IF NOT EXISTS emojis (
105            uuid uuid PRIMARY KEY NOT NULL,
106            name varchar(32) NOT NULL,
107            guild_uuid uuid REFERENCES guilds(uuid) ON DELETE SET NULL,
108            deleted boolean DEFAULT FALSE
109        );
110        CREATE TABLE IF NOT EXISTS message_reactions (
111            message_uuid uuid NOT NULL REFERENCES messages(uuid),
112            user_uuid uuid NOT NULL REFERENCES users(uuid),
113            emoji_uuid uuid NOT NULL REFERENCES emojis(uuid),
114            PRIMARY KEY (message_uuid, user_uuid, emoji_uuid)
115        )
116    */
117
118    let data = Data {
119        pool,
120        cache_pool,
121        config,
122        // TODO: Possibly implement "pepper" into this (thinking it could generate one if it doesnt exist and store it on disk)
123        argon2: Argon2::default(),
124        start_time: SystemTime::now(),
125        bunny_cdn,
126        mail_client,
127    };
128
129    HttpServer::new(move || {
130        // Set CORS headers
131        let cors = Cors::default()
132            /*
133                Set Allowed-Control-Allow-Origin header to whatever
134                the request's Origin header is. Must be done like this
135                rather than setting it to "*" due to CORS not allowing
136                sending of credentials (cookies) with wildcard origin.
137            */
138            .allowed_origin_fn(|_origin, _req_head| true)
139            /*
140                Allows any request method in CORS preflight requests.
141                This will be restricted to only ones actually in use later.
142            */
143            .allow_any_method()
144            /*
145                Allows any header(s) in request in CORS preflight requests.
146                This wll be restricted to only ones actually in use later.
147            */
148            .allow_any_header()
149            /*
150                Allows browser to include cookies in requests.
151                This is needed for receiving the secure HttpOnly refresh_token cookie.
152            */
153            .supports_credentials();
154
155        App::new()
156            .app_data(web::Data::new(data.clone()))
157            .wrap(cors)
158            .service(api::web(data.config.web.backend_url.path()))
159    })
160    .bind((web.ip, web.port))?
161    .run()
162    .await?;
163
164    Ok(())
165}