presentation wiring

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 09:30:20 +02:00
parent 97a496553a
commit 79a06e6844
18 changed files with 883 additions and 18 deletions

View File

@@ -1,4 +1,153 @@
use std::sync::Arc;
use anyhow::Context;
use async_trait::async_trait;
use domain::{
errors::DomainError,
events::DomainEvent,
models::Movie,
ports::{EventPublisher, MetadataClient, PasswordHasher, PosterFetcherClient, PosterStorage},
value_objects::{ExternalMetadataId, MovieId, PasswordHash, PosterPath, PosterUrl},
};
use sqlx::SqlitePool;
use tokio::net::TcpListener;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use application::context::AppContext;
use auth::StubAuthService;
use sqlite::SqliteMovieRepository;
use template_askama::AskamaHtmlRenderer;
use presentation::{routes, state::AppState};
struct StubMetadataClient;
#[async_trait]
impl MetadataClient for StubMetadataClient {
async fn fetch_movie_metadata(&self, _id: &ExternalMetadataId) -> Result<Movie, DomainError> {
Err(DomainError::InfrastructureError(
"metadata client not implemented".into(),
))
}
async fn get_poster_url(
&self,
_id: &ExternalMetadataId,
) -> Result<Option<PosterUrl>, DomainError> {
Err(DomainError::InfrastructureError(
"metadata client not implemented".into(),
))
}
}
struct StubPosterFetcher;
#[async_trait]
impl PosterFetcherClient for StubPosterFetcher {
async fn fetch_poster_bytes(&self, _url: &PosterUrl) -> Result<Vec<u8>, DomainError> {
Err(DomainError::InfrastructureError(
"poster fetcher not implemented".into(),
))
}
}
struct StubPosterStorage;
#[async_trait]
impl PosterStorage for StubPosterStorage {
async fn store_poster(
&self,
_movie_id: &MovieId,
_bytes: &[u8],
) -> Result<PosterPath, DomainError> {
Err(DomainError::InfrastructureError(
"poster storage not implemented".into(),
))
}
async fn get_poster(&self, _path: &PosterPath) -> Result<Vec<u8>, DomainError> {
Err(DomainError::InfrastructureError(
"poster storage not implemented".into(),
))
}
}
struct StubEventPublisher;
#[async_trait]
impl EventPublisher for StubEventPublisher {
async fn publish(&self, _event: &DomainEvent) -> Result<(), DomainError> {
Ok(())
}
}
struct StubPasswordHasher;
#[async_trait]
impl PasswordHasher for StubPasswordHasher {
async fn hash(&self, _plain: &str) -> Result<PasswordHash, DomainError> {
Err(DomainError::InfrastructureError(
"password hasher not implemented".into(),
))
}
async fn verify(&self, _plain: &str, _hash: &PasswordHash) -> Result<bool, DomainError> {
Err(DomainError::InfrastructureError(
"password hasher not implemented".into(),
))
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
init_tracing();
let state = wire_dependencies()
.await
.context("Failed to wire dependencies")?;
let app = routes::build_router(state);
let listener = TcpListener::bind("0.0.0.0:3000").await?;
tracing::info!("Listening on 0.0.0.0:3000");
axum::serve(listener, app).await?;
Ok(())
}
async fn wire_dependencies() -> anyhow::Result<AppState> {
let pool = SqlitePool::connect("sqlite://reviews.db")
.await
.context("Failed to connect to SQLite database")?;
let repo = SqliteMovieRepository::new(pool);
repo.migrate()
.await
.map_err(|e| anyhow::anyhow!("{}", e))
.context("Database migration failed")?;
let app_ctx = AppContext {
repository: Arc::new(repo),
metadata_client: Arc::new(StubMetadataClient),
poster_fetcher: Arc::new(StubPosterFetcher),
poster_storage: Arc::new(StubPosterStorage),
event_publisher: Arc::new(StubEventPublisher),
auth_service: Arc::new(StubAuthService),
password_hasher: Arc::new(StubPasswordHasher),
};
Ok(AppState {
app_ctx,
html_renderer: Arc::new(AskamaHtmlRenderer::new()),
})
}
fn init_tracing() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG")
.unwrap_or_else(|_| "presentation=debug,tower_http=debug".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
}