entertainarr 0.1.1

entertainarr server
Documentation
use anyhow::Context;
use entertainarr_adapter_rss::RssClient;
use entertainarr_adapter_sqlite::Pool;
use entertainarr_adapter_worker::Publisher;
use entertainarr_domain::auth::AuthenticationService;
use entertainarr_domain::media::MediaService;
use entertainarr_domain::podcast::PodcastService;
use entertainarr_domain::task::TaskService;
use entertainarr_domain::tvshow::TvShowService;

mod client;
pub mod tracing;

/// Entertainarr main configuration
#[derive(serde::Deserialize)]
pub struct Config {
    #[serde(default)]
    pub http_server: entertainarr_adapter_http::server::Config,
    #[serde(default)]
    pub jsonwebtoken: entertainarr_adapter_jsonwebtoken::Config,
    #[serde(default)]
    pub rss: entertainarr_adapter_rss::Config,
    #[serde(default)]
    pub scheduler: entertainarr_adapter_scheduler::Config,
    #[serde(default)]
    pub sqlite: entertainarr_adapter_sqlite::Config,
    #[serde(default)]
    pub storage: entertainarr_adapter_filesystem::Config,
    pub tmdb: entertainarr_adapter_tmdb::Config,
    #[serde(default)]
    pub worker: entertainarr_adapter_worker::Config,
}

impl Config {
    pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> anyhow::Result<Self> {
        let data = std::fs::read(path.as_ref())
            .with_context(|| format!("unable to open configuration file on {:?}", path.as_ref()))?;
        toml::from_slice(data.as_ref()).context("unable to deserialize config")
    }

    pub async fn build(self) -> anyhow::Result<Application> {
        let http_server = self.http_server.builder()?;
        let jsonwebtoken = self.jsonwebtoken.build()?;
        let rss_client = self.rss.build()?;
        let scheduler = self.scheduler.build();
        let sqlite_pool = self.sqlite.build().await?;
        let storage = self.storage.build().await?;
        let tmdb = self.tmdb.build()?;
        let worker = self.worker.build();
        let publisher = worker.publisher(sqlite_pool.clone());
        let authentication_service = AuthenticationService::builder()
            .authentication_repository(sqlite_pool.clone())
            .token_repository(jsonwebtoken)
            .build();
        let media_service = MediaService::builder()
            .media_repository(sqlite_pool.clone())
            .media_store(storage)
            .task_publisher(publisher.clone())
            .build();
        let podcast_service = PodcastService::builder()
            .rss_feed_loader(rss_client)
            .podcast_repository(sqlite_pool.clone())
            .podcast_episode_repository(sqlite_pool.clone())
            .podcast_subscription_repository(sqlite_pool.clone())
            .podcast_task_repository(sqlite_pool.clone())
            .task_publisher(publisher.clone())
            .build();
        let task_service = TaskService::builder()
            .task_repository(sqlite_pool.clone())
            .build();
        let tvshow_service = TvShowService::builder()
            .task_publisher(publisher.clone())
            .tvshow_provider(tmdb)
            .tvshow_repository(sqlite_pool.clone())
            .tvshow_episode_repository(sqlite_pool.clone())
            .tvshow_episode_file_repository(sqlite_pool.clone())
            .tvshow_season_repository(sqlite_pool.clone())
            .tvshow_subscription_repository(sqlite_pool.clone())
            .build();

        let http_server = http_server
            .with_authentication_service(authentication_service)
            .with_client_service(crate::client::ClientService)
            .with_media_service(media_service.clone())
            .with_podcast_service(podcast_service.clone())
            .with_tvshow_service(tvshow_service.clone())
            .build()?;
        let scheduler = scheduler
            .with_media_service(media_service.clone())
            .with_podcast_service(podcast_service.clone())
            .with_tvshow_service(tvshow_service.clone());
        let worker = worker
            .with_media_service(media_service)
            .with_podcast_service(podcast_service)
            .with_task_service(task_service)
            .with_tvshow_service(tvshow_service);

        Ok(Application {
            http_server,
            scheduler,
            worker,
        })
    }
}

/// Entertainarr application
pub struct Application {
    http_server: entertainarr_adapter_http::server::HttpServer,
    #[allow(clippy::type_complexity)]
    scheduler: entertainarr_adapter_scheduler::Runner<
        MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
        PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
        TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
    >,
    #[allow(clippy::type_complexity)]
    worker: entertainarr_adapter_worker::Runner<
        MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
        PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
        TaskService<Pool>,
        TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
    >,
}

impl Application {
    pub async fn run(self) -> anyhow::Result<()> {
        let scheduler = self.scheduler.start();
        let worker = self.worker.start();
        let server_res = self.http_server.run().await;
        let scheduler_res = scheduler.shutdown().await;
        let worker_res = worker.shutdown().await;
        match (server_res, scheduler_res, worker_res) {
            (Ok(_), Ok(_), Ok(_)) => Ok(()),
            other => Err(anyhow::anyhow!(
                "server, scheduler or worker failed {other:?}"
            )),
        }
    }
}