pub mod account_deletion; pub mod assets; pub mod cleanup; pub mod middleware; pub mod openapi; use actix_cors::Cors; use actix_web::http::Method; use actix_web::{Scope, web}; use export::AppState; use export::http::{Guard, Router}; pub const MAX_REQUEST_BODY_BYTES: usize = 2 * 1024 * 1024; pub fn build_cors(web: &config::web::WebConfig) -> Cors { let mut cors = Cors::default(); for origin in &web.cors_allow_origins { cors = cors.allowed_origin(origin); } let mut cors = cors .allowed_methods([ Method::GET, Method::POST, Method::PUT, Method::PATCH, Method::DELETE, Method::OPTIONS, ]) .allowed_headers(["content-type", "authorization", "x-request-id"]); if web.cors_allow_credentials { cors = cors.supports_credentials(); } cors } pub fn scope(state: AppState) -> Scope { let accounts = auth::Accounts::new(state.database.connection().clone(), state.cache.clone()); let access = space::Access::new(state.database.connection().clone(), state.cache.clone()); let notifications = export::notify::Notifications::new(state.database.clone(), state.redis.clone()); let auth = auth::AuthService::new( accounts.clone(), auth::services::AuthSettings::from_config(&state.config), state.mail.clone(), ); let chat = build_chat_service(&state, accounts.clone()); let issue_audience = issues::services::audience::Audience::new( accounts.clone(), notifications, state.mail.clone(), i18n::config_fallback(state.config.email.default_locale()), ); let issues = issues::services::service::IssueService::new( state.database.clone(), state.storage.clone(), issue_audience, issues::services::service::IssueSettings::from_config(&state.config), issues::services::admission::Admission::new( access.clone(), state.redis.clone(), state.config.quota.clone(), ), ); let space = space::services::service::SpaceService::new( access.clone(), space::services::images::SpaceImages::new( access.clone(), state.storage.clone(), state.redis.clone(), ), space::services::policy::SpacePolicy::new( space::services::service::SpaceSettings::from_config(&state.config), accounts.clone(), state.redis.clone(), ), export::notify::Notifications::new(state.database.clone(), state.redis.clone()), ); let user = user::services::service::UserService::new( state.database.clone(), accounts.clone(), access.clone(), user::services::avatar::AvatarService::new( state.database.clone(), state.storage.clone(), state.redis.clone(), ), ); let git_config = state.config.git.clone(); let ks = proto::KenShellClient::connect_with_key( &git_config.kenshell_endpoint, &git_config.kenshell_rpc_key, std::time::Duration::from_secs(git_config.kenshell_timeout_secs.max(1)), ) .unwrap_or_else(|error| { panic!( "invalid git.kenshell_endpoint `{}`: {error}", git_config.kenshell_endpoint ) }); let git = git::service::GitService::new( state.database.clone(), accounts.clone(), access, git::service::GitBackend::new(state.cache.clone(), git_config, std::sync::Arc::new(ks)), ); let pulls = pulls::services::service::PullService::new( state.database.clone(), git.clone(), issues.clone(), pulls::services::actors::Actors::new( accounts.clone(), state.redis.clone(), state.config.quota.clone(), ), ); let internal_service_key = state.config.internal.service_key.clone(); let cleanup_router = cleanup::router( state.database.clone(), space.clone(), issues.clone(), git.clone(), ); let account_deletion = Router::::new() .route( "/me/deletion", export::http::get(account_deletion::account_deletion_status) .post(account_deletion::request_account_deletion) .delete(account_deletion::cancel_account_deletion), ) .wrap(Guard::Session) .with_state(state.clone()); let accounts_for_http = accounts.clone(); let push_config = state.config.push.clone(); let public = Router::<()>::new() .setup(move |scope| { scope .app_data(web::Data::new(accounts_for_http)) .app_data(web::Data::new(push_config)) }) .merge(account_deletion) .merge(::auth::routes::router(auth.clone()).with_state(auth.clone())) .merge(chat::routes::router(chat.clone(), space.clone()).with_state(chat.clone())) .merge(issues::routes::router(issues.clone()).with_state(issues)) .merge(space::routes::router(space.clone()).with_state(space)) .merge(user::routes::router(user.clone()).with_state(user)) .merge(git::routes::router(git.clone()).with_state(git.clone())) .merge(pulls::routes::router().with_state(pulls)) .merge(cleanup_router); let internal = Router::<()>::new() .merge(git::routes::internal_router(git.clone()).with_state(git)) .merge(chat::routes::internal_router(chat.clone()).with_state(chat)) .with_state(internal_service_key) .wrap(Guard::ServiceKey); Router::<()>::new() .nest("/api/v1/internal", internal) .nest("/api/v1", public) .nest("/pages", ::auth::routes::pages_router(auth.clone())) .merge(export::health::router(state)) .into_scope(web::scope("")) .default_service(web::to(assets::serve)) } pub fn build_chat_service( state: &AppState, accounts: auth::Accounts, ) -> chat::services::service::ChatService { let audience = chat::services::audience::Audience::new( accounts, export::notify::Notifications::new(state.database.clone(), state.redis.clone()), state.mail.clone(), chat::share::realtime::Realtime::new(state.database.clone()), i18n::config_fallback(state.config.email.default_locale()), ); let participants = chat::services::presence::Participants::new( state.database.clone(), state.redis.clone(), space::Access::new(state.database.connection().clone(), state.cache.clone()), audience.clone(), chat::services::presence::ParticipantSettings::from_config(&state.config), ); chat::services::service::ChatService::new( state.database.clone(), chat::services::media::Media::new( state.storage.clone(), state.redis.clone(), state.database.clone(), ), audience, chat::services::service::ChatSettings::from_config(&state.config), participants, ) }