entertainarr-adapter-http 0.1.0

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

use axum::Json;
use axum::extract::State;
use entertainarr_domain::podcast::entity::ListPodcastEpisodeResponse;
use entertainarr_domain::podcast::prelude::PodcastService;
use serde_qs::axum::QsQuery;

use crate::entity::podcast::{PodcastDocument, PodcastEntity, PodcastRelationships};
use crate::entity::podcast_episode::{
    ListPodcastEpisodeParams, PodcastEpisodeDocument, PodcastEpisodeInclude,
    PodcastEpisodeProgressEntity, PodcastEpisodeRelation, PodcastEpisodeRelationships,
};
use crate::entity::podcast_subscription::PodcastSubscriptionEntity;
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,
    QsQuery(params): QsQuery<ListPodcastEpisodeParams>,
) -> Result<Json<ApiResource<Vec<PodcastEpisodeDocument>, PodcastEpisodeRelation>>, ApiErrorResponse>
where
    S: crate::server::prelude::ServerState,
{
    let response = state
        .podcast_service()
        .list_podcast_episode(
            entertainarr_domain::podcast::entity::ListPodcastEpisodeParams {
                user_id,
                filter: entertainarr_domain::podcast::entity::ListPodcastEpisodeFilter {
                    podcast_ids: &[],
                    filtered: params.filter.filtered,
                    subscribed: params.filter.subscribed,
                    watched: params.filter.watched,
                },
                sort: params.sort.into(),
                page: params.page.into(),
            },
        )
        .await
        .map_err(|err| {
            tracing::error!(error = ?err, "unable to list podcast episodes");
            ApiErrorResponse::internal()
        })?;

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

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

        let mut includes = Vec::with_capacity(
            response.podcasts.len()
                + response.progresses.len()
                + response.subscriptions.len()
                + response.synchronizations.len(),
        );
        if required.contains(&PodcastEpisodeInclude::Podcast) {
            includes.extend(response.podcasts.into_values().map(|podcast| {
                PodcastEpisodeRelation::Podcast(PodcastDocument {
                    id: podcast.id,
                    kind: Default::default(),
                    relationships: PodcastRelationships {
                        subscription: Relation {
                            data: response.subscriptions.get(&podcast.id).map(|s| {
                                PodcastSubscriptionEntity::new(Couple(s.podcast_id, s.user_id))
                            }),
                            meta: None,
                        },
                        synchronization: Relation {
                            data: response
                                .synchronizations
                                .get(&podcast.id)
                                .map(|s| TaskEntity::new(s.id)),
                            meta: (),
                        },
                    },
                    attributes: podcast.into(),
                })
            }));
        }
        if required.contains(&PodcastEpisodeInclude::PodcastEpisodeProgress) {
            includes.extend(
                response.progresses.into_values().map(|progress| {
                    PodcastEpisodeRelation::PodcastEpisodeProgress(progress.into())
                }),
            );
        }

        ApiResource { data, includes }
    }
}