You've already forked flix
Throw away flix files in favor of a flix database
This commit is contained in:
+18
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flix-tmdb"
|
||||
version = "0.0.8"
|
||||
version = "0.0.9"
|
||||
|
||||
categories = []
|
||||
description = "Clients and models for fetching data from TMDB"
|
||||
@@ -11,13 +11,30 @@ edition.workspace = true
|
||||
license-file.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
sea-orm = ["dep:sea-orm"]
|
||||
|
||||
[dependencies]
|
||||
flix-model = { workspace = true }
|
||||
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
governor = { workspace = true, features = ["std", "jitter"] }
|
||||
nonzero_ext = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
url-macro = { workspace = true }
|
||||
|
||||
sea-orm = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_test = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
//! Collections API
|
||||
|
||||
use core::time::Duration;
|
||||
use std::rc::Rc;
|
||||
|
||||
use governor::Jitter;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Collection, CollectionId};
|
||||
use crate::model::Collection;
|
||||
use crate::model::id::CollectionId;
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
@@ -24,12 +30,20 @@ impl Client {
|
||||
id: impl Into<CollectionId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Collection, Error> {
|
||||
self.config
|
||||
.limiter
|
||||
.until_ready_with_jitter(Jitter::new(
|
||||
Duration::from_millis(0),
|
||||
Duration::from_millis(50),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/collection/{}", id.into()),
|
||||
&format!("/3/collection/{}", id.into().into_raw()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
//! Episodes API
|
||||
|
||||
use core::time::Duration;
|
||||
use std::rc::Rc;
|
||||
|
||||
use flix_model::numbers::{EpisodeNumber, SeasonNumber};
|
||||
|
||||
use governor::Jitter;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Episode, ShowId};
|
||||
use crate::model::Episode;
|
||||
use crate::model::id::ShowId;
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
@@ -22,10 +30,18 @@ impl Client {
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<ShowId>,
|
||||
season: impl Into<u32>,
|
||||
episode: impl Into<u32>,
|
||||
season: impl Into<SeasonNumber>,
|
||||
episode: impl Into<EpisodeNumber>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Episode, Error> {
|
||||
self.config
|
||||
.limiter
|
||||
.until_ready_with_jitter(Jitter::new(
|
||||
Duration::from_millis(0),
|
||||
Duration::from_millis(50),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
@@ -33,7 +49,7 @@ impl Client {
|
||||
&self.config,
|
||||
&format!(
|
||||
"/3/tv/{}/season/{}/episode/{}",
|
||||
id.into(),
|
||||
id.into().into_raw(),
|
||||
season.into(),
|
||||
episode.into()
|
||||
),
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{MovieGenre, ShowGenre};
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
/// TMDB Genre API client
|
||||
pub struct Client {
|
||||
config: Rc<Config>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with the given configuration
|
||||
pub fn new(config: Rc<Config>) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Fetch the list of all valid movie genres
|
||||
pub async fn get_movie_genres(&self, language: Option<&str>) -> Result<Vec<MovieGenre>, Error> {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Genres {
|
||||
genres: Vec<MovieGenre>,
|
||||
}
|
||||
|
||||
let genres: Genres = self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(&self.config, "/3/genre/movie/list", language)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(genres.genres)
|
||||
}
|
||||
|
||||
/// Fetch the list of all valid show genres
|
||||
pub async fn get_tv_genres(&self, language: Option<&str>) -> Result<Vec<ShowGenre>, Error> {
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct Genres {
|
||||
genres: Vec<ShowGenre>,
|
||||
}
|
||||
|
||||
let genres: Genres = self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(&self.config, "/3/genre/tv/list", language)?)
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(genres.genres)
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
//! TMDB API clients
|
||||
|
||||
use reqwest::Request;
|
||||
use reqwest::header;
|
||||
|
||||
use crate::Config;
|
||||
|
||||
/// Collections API
|
||||
pub mod collections;
|
||||
/// Episodes API
|
||||
pub mod episodes;
|
||||
/// Genres API
|
||||
pub mod genres;
|
||||
/// Movies API
|
||||
pub mod movies;
|
||||
/// Seasons API
|
||||
pub mod seasons;
|
||||
/// Shows API
|
||||
pub mod shows;
|
||||
|
||||
/// A generic error wrapping Url and Reqwest errors
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
//! Movies API
|
||||
|
||||
use core::time::Duration;
|
||||
use std::rc::Rc;
|
||||
|
||||
use governor::Jitter;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Movie, MovieId};
|
||||
use crate::model::Movie;
|
||||
use crate::model::id::MovieId;
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
@@ -24,12 +30,20 @@ impl Client {
|
||||
id: impl Into<MovieId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Movie, Error> {
|
||||
self.config
|
||||
.limiter
|
||||
.until_ready_with_jitter(Jitter::new(
|
||||
Duration::from_millis(0),
|
||||
Duration::from_millis(50),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/movie/{}", id.into()),
|
||||
&format!("/3/movie/{}", id.into().into_raw()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
//! Seasons API
|
||||
|
||||
use core::time::Duration;
|
||||
use std::rc::Rc;
|
||||
|
||||
use flix_model::numbers::SeasonNumber;
|
||||
|
||||
use governor::Jitter;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Season, ShowId};
|
||||
use crate::model::Season;
|
||||
use crate::model::id::ShowId;
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
@@ -22,15 +30,23 @@ impl Client {
|
||||
pub async fn get_details(
|
||||
&self,
|
||||
id: impl Into<ShowId>,
|
||||
season: impl Into<u32>,
|
||||
season: impl Into<SeasonNumber>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Season, Error> {
|
||||
self.config
|
||||
.limiter
|
||||
.until_ready_with_jitter(Jitter::new(
|
||||
Duration::from_millis(0),
|
||||
Duration::from_millis(50),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/tv/{}/season/{}", id.into(), season.into()),
|
||||
&format!("/3/tv/{}/season/{}", id.into().into_raw(), season.into()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
//! Shows API
|
||||
|
||||
use core::time::Duration;
|
||||
use std::rc::Rc;
|
||||
|
||||
use governor::Jitter;
|
||||
|
||||
use crate::Config;
|
||||
use crate::model::{Show, ShowId};
|
||||
use crate::model::Show;
|
||||
use crate::model::id::ShowId;
|
||||
|
||||
use super::{Error, make_request};
|
||||
|
||||
@@ -24,12 +30,20 @@ impl Client {
|
||||
id: impl Into<ShowId>,
|
||||
language: Option<&str>,
|
||||
) -> Result<Show, Error> {
|
||||
self.config
|
||||
.limiter
|
||||
.until_ready_with_jitter(Jitter::new(
|
||||
Duration::from_millis(0),
|
||||
Duration::from_millis(50),
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.client
|
||||
.execute(make_request(
|
||||
&self.config,
|
||||
&format!("/3/tv/{}", id.into()),
|
||||
&format!("/3/tv/{}", id.into().into_raw()),
|
||||
language,
|
||||
)?)
|
||||
.await?
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::{Config, api};
|
||||
|
||||
/// The primary client that references all other clients
|
||||
pub struct Client {
|
||||
genres: api::genres::Client,
|
||||
collections: api::collections::Client,
|
||||
movies: api::movies::Client,
|
||||
shows: api::shows::Client,
|
||||
@@ -22,7 +21,6 @@ impl Client {
|
||||
pub fn new_with_config(config: Config) -> Self {
|
||||
let config = Rc::new(config);
|
||||
Self {
|
||||
genres: api::genres::Client::new(config.clone()),
|
||||
collections: api::collections::Client::new(config.clone()),
|
||||
movies: api::movies::Client::new(config.clone()),
|
||||
shows: api::shows::Client::new(config.clone()),
|
||||
@@ -33,11 +31,6 @@ impl Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Access the Genres API
|
||||
pub fn genres(&self) -> &api::genres::Client {
|
||||
&self.genres
|
||||
}
|
||||
|
||||
/// Access the Collections API
|
||||
pub fn collections(&self) -> &api::collections::Client {
|
||||
&self.collections
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use governor::clock::MonotonicClock;
|
||||
use governor::state::{InMemoryState, NotKeyed};
|
||||
use governor::{Quota, RateLimiter};
|
||||
use nonzero_ext::nonzero;
|
||||
use url::Url;
|
||||
use url_macro::url;
|
||||
|
||||
@@ -7,6 +11,8 @@ pub struct Config {
|
||||
pub base_url: Url,
|
||||
/// The reqwest client that is used for every request
|
||||
pub client: reqwest::Client,
|
||||
/// The rate limiter to use for the client
|
||||
pub limiter: RateLimiter<NotKeyed, InMemoryState, MonotonicClock>,
|
||||
/// The bearer token for readonly access to the API
|
||||
pub bearer_token: String,
|
||||
/// An optional user agent string to provide to the API
|
||||
@@ -19,6 +25,7 @@ impl Config {
|
||||
Self {
|
||||
base_url: url!("https://api.themoviedb.org"),
|
||||
client: reqwest::Client::new(),
|
||||
limiter: RateLimiter::direct(Quota::per_second(nonzero!(30u32))),
|
||||
bearer_token,
|
||||
user_agent: None,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! flix-tmdb provides clients and models for fetching data from TMDB
|
||||
|
||||
/// TMDB API clients
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
pub mod api;
|
||||
/// Deserializable types from the TMDB API
|
||||
pub mod model;
|
||||
|
||||
mod client;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{CollectionId, MovieId};
|
||||
use super::id::{CollectionId, MovieId};
|
||||
|
||||
/// A deserialized Collection from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
@@ -20,4 +20,6 @@ pub struct Collection {
|
||||
pub struct Item {
|
||||
/// The movie's TMDB ID
|
||||
pub id: MovieId,
|
||||
/// The movie's title
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
use core::time::Duration;
|
||||
|
||||
use flix_model::numbers::EpisodeNumber;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::duration_from_minutes;
|
||||
|
||||
/// A deserialized Episode from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct Episode {
|
||||
/// The episode's number
|
||||
pub episode_number: u32,
|
||||
pub episode_number: EpisodeNumber,
|
||||
/// The episode's title
|
||||
#[serde(rename = "name")]
|
||||
pub title: String,
|
||||
@@ -12,4 +18,7 @@ pub struct Episode {
|
||||
pub overview: String,
|
||||
/// The episode's air date
|
||||
pub air_date: NaiveDate,
|
||||
/// The movie's runtime
|
||||
#[serde(deserialize_with = "duration_from_minutes")]
|
||||
pub runtime: Duration,
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
use super::id::{MovieGenreId, ShowGenreId};
|
||||
|
||||
/// A deserialized movie Genre from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct MovieGenre {
|
||||
/// The genre's TMDB ID
|
||||
pub id: MovieGenreId,
|
||||
/// The genre's name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// A deserialized show Genre from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct ShowGenre {
|
||||
/// The genre's TMDB ID
|
||||
pub id: ShowGenreId,
|
||||
/// The genre's name
|
||||
pub name: String,
|
||||
}
|
||||
+215
-63
@@ -1,90 +1,242 @@
|
||||
//! Typed TMDB IDs
|
||||
|
||||
use core::cmp::Ordering;
|
||||
use core::fmt;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// The TMDB ID type of a movie genre
|
||||
pub type MovieGenreId = TmdbId<MovieGenre>;
|
||||
/// The TMDB ID type of a show genre
|
||||
pub type ShowGenreId = TmdbId<ShowGenre>;
|
||||
/// The TMDB ID type of a collection
|
||||
pub type CollectionId = TmdbId<Collection>;
|
||||
/// The TMDB ID type of a movie
|
||||
pub type MovieId = TmdbId<Movie>;
|
||||
/// The TMDB ID type of a show
|
||||
pub type ShowId = TmdbId<Show>;
|
||||
#[cfg(feature = "sea-orm")]
|
||||
use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
|
||||
#[cfg(feature = "sea-orm")]
|
||||
use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
|
||||
|
||||
pub enum MovieGenre {}
|
||||
pub enum ShowGenre {}
|
||||
pub enum Collection {}
|
||||
pub enum Movie {}
|
||||
pub enum Show {}
|
||||
/// The internal representation used by TMDB
|
||||
pub type TmdbRepr = u32;
|
||||
|
||||
/// The inner type of TmdbId
|
||||
pub type Inner = u32;
|
||||
|
||||
/// Wraps an ID from TMDB, the generic parameter is to enforce that
|
||||
/// IDs for different types of media are not interchangeable
|
||||
/// An opaque type representing a TMDB ID
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(transparent)]
|
||||
#[repr(transparent)]
|
||||
pub struct TmdbId<T> {
|
||||
inner: Inner,
|
||||
pub struct Id<T> {
|
||||
id: TmdbRepr,
|
||||
#[serde(skip_serializing, default)]
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TmdbId<T> {
|
||||
/// Extract the inner value
|
||||
pub fn inner(self) -> Inner {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Inner> for TmdbId<T> {
|
||||
fn from(value: Inner) -> Self {
|
||||
Self {
|
||||
inner: value,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<TmdbId<T>> for Inner {
|
||||
fn from(value: TmdbId<T>) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for TmdbId<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Display for TmdbId<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for TmdbId<T> {
|
||||
// Manual implementation since `T: Clone` is not required
|
||||
impl<T> Clone for Id<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for TmdbId<T> {}
|
||||
// Manual implementation since `T: Copy` is not required
|
||||
impl<T> Copy for Id<T> {}
|
||||
|
||||
impl<T> PartialEq for TmdbId<T> {
|
||||
// Manual implementation since `T: PartialEq` is not required
|
||||
impl<T> PartialEq for Id<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for TmdbId<T> {}
|
||||
// Manual implementation since `T: Eq` is not required
|
||||
impl<T> Eq for Id<T> {}
|
||||
|
||||
impl<T> Hash for TmdbId<T> {
|
||||
// Manual implementation since `T: PartialOrd` is not required
|
||||
impl<T> PartialOrd for Id<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
// Manual implementation since `T: Ord` is not required
|
||||
impl<T> Ord for Id<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.id.cmp(&other.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Manual implementation since `T: Hash` is not required
|
||||
impl<T> Hash for Id<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.inner.hash(state);
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Id<T> {
|
||||
/// Allows the conversion from a raw value to [Id], though the use is discouraged.
|
||||
pub fn from_raw(raw: TmdbRepr) -> Self {
|
||||
Self {
|
||||
id: raw,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows extracting the raw value, though the use is discouraged.
|
||||
pub fn into_raw(self) -> TmdbRepr {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Id<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Id")
|
||||
.field("T", &core::any::type_name::<T>())
|
||||
.field("id", &self.id)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea-orm")]
|
||||
impl<T> ValueType for Id<T> {
|
||||
fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
|
||||
<TmdbRepr as ValueType>::try_from(v).map(|id| Self {
|
||||
id,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn type_name() -> String {
|
||||
format!("Id<{}>", &core::any::type_name::<T>())
|
||||
}
|
||||
|
||||
fn array_type() -> ArrayType {
|
||||
TmdbRepr::array_type()
|
||||
}
|
||||
|
||||
fn column_type() -> ColumnType {
|
||||
TmdbRepr::column_type()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea-orm")]
|
||||
impl<T> From<Id<T>> for Value {
|
||||
fn from(value: Id<T>) -> Self {
|
||||
value.id.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea-orm")]
|
||||
impl<T> TryGetable for Id<T> {
|
||||
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
|
||||
TmdbRepr::try_get_by(res, index).map(|id| Self {
|
||||
id,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea-orm")]
|
||||
impl<T> TryFromU64 for Id<T> {
|
||||
fn try_from_u64(n: u64) -> Result<Self, DbErr> {
|
||||
TmdbRepr::try_from_u64(n).map(|id| Self {
|
||||
id,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sea-orm")]
|
||||
impl<T> Nullable for Id<T> {
|
||||
fn null() -> Value {
|
||||
TmdbRepr::null()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(feature = "sea-orm")]
|
||||
fn test_sea_orm() {
|
||||
use sea_orm::{
|
||||
ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EnumIter,
|
||||
PrimaryKeyTrait,
|
||||
};
|
||||
|
||||
use super::Id;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "ids")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
id: Id<Model>,
|
||||
nullable: Option<Id<Model>>,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
use super::Id;
|
||||
|
||||
let id: Id<()> = Id::from_raw(1234);
|
||||
serde_test::assert_tokens(&id, &[serde_test::Token::U32(1234)]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for the raw ID representation
|
||||
pub use self::TmdbRepr as RawId;
|
||||
|
||||
/// A placeholder type used for CollectionId
|
||||
pub enum Collection {}
|
||||
/// Type alias for a collection ID
|
||||
pub type CollectionId = Id<Collection>;
|
||||
|
||||
impl From<CollectionId> for flix_model::id::CollectionId {
|
||||
fn from(value: CollectionId) -> Self {
|
||||
Self::from_raw(value.into_raw().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<flix_model::id::CollectionId> for CollectionId {
|
||||
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
|
||||
|
||||
fn try_from(value: flix_model::id::CollectionId) -> Result<Self, Self::Error> {
|
||||
value.into_raw().try_into().map(Self::from_raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder type used for MovieId
|
||||
pub enum Movie {}
|
||||
/// Type alias for a movie ID
|
||||
pub type MovieId = Id<Movie>;
|
||||
|
||||
impl From<MovieId> for flix_model::id::MovieId {
|
||||
fn from(value: MovieId) -> Self {
|
||||
Self::from_raw(value.into_raw().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<flix_model::id::MovieId> for MovieId {
|
||||
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
|
||||
|
||||
fn try_from(value: flix_model::id::MovieId) -> Result<Self, Self::Error> {
|
||||
value.into_raw().try_into().map(Self::from_raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder type used for ShowId
|
||||
pub enum Show {}
|
||||
/// Type alias for a show ID
|
||||
pub type ShowId = Id<Show>;
|
||||
|
||||
impl From<ShowId> for flix_model::id::ShowId {
|
||||
fn from(value: ShowId) -> Self {
|
||||
Self::from_raw(value.into_raw().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<flix_model::id::ShowId> for ShowId {
|
||||
type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
|
||||
|
||||
fn try_from(value: flix_model::id::ShowId) -> Result<Self, Self::Error> {
|
||||
value.into_raw().try_into().map(Self::from_raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
//! Deserializable types from the TMDB API
|
||||
|
||||
use core::time::Duration;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
pub mod id;
|
||||
|
||||
mod collection;
|
||||
mod episode;
|
||||
mod genre;
|
||||
mod id;
|
||||
mod movie;
|
||||
mod season;
|
||||
mod show;
|
||||
|
||||
pub use collection::*;
|
||||
pub use episode::*;
|
||||
pub use genre::*;
|
||||
pub use movie::*;
|
||||
pub use season::*;
|
||||
pub use serde::*;
|
||||
pub use show::*;
|
||||
|
||||
pub use id::{CollectionId, MovieGenreId, MovieId, ShowGenreId, ShowId};
|
||||
pub use id::{Inner as TmdbIdInner, TmdbId};
|
||||
fn duration_from_minutes<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let minutes = u64::deserialize(deserializer)?;
|
||||
Ok(Duration::from_secs(minutes.saturating_mul(60)))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use core::time::Duration;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::{CollectionId, MovieGenre, MovieId};
|
||||
use super::duration_from_minutes;
|
||||
use super::id::{CollectionId, MovieId};
|
||||
|
||||
/// A deserialized Movie from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
@@ -12,14 +15,15 @@ pub struct Movie {
|
||||
pub collection: Option<InCollection>,
|
||||
/// The movie's title
|
||||
pub title: String,
|
||||
/// The movie's tagline
|
||||
pub tagline: String,
|
||||
/// The movie's overview
|
||||
pub overview: String,
|
||||
/// The list of genres the movie belongs to
|
||||
pub genres: Vec<MovieGenre>,
|
||||
/// The movie's release date
|
||||
pub release_date: NaiveDate,
|
||||
/// The movie's status
|
||||
pub status: MovieStatus,
|
||||
/// The movie's runtime
|
||||
#[serde(deserialize_with = "duration_from_minutes")]
|
||||
pub runtime: Duration,
|
||||
}
|
||||
|
||||
/// A deserialized movie's collection from the TMDB API
|
||||
@@ -27,27 +31,15 @@ pub struct Movie {
|
||||
pub struct InCollection {
|
||||
/// The collection's TMDB ID
|
||||
pub id: CollectionId,
|
||||
/// The collection's title
|
||||
#[serde(rename = "name")]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// A deserialized movie status from the TMDB API
|
||||
#[derive(Debug, Clone, Copy, serde::Deserialize)]
|
||||
pub enum MovieStatus {
|
||||
/// The movie was cancelled
|
||||
#[serde(rename = "Canceled")]
|
||||
Canceled,
|
||||
/// The movie is in production
|
||||
#[serde(rename = "In Production")]
|
||||
InProduction,
|
||||
/// The movie is planned
|
||||
#[serde(rename = "Planned")]
|
||||
Planned,
|
||||
/// The movie is in post production
|
||||
#[serde(rename = "Post Production")]
|
||||
PostProduction,
|
||||
/// The movie is released
|
||||
#[serde(rename = "Released")]
|
||||
Released,
|
||||
/// The movie is rumored
|
||||
#[serde(rename = "Rumored")]
|
||||
Rumored,
|
||||
}
|
||||
// TODO: Genres
|
||||
// pub genres: Vec<Genre>,
|
||||
// where: struct Genre { id, name }
|
||||
|
||||
// TODO: Company
|
||||
// pub companies: Vec<Company>
|
||||
// where: struct Company { id, name }
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::Episode;
|
||||
use flix_model::numbers::SeasonNumber;
|
||||
|
||||
/// A deserialized Season from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct Season {
|
||||
/// The season's number
|
||||
pub season_number: u32,
|
||||
pub season_number: SeasonNumber,
|
||||
/// The season's title
|
||||
#[serde(rename = "name")]
|
||||
pub title: String,
|
||||
@@ -14,6 +14,10 @@ pub struct Season {
|
||||
pub overview: String,
|
||||
/// The season's air date
|
||||
pub air_date: NaiveDate,
|
||||
/// The list of episodes in this season
|
||||
pub episodes: Vec<Episode>,
|
||||
/// The number of episodes in this season
|
||||
pub episodes: Vec<FakeEpisode>,
|
||||
}
|
||||
|
||||
/// A placeholder struct for parsing the episodes list for a season
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
pub struct FakeEpisode {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::NaiveDate;
|
||||
|
||||
use super::{ShowGenre, ShowId};
|
||||
use super::id::ShowId;
|
||||
|
||||
/// A deserialized Show from the TMDB API
|
||||
#[derive(Debug, Clone, serde::Deserialize)]
|
||||
@@ -10,39 +10,28 @@ pub struct Show {
|
||||
/// The show's title
|
||||
#[serde(rename = "name")]
|
||||
pub title: String,
|
||||
/// The show's tagline
|
||||
pub tagline: String,
|
||||
/// The show's overview
|
||||
pub overview: String,
|
||||
/// The list of genres this show belongs to
|
||||
pub genres: Vec<ShowGenre>,
|
||||
/// The show's first air date
|
||||
pub first_air_date: NaiveDate,
|
||||
/// The show's last air date
|
||||
pub last_air_date: NaiveDate,
|
||||
/// The total number of episodes in this show
|
||||
pub number_of_episodes: u32,
|
||||
/// The number of seasons in this show
|
||||
pub number_of_seasons: u32,
|
||||
/// The show's status
|
||||
pub status: ShowStatus,
|
||||
}
|
||||
|
||||
/// A deserialized show Status from the TMDB API
|
||||
#[derive(Debug, Clone, Copy, serde::Deserialize)]
|
||||
pub enum ShowStatus {
|
||||
/// The show is returning
|
||||
#[serde(rename = "Returning Series")]
|
||||
Returning,
|
||||
/// The show is planned
|
||||
#[serde(rename = "Planned")]
|
||||
Planned,
|
||||
/// The show is in procuction
|
||||
#[serde(rename = "In Production")]
|
||||
InProduction,
|
||||
/// The show has ended
|
||||
#[serde(rename = "Ended")]
|
||||
Ended,
|
||||
/// The show is canceled
|
||||
#[serde(rename = "Canceled")]
|
||||
Canceled,
|
||||
/// The show only released a pilot
|
||||
#[serde(rename = "Pilot")]
|
||||
Pilot,
|
||||
}
|
||||
// TODO: Genres
|
||||
// pub genres: Vec<Genre>,
|
||||
// where: struct Genre { id, name }
|
||||
|
||||
// TODO: Network
|
||||
// pub networks: Vec<Network>
|
||||
// where: struct Network { id, name }
|
||||
|
||||
// TODO: Company
|
||||
// pub companies: Vec<Company>
|
||||
// where: struct Company { id, name }
|
||||
|
||||
Reference in New Issue
Block a user