feat(poster-fetcher): add poster fetcher adapter with configuration and integration
This commit is contained in:
@@ -4,6 +4,7 @@ JWT_SECRET=
|
||||
JWT_TTL_SECONDS=
|
||||
ALLOW_REGISTRATION=true
|
||||
OMDB_API_KEY=
|
||||
POSTER_FETCH_TIMEOUT_SECONDS=30
|
||||
MINIO_ENDPOINT=
|
||||
MINIO_ACCESS_KEY_ID=
|
||||
MINIO_SECRET_ACCESS_KEY=
|
||||
|
||||
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -1620,6 +1620,16 @@ version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||
|
||||
[[package]]
|
||||
name = "poster-fetcher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"domain",
|
||||
"reqwest 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poster-storage"
|
||||
version = "0.1.0"
|
||||
@@ -1671,6 +1681,7 @@ dependencies = [
|
||||
"dotenvy",
|
||||
"http-body-util",
|
||||
"metadata",
|
||||
"poster-fetcher",
|
||||
"poster-storage",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/adapters/auth",
|
||||
"crates/adapters/metadata", "crates/adapters/poster-storage",
|
||||
"crates/adapters/metadata", "crates/adapters/poster-fetcher", "crates/adapters/poster-storage",
|
||||
"crates/adapters/rss",
|
||||
"crates/adapters/sqlite",
|
||||
"crates/adapters/template-askama",
|
||||
@@ -39,6 +39,7 @@ application = { path = "crates/application" }
|
||||
presentation = { path = "crates/presentation" }
|
||||
auth = { path = "crates/adapters/auth" }
|
||||
metadata = { path = "crates/adapters/metadata" }
|
||||
poster-fetcher = { path = "crates/adapters/poster-fetcher" }
|
||||
poster-storage = { path = "crates/adapters/poster-storage" }
|
||||
rss = { path = "crates/adapters/rss" }
|
||||
sqlite = { path = "crates/adapters/sqlite" }
|
||||
|
||||
10
crates/adapters/poster-fetcher/Cargo.toml
Normal file
10
crates/adapters/poster-fetcher/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "poster-fetcher"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
13
crates/adapters/poster-fetcher/src/config.rs
Normal file
13
crates/adapters/poster-fetcher/src/config.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub struct PosterFetcherConfig {
|
||||
pub timeout_seconds: u64,
|
||||
}
|
||||
|
||||
impl PosterFetcherConfig {
|
||||
pub fn from_env() -> Self {
|
||||
let timeout_seconds = std::env::var("POSTER_FETCH_TIMEOUT_SECONDS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(30);
|
||||
Self { timeout_seconds }
|
||||
}
|
||||
}
|
||||
38
crates/adapters/poster-fetcher/src/lib.rs
Normal file
38
crates/adapters/poster-fetcher/src/lib.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
mod config;
|
||||
pub use config::PosterFetcherConfig;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, ports::PosterFetcherClient, value_objects::PosterUrl};
|
||||
|
||||
pub struct ReqwestPosterFetcher {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl ReqwestPosterFetcher {
|
||||
pub fn new(config: PosterFetcherConfig) -> anyhow::Result<Self> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(config.timeout_seconds))
|
||||
.build()?;
|
||||
Ok(Self { client })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PosterFetcherClient for ReqwestPosterFetcher {
|
||||
async fn fetch_poster_bytes(&self, poster_url: &PosterUrl) -> Result<Vec<u8>, DomainError> {
|
||||
let bytes = self
|
||||
.client
|
||||
.get(poster_url.value())
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?
|
||||
.error_for_status()
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
|
||||
Ok(bytes.to_vec())
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ domain = { workspace = true }
|
||||
application = { workspace = true }
|
||||
auth = { workspace = true }
|
||||
metadata = { workspace = true }
|
||||
poster-fetcher = { workspace = true }
|
||||
poster-storage = { workspace = true }
|
||||
sqlite = { workspace = true }
|
||||
sqlx = { workspace = true }
|
||||
|
||||
@@ -2,12 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
ports::{EventPublisher, PosterFetcherClient},
|
||||
value_objects::PosterUrl,
|
||||
};
|
||||
use domain::{errors::DomainError, events::DomainEvent, ports::EventPublisher};
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::net::TcpListener;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
@@ -15,23 +10,13 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use application::{config::AppConfig, context::AppContext};
|
||||
use auth::{AuthConfig, Argon2PasswordHasher, JwtAuthService};
|
||||
use metadata::MetadataClientImpl;
|
||||
use poster_fetcher::{PosterFetcherConfig, ReqwestPosterFetcher};
|
||||
use poster_storage::{PosterStorageAdapter, StorageConfig};
|
||||
use sqlite::{SqliteMovieRepository, SqliteUserRepository};
|
||||
use template_askama::AskamaHtmlRenderer;
|
||||
|
||||
use presentation::{routes, state::AppState};
|
||||
|
||||
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 StubEventPublisher;
|
||||
|
||||
#[async_trait]
|
||||
@@ -81,7 +66,7 @@ async fn wire_dependencies() -> anyhow::Result<AppState> {
|
||||
let app_ctx = AppContext {
|
||||
repository: Arc::new(movie_repo),
|
||||
metadata_client: Arc::new(MetadataClientImpl::new_omdb(omdb_api_key)),
|
||||
poster_fetcher: Arc::new(StubPosterFetcher),
|
||||
poster_fetcher: Arc::new(ReqwestPosterFetcher::new(PosterFetcherConfig::from_env())?),
|
||||
poster_storage: Arc::new(PosterStorageAdapter::from_config(storage_config)?),
|
||||
event_publisher: Arc::new(StubEventPublisher),
|
||||
auth_service: Arc::new(JwtAuthService::new(auth_config)),
|
||||
|
||||
Reference in New Issue
Block a user