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;
26pub 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 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 let app_state = Arc::new(AppState {
116 pool,
117 cache_pool,
118 config,
119 argon2: Argon2::default(),
121 start_time: SystemTime::now(),
122 bunny_storage,
123 mail_client,
124 });
125
126 let cors = CorsLayer::new()
127 .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(true);
153
154 let app = Router::new()
162 .merge(api::router(
164 web.backend_url.path().trim_end_matches("/"),
165 app_state.clone(),
166 ))
167 .with_state(app_state)
168 .layer(cors);
170
171 let listener = tokio::net::TcpListener::bind(web.ip + ":" + &web.port.to_string()).await?;
173 axum::serve(listener, app).await?;
174
175 Ok(())
176}