Files
k-photos/crates/bootstrap/src/factory.rs
Gabriel Kaszewski a6b86c23d8 feat: wire remaining handlers — tag, quota, register asset, sidecar, processing
14 new endpoints: POST tags, GET quota, POST register, 6 sidecar, 7 processing.
DTOs, AppState groups, LogSidecarWriter, full bootstrap wiring.
2026-05-31 11:04:22 +02:00

297 lines
11 KiB
Rust

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<Router> {
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<LogEventPublisher> = Arc::new(LogEventPublisher);
let sidecar_writer: Arc<LogSidecarWriter> = Arc::new(LogSidecarWriter);
// File storage
let storage_path = std::env::var("STORAGE_PATH").unwrap_or_else(|_| "./data/media".to_string());
let file_storage: Arc<LocalFileStorage> = 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::<HeaderValue>().ok())
.collect::<Vec<_>>(),
)
.allow_methods(Any)
.allow_headers(Any);
Ok(app_router()
.with_state(state)
.layer(TraceLayer::new_for_http())
.layer(cors))
}