entertainarr-adapter-http 0.1.1

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

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

use crate::entity::podcast::{
    PodcastAttributes, PodcastDocument, PodcastInclude, PodcastRelation, PodcastRelationships,
};
use crate::entity::podcast_subscription::{
    PodcastSubscriptionAttributes, PodcastSubscriptionDocument, 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,
    QsQuery(params): QsQuery<FindQueryParams<PodcastInclude>>,
) -> Result<Json<ApiResource<Vec<PodcastDocument>, PodcastRelation>>, ApiErrorResponse>
where
    S: crate::server::prelude::ServerState,
{
    let response = state
        .podcast_service()
        .list_podcast(entertainarr_domain::podcast::entity::ListPodcastParams {
            user_id: Some(user_id),
            filter: entertainarr_domain::podcast::entity::ListPodcastFilter {
                podcast_ids: &[],
                subscribed: Some(true),
            },
            page: Page {
                limit: 100,
                offset: 0,
            },
        })
        .await
        .map_err(|err| {
            tracing::error!(error = ?err, "unable to list user podcasts");
            ApiErrorResponse::internal()
        })?;

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

impl FromDomainResponse<ListPodcastResponse, PodcastInclude>
    for ApiResource<Vec<PodcastDocument>, PodcastRelation>
{
    fn from_response(response: ListPodcastResponse, required: HashSet<PodcastInclude>) -> Self {
        let data = response
            .podcasts
            .into_iter()
            .map(|podcast| PodcastDocument {
                id: podcast.id,
                kind: Default::default(),
                relationships: PodcastRelationships {
                    subscription: Relation {
                        data: response.subscriptions.get(&podcast.id).map(|sub| {
                            PodcastSubscriptionEntity::new(Couple(sub.podcast_id, sub.user_id))
                        }),
                        meta: None,
                    },
                    synchronization: Relation {
                        data: response
                            .synchronizations
                            .get(&podcast.id)
                            .map(|sync| TaskEntity::new(sync.id)),
                        meta: (),
                    },
                },
                attributes: PodcastAttributes::from(podcast),
            })
            .collect();

        let mut includes = Vec::with_capacity(2);

        if required.contains(&PodcastInclude::Subscription) {
            includes.extend(response.subscriptions.into_values().map(|sub| {
                PodcastRelation::PocastSubscription(PodcastSubscriptionDocument {
                    id: Couple(sub.podcast_id, sub.user_id),
                    kind: Default::default(),
                    attributes: PodcastSubscriptionAttributes {
                        min_duration: sub.min_duration,
                        max_duration: sub.max_duration,
                        created_at: sub.created_at,
                    },
                })
            }));
        }

        if required.contains(&PodcastInclude::Synchronization) {
            includes.extend(
                response
                    .synchronizations
                    .into_values()
                    .map(|sync| PodcastRelation::Task(sync.into())),
            );
        }
        ApiResource { data, includes }
    }
}