entertainarr-adapter-http 0.1.1

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

use axum::routing::{get, put};
use entertainarr_domain::tvshow::entity::{FindTvShowEpisodeResponse, ListTvShowEpisodeResponse};

use crate::entity::media_file::{MediaFileDocument, MediaFileEntity};
use crate::entity::tvshow::{TvShowDocument, TvShowEntity, TvShowRelationships};
use crate::entity::tvshow_episode::{
    TvShowEpisodeAttributes, TvShowEpisodeDocument, TvShowEpisodeInclude,
    TvShowEpisodeProgressAttributes, TvShowEpisodeProgressDocument, TvShowEpisodeProgressEntity,
    TvShowEpisodeRelation, TvShowEpisodeRelationships,
};
use crate::entity::tvshow_season::{
    TvShowSeasonDocument, TvShowSeasonEntity, TvShowSeasonRelationships,
};
use crate::entity::tvshow_subscription::TvShowSubscriptionEntity;
use crate::entity::{ApiResource, Couple, Entity, Relation};
use crate::server::handler::prelude::FromDomainResponse;

mod find;
mod list;
mod list_by_season;
mod progress;

pub fn create<S>() -> axum::Router<S>
where
    S: crate::server::prelude::ServerState + Clone,
{
    axum::Router::new()
        .route("/tvshow-episodes/{episode_id}", get(find::handle::<S>))
        .route("/tvshow-episodes", get(list::handle::<S>))
        .route(
            "/tvshow-seasons/{season_id}/episodes",
            get(list_by_season::handle::<S>),
        )
        .route(
            "/users/me/tvshow-episode-progresses",
            put(progress::upsert::handle::<S>),
        )
}

impl From<crate::entity::tvshow_episode::TvShowEpisodeField>
    for entertainarr_domain::tvshow::entity::TvShowEpisodeField
{
    fn from(value: crate::entity::tvshow_episode::TvShowEpisodeField) -> Self {
        match value {
            crate::entity::tvshow_episode::TvShowEpisodeField::AirDate => Self::AirDate,
            crate::entity::tvshow_episode::TvShowEpisodeField::SeasonNumber => Self::EpisodeNumber,
        }
    }
}

impl From<entertainarr_domain::tvshow::entity::TvShowEpisodeProgress>
    for TvShowEpisodeProgressDocument
{
    fn from(item: entertainarr_domain::tvshow::entity::TvShowEpisodeProgress) -> Self {
        Self {
            id: Couple(item.tvshow_episode_id, item.user_id),
            kind: Default::default(),
            attributes: TvShowEpisodeProgressAttributes {
                progress: item.progress,
                completed: item.completed,
                created_at: item.created_at,
                updated_at: item.updated_at,
            },
        }
    }
}

impl From<entertainarr_domain::tvshow::entity::TvShowEpisode> for TvShowEpisodeAttributes {
    fn from(episode: entertainarr_domain::tvshow::entity::TvShowEpisode) -> Self {
        Self {
            source: episode.source.into(),
            language: episode.language.into(),
            episode_number: episode.episode_number,
            air_date: episode.air_date,
            name: episode.name,
            overview: episode.overview,
            production_code: episode.production_code,
            still_url: episode.still_url,
            duration: episode.runtime.map(|value| (value * 60) as u64),
            vote_average: episode.vote_average,
            vote_count: episode.vote_count,
            created_at: episode.created_at,
            updated_at: episode.updated_at,
        }
    }
}

impl From<entertainarr_domain::tvshow::entity::TvShowEpisodeProgress>
    for TvShowEpisodeProgressAttributes
{
    fn from(value: entertainarr_domain::tvshow::entity::TvShowEpisodeProgress) -> Self {
        Self {
            progress: value.progress,
            completed: value.completed,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}

impl FromDomainResponse<FindTvShowEpisodeResponse, TvShowEpisodeInclude>
    for ApiResource<TvShowEpisodeDocument, TvShowEpisodeRelation>
{
    fn from_response(
        response: FindTvShowEpisodeResponse,
        required: HashSet<TvShowEpisodeInclude>,
    ) -> Self {
        let data = TvShowEpisodeDocument {
            id: response.episode.id,
            kind: Default::default(),
            relationships: TvShowEpisodeRelationships {
                tvshow: Relation {
                    data: Some(TvShowEntity::new(response.tvshow.id)),
                    meta: (),
                },
                season: Relation {
                    data: Some(TvShowSeasonEntity::new(response.season.id)),
                    meta: (),
                },
                progress: Relation {
                    data: response.progress.as_ref().map(|p| {
                        TvShowEpisodeProgressEntity::new(Couple(p.tvshow_episode_id, p.user_id))
                    }),
                    meta: (),
                },
                files: Relation {
                    data: Some(
                        response
                            .files
                            .iter()
                            .map(|item| MediaFileEntity::new(item.media_file.id))
                            .collect(),
                    ),
                    meta: (),
                },
            },
            attributes: response.episode.into(),
        };
        let mut includes = Vec::with_capacity(3 + response.files.len());
        if required.contains(&TvShowEpisodeInclude::TvShow) {
            includes.push(TvShowEpisodeRelation::TvShow(TvShowDocument {
                id: response.tvshow.id,
                kind: Default::default(),
                attributes: response.tvshow.into(),
                relationships: TvShowRelationships {
                    subscription: Relation {
                        data: response.subscription.as_ref().map(|sub| {
                            TvShowSubscriptionEntity::new(Couple(sub.tvshow_id, sub.user_id))
                        }),
                        meta: (),
                    },
                },
            }));
        }
        if required.contains(&TvShowEpisodeInclude::TvShowSeason) {
            includes.push(TvShowEpisodeRelation::TvShowSeason(TvShowSeasonDocument {
                id: response.season.id,
                kind: Default::default(),
                relationships: TvShowSeasonRelationships {
                    tvshow: Relation {
                        data: Some(TvShowEntity::new(response.season.tvshow_id)),
                        meta: (),
                    },
                },
                attributes: response.season.into(),
            }));
        }
        if required.contains(&TvShowEpisodeInclude::MediaFile) {
            includes.extend(response.files.into_iter().map(|item| {
                TvShowEpisodeRelation::MediaFile(MediaFileDocument::from(item.media_file))
            }));
        }
        if let Some(progress) = response.progress {
            includes.push(TvShowEpisodeRelation::TvShowEpisodeProgress(
                progress.into(),
            ));
        }
        ApiResource { data, includes }
    }
}

impl FromDomainResponse<ListTvShowEpisodeResponse, TvShowEpisodeInclude>
    for ApiResource<Vec<TvShowEpisodeDocument>, TvShowEpisodeRelation>
{
    fn from_response(
        response: ListTvShowEpisodeResponse,
        required: HashSet<TvShowEpisodeInclude>,
    ) -> Self {
        let data = response
            .episodes
            .into_iter()
            .map(|episode| {
                let season = response.seasons.get(&episode.tvshow_season_id);
                let tvshow = season.and_then(|s| response.tvshows.get(&s.tvshow_id));
                TvShowEpisodeDocument {
                    id: episode.id,
                    kind: Default::default(),
                    relationships: TvShowEpisodeRelationships {
                        tvshow: Relation {
                            data: tvshow.map(|t| TvShowEntity::new(t.id)),
                            meta: (),
                        },
                        season: Relation {
                            data: season.map(|s| TvShowSeasonEntity::new(s.id)),
                            meta: (),
                        },
                        progress: Relation {
                            data: response.progresses.get(&episode.id).map(|p| {
                                TvShowEpisodeProgressEntity::new(Couple(
                                    p.tvshow_episode_id,
                                    p.user_id,
                                ))
                            }),
                            meta: (),
                        },
                        files: Relation {
                            data: Some(
                                response
                                    .files
                                    .get(&episode.id)
                                    .iter()
                                    .flat_map(|file| file.iter())
                                    .map(|item| MediaFileEntity::new(item.media_file.id))
                                    .collect(),
                            ),
                            meta: (),
                        },
                    },
                    attributes: episode.into(),
                }
            })
            .collect();
        let mut includes = Vec::with_capacity(
            response.tvshows.len()
                + response.seasons.len()
                + response.progresses.len()
                + response.files.len(),
        );
        if required.contains(&TvShowEpisodeInclude::TvShow) {
            for (tvshow_id, tvshow) in response.tvshows {
                includes.push(TvShowEpisodeRelation::TvShow(TvShowDocument {
                    id: tvshow_id,
                    kind: Default::default(),
                    relationships: TvShowRelationships {
                        subscription: Relation {
                            data: response
                                .subscriptions
                                .get(&tvshow_id)
                                .map(|sub| Entity::new(Couple(sub.tvshow_id, sub.user_id))),
                            meta: (),
                        },
                    },
                    attributes: tvshow.into(),
                }));
            }
        }
        if required.contains(&TvShowEpisodeInclude::TvShowSeason) {
            for (season_id, season) in response.seasons {
                includes.push(TvShowEpisodeRelation::TvShowSeason(TvShowSeasonDocument {
                    id: season_id,
                    kind: Default::default(),
                    relationships: TvShowSeasonRelationships {
                        tvshow: Relation {
                            data: Some(TvShowEntity::new(season.tvshow_id)),
                            meta: (),
                        },
                    },
                    attributes: season.into(),
                }));
            }
        }
        if required.contains(&TvShowEpisodeInclude::TvShowEpisodeProgress) {
            for (_episode_id, progress) in response.progresses {
                includes.push(TvShowEpisodeRelation::TvShowEpisodeProgress(
                    TvShowEpisodeProgressDocument {
                        id: Couple(progress.tvshow_episode_id, progress.user_id),
                        kind: Default::default(),
                        attributes: progress.into(),
                    },
                ));
            }
        }
        if required.contains(&TvShowEpisodeInclude::MediaFile) {
            includes.extend(
                response
                    .files
                    .into_values()
                    .flatten()
                    .map(|file| TvShowEpisodeRelation::MediaFile(file.media_file.into())),
            );
        }

        ApiResource { data, includes }
    }
}