entertainarr-adapter-http 0.1.0

HTTP adapter for entertainarr
Documentation
use std::collections::HashSet;

use axum::Json;
use axum::extract::{Path, State};
use entertainarr_domain::podcast::entity::FindPodcastEpisodeResponse;
use entertainarr_domain::podcast::prelude::PodcastService;
use serde_qs::axum::QsQuery;

use crate::entity::podcast::{PodcastDocument, PodcastEntity, PodcastRelationships};
use crate::entity::podcast_episode::{
    PodcastEpisodeDocument, PodcastEpisodeInclude, PodcastEpisodeProgressEntity,
    PodcastEpisodeRelation, PodcastEpisodeRelationships,
};
use crate::entity::podcast_subscription::PodcastSubscriptionEntity;
use crate::entity::prelude::FindQueryParams;
use crate::entity::task::TaskEntity;
use crate::entity::{ApiResource, Couple, Relation};
use crate::server::extractor::user::CurrentUser;
use crate::server::handler::error::ApiErrorResponse;
use crate::server::handler::prelude::FromDomainResponse;

pub async fn handle<S>(
    State(state): State<S>,
    CurrentUser(user_id): CurrentUser,
    Path(episode_id): Path<u64>,
    QsQuery(params): QsQuery<FindQueryParams<PodcastEpisodeInclude>>,
) -> Result<Json<ApiResource<PodcastEpisodeDocument, PodcastEpisodeRelation>>, ApiErrorResponse>
where
    S: crate::server::prelude::ServerState,
{
    let response = state
        .podcast_service()
        .find_podcast_episode_by_id(user_id, episode_id)
        .await
        .map_err(|err| {
            tracing::error!(error = ?err, "unable to fetch podcast episode");
            ApiErrorResponse::internal()
        })?
        .ok_or_else(|| ApiErrorResponse::not_found("podcast episode not found"))?;

    Ok(Json(ApiResource::from_response(response, params.include)))
}

impl FromDomainResponse<FindPodcastEpisodeResponse, PodcastEpisodeInclude>
    for ApiResource<PodcastEpisodeDocument, PodcastEpisodeRelation>
{
    fn from_response(
        response: FindPodcastEpisodeResponse,
        required: HashSet<PodcastEpisodeInclude>,
    ) -> Self {
        let data = PodcastEpisodeDocument {
            id: response.episode.id,
            kind: Default::default(),
            relationships: PodcastEpisodeRelationships {
                podcast: Relation {
                    data: Some(PodcastEntity::new(response.episode.podcast_id)),
                    meta: (),
                },
                progress: Relation {
                    data: response.progress.as_ref().map(|p| {
                        PodcastEpisodeProgressEntity::new(Couple(p.podcast_episode_id, p.user_id))
                    }),
                    meta: (),
                },
            },
            attributes: response.episode.into(),
        };

        let mut includes = Vec::with_capacity(2);
        if required.contains(&PodcastEpisodeInclude::Podcast) {
            includes.push(PodcastEpisodeRelation::Podcast(PodcastDocument {
                id: response.podcast.id,
                kind: Default::default(),
                relationships: PodcastRelationships {
                    subscription: Relation {
                        data: response.subscription.as_ref().map(|s| {
                            PodcastSubscriptionEntity::new(Couple(s.podcast_id, s.user_id))
                        }),
                        meta: None,
                    },
                    synchronization: Relation {
                        data: response
                            .synchronization
                            .as_ref()
                            .map(|s| TaskEntity::new(s.id)),
                        meta: (),
                    },
                },
                attributes: response.podcast.into(),
            }));
        }
        if required.contains(&PodcastEpisodeInclude::PodcastEpisodeProgress)
            && let Some(progress) = response.progress
        {
            includes.push(PodcastEpisodeRelation::PodcastEpisodeProgress(
                progress.into(),
            ));
        }

        ApiResource { data, includes }
    }
}