use anyhow::Result; use axum::Router; use axum::http::HeaderValue; use std::sync::Arc; use tower_http::{ cors::{Any, CorsLayer}, trace::TraceLayer, }; use adapters_auth::{BcryptPasswordHasher, JwtTokenIssuer}; use adapters_postgres::{ PostgresAlbumRepository, PostgresAssetMetadataRepository, PostgresAssetRepository, PostgresDuplicateRepository, PostgresIngestSessionRepository, PostgresJobBatchRepository, PostgresJobRepository, PostgresLibraryPathRepository, PostgresPipelineRepository, PostgresPluginRepository, PostgresQuotaRepository, PostgresShareRepository, PostgresSidecarRepository, PostgresStorageVolumeRepository, PostgresTagRepository, PostgresUsageLedgerRepository, PostgresUserRepository, PostgresVisibilityFilterRepository, connect, run_migrations, }; use adapters_storage::LocalFileStorage; use application::{ catalog::{ GetAssetHandler, GetTimelineHandler, ReadAssetFileHandler, RegisterAssetHandler, UpdateMetadataHandler, }, identity::{GetProfileHandler, LoginUserHandler, RegisterUserHandler}, organization::{ CreateAlbumHandler, GetAlbumHandler, ManageAlbumEntriesHandler, TagAssetHandler, }, processing::{ CompleteJobHandler, ConfigurePipelineHandler, EnqueueJobHandler, FailJobHandler, ManagePluginHandler, ReportBatchProgressHandler, StartJobHandler, }, sharing::{ AccessSharedResourceHandler, GenerateShareLinkHandler, RevokeShareHandler, ShareResourceHandler, }, sidecar::{ DetectExternalChangesHandler, ExportSidecarHandler, FullExportHandler, FullImportHandler, ImportSidecarHandler, ResolveConflictHandler, }, storage::{ CheckQuotaHandler, IngestAssetHandler, RegisterLibraryPathHandler, RegisterVolumeHandler, }, }; use presentation::{ routes::app_router, state::{ AppState, CatalogHandlers, IdentityHandlers, OrganizationHandlers, ProcessingHandlers, SharingHandlers, SidecarHandlers, StorageHandlers, }, }; use crate::config::Config; use crate::log_event_publisher::LogEventPublisher; use crate::log_sidecar_writer::LogSidecarWriter; pub async fn build_app(config: &Config) -> Result { let pool = connect(&config.database_url).await?; run_migrations(&pool).await?; // Identity let user_repo = Arc::new(PostgresUserRepository::new(pool.clone())); let hasher = Arc::new(BcryptPasswordHasher); let issuer = Arc::new(JwtTokenIssuer::new(&config.jwt_secret)); let register_handler = Arc::new(RegisterUserHandler::new(user_repo.clone(), hasher.clone())); let login_handler = Arc::new(LoginUserHandler::new( user_repo.clone(), hasher, issuer.clone(), )); let get_profile_handler = Arc::new(GetProfileHandler::new(user_repo)); // Repos let album_repo = Arc::new(PostgresAlbumRepository::new(pool.clone())); let asset_repo = Arc::new(PostgresAssetRepository::new(pool.clone())); let metadata_repo = Arc::new(PostgresAssetMetadataRepository::new(pool.clone())); let volume_repo = Arc::new(PostgresStorageVolumeRepository::new(pool.clone())); let path_repo = Arc::new(PostgresLibraryPathRepository::new(pool.clone())); let session_repo = Arc::new(PostgresIngestSessionRepository::new(pool.clone())); let quota_repo = Arc::new(PostgresQuotaRepository::new(pool.clone())); let ledger_repo = Arc::new(PostgresUsageLedgerRepository::new(pool.clone())); let tag_repo = Arc::new(PostgresTagRepository::new(pool.clone())); let duplicate_repo = Arc::new(PostgresDuplicateRepository::new(pool.clone())); let sidecar_repo = Arc::new(PostgresSidecarRepository::new(pool.clone())); let job_repo = Arc::new(PostgresJobRepository::new(pool.clone())); let batch_repo = Arc::new(PostgresJobBatchRepository::new(pool.clone())); let plugin_repo = Arc::new(PostgresPluginRepository::new(pool.clone())); let pipeline_repo = Arc::new(PostgresPipelineRepository::new(pool.clone())); let event_publisher: Arc = Arc::new(LogEventPublisher); let sidecar_writer: Arc = Arc::new(LogSidecarWriter); // File storage let storage_path = std::env::var("STORAGE_PATH").unwrap_or_else(|_| "./data/media".to_string()); let file_storage: Arc = Arc::new(LocalFileStorage::new(&storage_path)); // Album handlers let create_album_handler = Arc::new(CreateAlbumHandler::new(album_repo.clone())); let get_album_handler = Arc::new(GetAlbumHandler::new(album_repo.clone())); let manage_album_entries_handler = Arc::new(ManageAlbumEntriesHandler::new(album_repo)); // Asset handlers let ingest_asset_handler = Arc::new(IngestAssetHandler::new( session_repo, path_repo.clone(), quota_repo.clone(), ledger_repo.clone(), asset_repo.clone(), file_storage.clone(), event_publisher.clone(), )); let get_asset_handler = Arc::new(GetAssetHandler::new( asset_repo.clone(), metadata_repo.clone(), )); let get_timeline_handler = Arc::new(GetTimelineHandler::new( asset_repo.clone(), metadata_repo.clone(), )); let update_metadata_handler = Arc::new(UpdateMetadataHandler::new( asset_repo.clone(), metadata_repo.clone(), event_publisher.clone(), )); let read_asset_file_handler = Arc::new(ReadAssetFileHandler::new(asset_repo.clone(), file_storage)); // Register asset handler let register_asset_handler = Arc::new(RegisterAssetHandler::new( asset_repo.clone(), duplicate_repo, event_publisher.clone(), )); // Tag handler let tag_asset_handler = Arc::new(TagAssetHandler::new(asset_repo.clone(), tag_repo)); // Check quota handler let check_quota_handler = Arc::new(CheckQuotaHandler::new(quota_repo, ledger_repo)); // Sidecar handlers let export_sidecar_handler = Arc::new(ExportSidecarHandler::new( metadata_repo.clone(), sidecar_repo.clone(), sidecar_writer.clone(), )); let detect_changes_handler = Arc::new(DetectExternalChangesHandler::new( sidecar_repo.clone(), sidecar_writer.clone(), )); let import_sidecar_handler = Arc::new(ImportSidecarHandler::new( sidecar_repo.clone(), sidecar_writer.clone(), metadata_repo.clone(), )); let resolve_conflict_handler = Arc::new(ResolveConflictHandler::new( sidecar_repo.clone(), sidecar_writer.clone(), metadata_repo.clone(), )); let full_export_handler = Arc::new(FullExportHandler::new( asset_repo.clone(), metadata_repo.clone(), sidecar_repo.clone(), sidecar_writer.clone(), )); let full_import_handler = Arc::new(FullImportHandler::new( asset_repo, metadata_repo, sidecar_repo, sidecar_writer, )); // Processing handlers let enqueue_job_handler = Arc::new(EnqueueJobHandler::new( job_repo.clone(), event_publisher.clone(), )); let start_job_handler = Arc::new(StartJobHandler::new(job_repo.clone())); let complete_job_handler = Arc::new(CompleteJobHandler::new( job_repo.clone(), batch_repo.clone(), event_publisher.clone(), )); let fail_job_handler = Arc::new(FailJobHandler::new( job_repo.clone(), batch_repo.clone(), event_publisher.clone(), )); let batch_progress_handler = Arc::new(ReportBatchProgressHandler::new(batch_repo, job_repo)); let manage_plugin_handler = Arc::new(ManagePluginHandler::new(plugin_repo.clone())); let configure_pipeline_handler = Arc::new(ConfigurePipelineHandler::new(pipeline_repo, plugin_repo)); // Sharing repos & handlers let share_repo = Arc::new(PostgresShareRepository::new(pool.clone())); let _visibility_filter_repo = Arc::new(PostgresVisibilityFilterRepository::new(pool)); let share_resource_handler = Arc::new(ShareResourceHandler::new( share_repo.clone(), event_publisher.clone(), )); let generate_link_handler = Arc::new(GenerateShareLinkHandler::new(share_repo.clone())); let revoke_handler = Arc::new(RevokeShareHandler::new(share_repo.clone(), event_publisher)); let access_handler = Arc::new(AccessSharedResourceHandler::new(share_repo)); // Storage handlers let register_volume_handler = Arc::new(RegisterVolumeHandler::new(volume_repo.clone())); let register_library_path_handler = Arc::new(RegisterLibraryPathHandler::new(volume_repo, path_repo)); let identity = IdentityHandlers { register: register_handler, login: login_handler, get_profile: get_profile_handler, }; let catalog = CatalogHandlers { ingest_asset: ingest_asset_handler, get_asset: get_asset_handler, get_timeline: get_timeline_handler, update_metadata: update_metadata_handler, read_asset_file: read_asset_file_handler, register_asset: register_asset_handler, }; let organization = OrganizationHandlers { create_album: create_album_handler, get_album: get_album_handler, manage_album_entries: manage_album_entries_handler, tag_asset: tag_asset_handler, }; let storage_handlers = StorageHandlers { register_volume: register_volume_handler, register_library_path: register_library_path_handler, check_quota: check_quota_handler, }; let sidecar = SidecarHandlers { export: export_sidecar_handler, detect_changes: detect_changes_handler, import: import_sidecar_handler, resolve: resolve_conflict_handler, full_export: full_export_handler, full_import: full_import_handler, }; let processing = ProcessingHandlers { enqueue_job: enqueue_job_handler, start_job: start_job_handler, complete_job: complete_job_handler, fail_job: fail_job_handler, batch_progress: batch_progress_handler, manage_plugin: manage_plugin_handler, configure_pipeline: configure_pipeline_handler, }; let sharing = SharingHandlers { share_resource: share_resource_handler, generate_link: generate_link_handler, revoke: revoke_handler, access: access_handler, }; let state = AppState { identity, catalog, organization, storage: storage_handlers, sharing, sidecar, processing, token_issuer: issuer, }; let cors = CorsLayer::new() .allow_origin( config .cors_allowed_origins .iter() .filter_map(|o| o.parse::().ok()) .collect::>(), ) .allow_methods(Any) .allow_headers(Any); Ok(app_router() .with_state(state) .layer(TraceLayer::new_for_http()) .layer(cors)) }