entertainarr-adapter-http 0.1.0

HTTP adapter for entertainarr
Documentation
use std::str::FromStr;

use axum::routing::{get, put};
use entertainarr_domain::podcast::entity::{PodcastEpisode, PodcastEpisodeProgress};

use crate::entity::podcast::PodcastEntity;
use crate::entity::podcast_episode::{
    PodcastEpisodeAttributes, PodcastEpisodeDocument, PodcastEpisodeField, PodcastEpisodeInclude,
    PodcastEpisodeProgressAttributes, PodcastEpisodeProgressDocument, PodcastEpisodeProgressEntity,
    PodcastEpisodeRelationships,
};
use crate::entity::{Couple, Relation};

pub mod delete_progression;
pub mod find;
pub mod list;
pub mod list_by_podcast;
pub mod upsert_progression;

pub fn create<S>() -> axum::Router<S>
where
    S: crate::server::prelude::ServerState + Clone,
{
    axum::Router::new()
        .route("/podcast-episodes", get(list::handle::<S>))
        .route(
            "/podcasts/{podcast_id}/episodes",
            get(list_by_podcast::handle::<S>),
        )
        .route("/podcast-episodes/{episode_id}", get(find::handle::<S>))
        .route(
            "/users/me/podcast-episode-progresses",
            put(upsert_progression::handle::<S>).delete(delete_progression::handle::<S>),
        )
}

impl From<PodcastEpisode> for PodcastEpisodeDocument {
    fn from(value: PodcastEpisode) -> Self {
        Self {
            id: value.id,
            kind: Default::default(),
            attributes: PodcastEpisodeAttributes {
                guid: value.guid,
                published_at: value.published_at,
                title: value.title,
                description: value.description,
                link: value.link,
                duration: value.duration.map(|v| v.as_secs()),
                file_url: value.file_url,
                file_size: value.file_size,
                file_type: value.file_type,
                created_at: value.created_at,
                updated_at: value.updated_at,
            },
            relationships: PodcastEpisodeRelationships {
                podcast: Relation {
                    data: Some(PodcastEntity {
                        id: value.podcast_id,
                        kind: Default::default(),
                    }),
                    meta: (),
                },
                progress: Relation {
                    data: None,
                    meta: (),
                },
            },
        }
    }
}

impl From<PodcastEpisode> for PodcastEpisodeAttributes {
    fn from(value: PodcastEpisode) -> Self {
        PodcastEpisodeAttributes {
            guid: value.guid,
            published_at: value.published_at,
            title: value.title,
            description: value.description,
            link: value.link,
            duration: value.duration.map(|v| v.as_secs()),
            file_url: value.file_url,
            file_size: value.file_size,
            file_type: value.file_type,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}

impl PodcastEpisodeDocument {
    pub fn maybe_with_progress(self, progress: Option<&PodcastEpisodeProgress>) -> Self {
        match progress {
            Some(value) => self.with_progress(value),
            None => self,
        }
    }

    pub fn with_progress(mut self, progress: &PodcastEpisodeProgress) -> Self {
        self.relationships.progress.data = Some(PodcastEpisodeProgressEntity {
            id: Couple(progress.podcast_episode_id, progress.user_id),
            kind: Default::default(),
        });
        self
    }
}

impl From<PodcastEpisodeField> for entertainarr_domain::podcast::entity::PodcastEpisodeField {
    fn from(value: PodcastEpisodeField) -> Self {
        match value {
            PodcastEpisodeField::PublishedAt => {
                entertainarr_domain::podcast::entity::PodcastEpisodeField::PublishedAt
            }
        }
    }
}

#[derive(Debug)]
pub struct ParsePodcastEpisodeIncludeError;

impl std::fmt::Display for ParsePodcastEpisodeIncludeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("invalid podcast episode include")
    }
}

impl FromStr for PodcastEpisodeInclude {
    type Err = ParsePodcastEpisodeIncludeError;

    fn from_str(input: &str) -> Result<Self, Self::Err> {
        match input {
            "podcast" => Ok(Self::Podcast),
            "podcast-episode-progress" => Ok(Self::PodcastEpisodeProgress),
            other => {
                tracing::warn!(value = other, "invalid podcast episode include");
                Err(ParsePodcastEpisodeIncludeError)
            }
        }
    }
}

impl From<PodcastEpisodeProgress> for PodcastEpisodeProgressDocument {
    fn from(value: PodcastEpisodeProgress) -> Self {
        PodcastEpisodeProgressDocument {
            id: Couple(value.podcast_episode_id, value.user_id),
            kind: Default::default(),
            attributes: value.into(),
        }
    }
}

impl From<PodcastEpisodeProgress> for PodcastEpisodeProgressAttributes {
    fn from(value: PodcastEpisodeProgress) -> Self {
        PodcastEpisodeProgressAttributes {
            progress: value.progress,
            completed: value.completed,
            created_at: value.created_at,
            updated_at: value.updated_at,
        }
    }
}