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 }
}
}