Update dependencies and lints

This commit is contained in:
2026-09-07 22:31:04 -07:00
parent b518d762f1
commit 9c288d051c
79 changed files with 3473 additions and 2923 deletions
+8 -6
View File
@@ -1,14 +1,16 @@
[package]
name = "flix-db"
version = "0.0.20"
license-file.workspace = true
version = "0.1.0"
description = "Types for storing persistent data about media"
repository = "https://github.com/QuantumShade/flix"
categories = []
repository = "https://git.skrundz.dev/quantumshade/flix"
keywords = ["flix"]
categories = ["multimedia"]
include = ["/src"]
publish = true
edition.workspace = true
rust-version.workspace = true
license-file.workspace = true
[package.metadata.docs.rs]
all-features = true
@@ -29,7 +31,7 @@ flix-tmdb = { workspace = true, features = ["sea-orm"], optional = true }
[dev-dependencies]
sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls"] }
tokio = { version = "^1", default-features = false, features = [
tokio = { workspace = true, features = [
"macros",
"rt",
] }
+15 -3
View File
@@ -1,15 +1,26 @@
//! Types and functions related to [DatabaseConnection]s
//! Types and functions related to [`DatabaseConnection`]s.
use sea_orm::{DatabaseConnection, DbErr};
use sea_orm_migration::MigratorTrait as _;
/// A newtype wrapping a [DatabaseConnection]
/// A newtype wrapping a [`DatabaseConnection`].
#[derive(Debug)]
pub struct Connection(DatabaseConnection);
impl Connection {
/// Helper function for applying database migrations while wrapping a
/// [DatabaseConnection] in a newtype
/// [`DatabaseConnection`] in a newtype.
///
/// # Errors
/// Fails if connecting or applying migrations fail.
#[inline]
pub async fn try_from(db: DatabaseConnection) -> Result<Self, DbErr> {
// The migrations only create views which store no data, so it is
// important to down before up since modifications are impossible.
//
// Syncing the schema registry allows all real tables to be upgraded.
// It is important to sync twice to ensure internal consistency so that
// the views can be recreated on the latest schema.
crate::migration::Migrator::down(&db, None).await?;
db.get_schema_registry("flix_db::*").sync(&db).await?;
db.get_schema_registry("flix_db::*").sync(&db).await?;
@@ -19,6 +30,7 @@ impl Connection {
}
impl AsRef<DatabaseConnection> for Connection {
#[inline]
fn as_ref(&self) -> &DatabaseConnection {
&self.0
}
+183 -145
View File
@@ -1,6 +1,6 @@
//! This module contains entities for storing media file information
//! This module contains entities for storing media file information.
/// Library entity
/// Library entity.
pub mod libraries {
use flix_model::id::LibraryId;
@@ -10,42 +10,43 @@ pub mod libraries {
use chrono::{DateTime, Utc};
use sea_orm::entity::prelude::*;
/// The database representation of a library media folder
/// The database representation of a library media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_libraries")]
pub struct Model {
/// The library's ID
/// The library's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: LibraryId,
/// The library's directory
/// The library's directory.
pub directory: PathBytes,
/// The library's last scan data
/// The library's last scan data.
pub last_scan_date: Option<DateTime<Utc>>,
/// The library's last scan duration
/// The library's last scan duration.
pub last_scan_duration: Option<Seconds>,
/// Collections that are part of this library
/// Collections that are part of this library.
#[sea_orm(has_many)]
pub collections: HasMany<super::collections::Entity>,
/// Movies that are part of this library
/// Movies that are part of this library.
#[sea_orm(has_many)]
pub movies: HasMany<super::movies::Entity>,
/// Shows that are part of this library
/// Shows that are part of this library.
#[sea_orm(has_many)]
pub shows: HasMany<super::shows::Entity>,
/// Seasons that are part of this library
/// Seasons that are part of this library.
#[sea_orm(has_many)]
pub seasons: HasMany<super::seasons::Entity>,
/// Episodes that are part of this library
/// Episodes that are part of this library.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Collection entity
/// Collection entity.
pub mod collections {
use flix_model::id::{CollectionId, LibraryId};
@@ -55,25 +56,25 @@ pub mod collections {
use crate::entity;
/// The database representation of a collection media folder
/// The database representation of a collection media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_collections")]
pub struct Model {
/// The collection's ID
/// The collection's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: CollectionId,
/// The collection's parent
/// The collection's parent.
#[sea_orm(indexed)]
pub parent_id: Option<CollectionId>,
/// The collection's library ID
/// The collection's library ID.
pub library_id: LibraryId,
/// The collection's directory
/// The collection's directory.
pub directory: PathBytes,
/// The collection's poster path
/// The collection's poster path.
pub relative_poster_path: Option<String>,
/// This collection's parent
/// This collection's parent.
#[sea_orm(
self_ref,
relation_enum = "Parent",
@@ -83,7 +84,7 @@ pub mod collections {
on_delete = "Cascade"
)]
pub parent: HasOne<Entity>,
/// The library this collection belongs to
/// The library this collection belongs to.
#[sea_orm(
belongs_to,
from = "library_id",
@@ -92,7 +93,7 @@ pub mod collections {
on_delete = "Cascade"
)]
pub library: HasOne<super::libraries::Entity>,
/// The info for this collection
/// The info for this collection.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -103,15 +104,16 @@ pub mod collections {
)]
pub info: HasOne<entity::info::collections::Entity>,
/// The watched info for this collection
/// The watched info for this collection.
#[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
pub watched: HasMany<entity::watched::collections::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Movie entity
/// Movie entity.
pub mod movies {
use flix_model::id::{CollectionId, LibraryId, MovieId};
@@ -121,27 +123,27 @@ pub mod movies {
use crate::entity;
/// The database representation of a movie media folder
/// The database representation of a movie media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_movies")]
pub struct Model {
/// The movie's ID
/// The movie's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: MovieId,
/// The movie's parent
/// The movie's parent.
#[sea_orm(indexed)]
pub parent_id: Option<CollectionId>,
/// The movie's library
/// The movie's library.
pub library_id: LibraryId,
/// The movie's directory
/// The movie's directory.
pub directory: PathBytes,
/// The movie's media path
/// The movie's media path.
pub relative_media_path: String,
/// The movie's poster path
/// The movie's poster path.
pub relative_poster_path: Option<String>,
/// This movie's parent
/// This movie's parent.
#[sea_orm(
belongs_to,
from = "parent_id",
@@ -150,7 +152,7 @@ pub mod movies {
on_delete = "Cascade"
)]
pub parent: HasOne<super::collections::Entity>,
/// The library this movie belongs to
/// The library this movie belongs to.
#[sea_orm(
belongs_to,
from = "library_id",
@@ -159,7 +161,7 @@ pub mod movies {
on_delete = "Cascade"
)]
pub library: HasOne<super::libraries::Entity>,
/// The info for this movie
/// The info for this movie.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -170,15 +172,16 @@ pub mod movies {
)]
pub info: HasOne<entity::info::movies::Entity>,
/// The watched info for this movie
/// The watched info for this movie.
#[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
pub watched: HasMany<entity::watched::movies::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Show entity
/// Show entity.
pub mod shows {
use flix_model::id::{CollectionId, LibraryId, ShowId};
@@ -188,25 +191,25 @@ pub mod shows {
use crate::entity;
/// The database representation of a show media folder
/// The database representation of a show media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_shows")]
pub struct Model {
/// The show's ID
/// The show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: ShowId,
/// The show's parent
/// The show's parent.
#[sea_orm(indexed)]
pub parent_id: Option<CollectionId>,
/// The show's library
/// The show's library.
pub library_id: LibraryId,
/// The show's directory
/// The show's directory.
pub directory: PathBytes,
/// The show's poster path
/// The show's poster path.
pub relative_poster_path: Option<String>,
/// This show's parent
/// This show's parent.
#[sea_orm(
belongs_to,
from = "parent_id",
@@ -215,7 +218,7 @@ pub mod shows {
on_delete = "Cascade"
)]
pub parent: HasOne<super::collections::Entity>,
/// The library this show belongs to
/// The library this show belongs to.
#[sea_orm(
belongs_to,
from = "library_id",
@@ -224,7 +227,7 @@ pub mod shows {
on_delete = "Cascade"
)]
pub library: HasOne<super::libraries::Entity>,
/// The info for this show
/// The info for this show.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -235,21 +238,22 @@ pub mod shows {
)]
pub info: HasOne<entity::info::shows::Entity>,
/// Seasons that are part of this show
/// Seasons that are part of this show.
#[sea_orm(has_many)]
pub seasons: HasMany<super::seasons::Entity>,
/// Episodes that are part of this show
/// Episodes that are part of this show.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
/// The watched info for this show
/// The watched info for this show.
#[sea_orm(has_many, relation_enum = "Watched", from = "id", to = "id")]
pub watched: HasMany<entity::watched::shows::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Season entity
/// Season entity.
pub mod seasons {
use flix_model::id::{LibraryId, ShowId};
use flix_model::numbers::SeasonNumber;
@@ -260,25 +264,25 @@ pub mod seasons {
use crate::entity;
/// The database representation of a season media folder
/// The database representation of a season media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_seasons")]
pub struct Model {
/// The season's show's ID
/// The season's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The season's number
/// The season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The season's library
/// The season's library.
pub library_id: LibraryId,
/// The season's directory
/// The season's directory.
pub directory: PathBytes,
/// The season's poster path
/// The season's poster path.
pub relative_poster_path: Option<String>,
/// This season's show
/// This season's show.
#[sea_orm(
belongs_to,
from = "show_id",
@@ -287,7 +291,7 @@ pub mod seasons {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// The library this season belongs to
/// The library this season belongs to.
#[sea_orm(
belongs_to,
from = "library_id",
@@ -296,7 +300,7 @@ pub mod seasons {
on_delete = "Cascade"
)]
pub library: HasOne<super::libraries::Entity>,
/// The info for this season
/// The info for this season.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -307,10 +311,10 @@ pub mod seasons {
)]
pub info: HasOne<entity::info::seasons::Entity>,
/// Episodes that are part of this show
/// Episodes that are part of this show.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
/// The watched info for this season
/// The watched info for this season.
#[sea_orm(
has_many,
relation_enum = "Watched",
@@ -320,10 +324,11 @@ pub mod seasons {
pub watched: HasMany<entity::watched::seasons::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Episode entity
/// Episode entity.
pub mod episodes {
use flix_model::id::{LibraryId, ShowId};
use flix_model::numbers::{EpisodeNumber, SeasonNumber};
@@ -334,32 +339,32 @@ pub mod episodes {
use crate::entity;
/// The database representation of a episode media folder
/// The database representation of a episode media folder.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_episodes")]
pub struct Model {
/// The episode's show's ID
/// The episode's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The episode's season's number
/// The episode's season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The episode's number
/// The episode's number.
#[sea_orm(primary_key, auto_increment = false)]
pub episode_number: EpisodeNumber,
/// The number of additional contained episodes
/// The number of additional contained episodes.
pub count: u8,
/// The episode's library
/// The episode's library.
pub library_id: LibraryId,
/// The episode's directory
/// The episode's directory.
pub directory: PathBytes,
/// The episode's media path
/// The episode's media path.
pub relative_media_path: String,
/// The episode's poster path
/// The episode's poster path.
pub relative_poster_path: Option<String>,
/// This episode's show
/// This episode's show.
#[sea_orm(
belongs_to,
from = "show_id",
@@ -368,7 +373,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// This episode's season
/// This episode's season.
#[sea_orm(
belongs_to,
from = "(show_id, season_number)",
@@ -377,7 +382,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub season: HasOne<super::seasons::Entity>,
/// The library this episode belongs to
/// The library this episode belongs to.
#[sea_orm(
belongs_to,
from = "library_id",
@@ -386,7 +391,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub library: HasOne<super::libraries::Entity>,
/// The info for this episode
/// The info for this episode.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -397,7 +402,7 @@ pub mod episodes {
)]
pub info: HasOne<entity::info::episodes::Entity>,
/// The watched info for this episode
/// The watched info for this episode.
#[sea_orm(
has_many,
relation_enum = "Watched",
@@ -407,23 +412,26 @@ pub mod episodes {
pub watched: HasMany<entity::watched::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Macros for creating content entities
/// Macros for creating content entities.
#[cfg(test)]
pub mod test {
macro_rules! make_content_library {
($db:expr, $id:expr) => {
$crate::entity::content::libraries::ActiveModel {
id: Set(::flix_model::id::LibraryId::from_raw($id)),
directory: Set(::std::path::PathBuf::new().into()),
last_scan_date: Set(None),
last_scan_duration: Set(None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::libraries::ActiveModel {
id: Set(::flix_model::id::LibraryId::from_raw($id)),
directory: Set(::std::path::PathBuf::new().into()),
last_scan_date: Set(None),
last_scan_duration: Set(None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_library;
@@ -431,16 +439,18 @@ pub mod test {
macro_rules! make_content_collection {
($db:expr, $lid:expr, $id:expr, $pid:expr) => {
$crate::entity::info::test::make_info_collection!($db, $id);
$crate::entity::content::collections::ActiveModel {
id: Set(::flix_model::id::CollectionId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::collections::ActiveModel {
id: Set(::flix_model::id::CollectionId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_collection;
@@ -448,17 +458,19 @@ pub mod test {
macro_rules! make_content_movie {
($db:expr, $lid:expr, $id:expr, $pid:expr) => {
$crate::entity::info::test::make_info_movie!($db, $id);
$crate::entity::content::movies::ActiveModel {
id: Set(::flix_model::id::MovieId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_media_path: Set(::std::string::String::new()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::movies::ActiveModel {
id: Set(::flix_model::id::MovieId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_media_path: Set(::alloc::string::String::new()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_movie;
@@ -466,16 +478,18 @@ pub mod test {
macro_rules! make_content_show {
($db:expr, $lid:expr, $id:expr, $pid:expr) => {
$crate::entity::info::test::make_info_show!($db, $id);
$crate::entity::content::shows::ActiveModel {
id: Set(::flix_model::id::ShowId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::shows::ActiveModel {
id: Set(::flix_model::id::ShowId::from_raw($id)),
parent_id: Set($pid.map(::flix_model::id::CollectionId::from_raw)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_show;
@@ -483,16 +497,18 @@ pub mod test {
macro_rules! make_content_season {
($db:expr, $lid:expr, $show:expr, $season:expr) => {
$crate::entity::info::test::make_info_season!($db, $show, $season);
$crate::entity::content::seasons::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::seasons::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_season;
@@ -506,19 +522,21 @@ pub mod test {
};
(@make, $db:expr, $lid:expr, $show:expr, $season:expr, $episode:expr, $count:literal) => {
$crate::entity::info::test::make_info_episode!($db, $show, $season, $episode);
$crate::entity::content::episodes::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
count: Set($count),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_media_path: Set(::std::string::String::new()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::content::episodes::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
count: Set($count),
library_id: Set(::flix_model::id::LibraryId::from_raw($lid)),
directory: Set(::std::path::PathBuf::new().into()),
relative_media_path: Set(::alloc::string::String::new()),
relative_poster_path: Set(::core::option::Option::None),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_content_episode;
@@ -550,7 +568,10 @@ mod tests {
use super::super::tests::get_error_kind;
use super::super::tests::{noneable, notsettable};
#[cfg(not(miri))]
#[tokio::test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn use_test_macros() {
let db = new_initialized_memory_db().await;
@@ -562,8 +583,10 @@ mod tests {
make_content_episode!(&db, 1, 1, 1, 1);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_libraries() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn round_trip_libraries() {
let db = new_initialized_memory_db().await;
macro_rules! assert_library {
@@ -601,8 +624,11 @@ mod tests {
assert_library!(&db, 6, Success; last_scan_duration);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_collections() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_collections() {
let db = new_initialized_memory_db().await;
macro_rules! assert_collection {
@@ -656,8 +682,11 @@ mod tests {
assert_collection!(&db, 7, None, 1, Success; relative_poster_path);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_movies() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_movies() {
let db = new_initialized_memory_db().await;
macro_rules! assert_movie {
@@ -718,8 +747,11 @@ mod tests {
assert_movie!(&db, 8, None, 1, Success; relative_poster_path);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_shows() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_shows() {
let db = new_initialized_memory_db().await;
macro_rules! assert_show {
@@ -776,8 +808,11 @@ mod tests {
assert_show!(&db, 7, None, 1, Success; relative_poster_path);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_seasons() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_seasons() {
let db = new_initialized_memory_db().await;
macro_rules! assert_season {
@@ -828,8 +863,11 @@ mod tests {
assert_season!(&db, 1, 7, 1, Success; relative_poster_path);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_episodes() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_episodes() {
let db = new_initialized_memory_db().await;
macro_rules! assert_episode {
+149 -119
View File
@@ -1,7 +1,7 @@
//! This module contains entities for storing media information such as
//! titles and overviews
//! titles and overviews.
/// Collection entity
/// Collection entity.
pub mod collections {
use flix_model::id::CollectionId;
@@ -9,38 +9,39 @@ pub mod collections {
use crate::entity;
/// The database representation of a flix collection
/// The database representation of a flix collection.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_info_collections")]
pub struct Model {
/// The collection's ID
/// The collection's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: CollectionId,
/// The collection's title
/// The collection's title.
pub title: String,
/// The collection's overview
/// The collection's overview.
pub overview: String,
/// The sortable title
/// The sortable title.
#[sea_orm(indexed)]
pub sort_title: String,
/// The filesystem-safe slug
/// The filesystem-safe slug.
#[sea_orm(indexed, unique)]
pub fs_slug: String,
/// The url-safe slug
/// The url-safe slug.
#[sea_orm(indexed, unique)]
pub web_slug: String,
/// Potential content for this collection
/// Potential content for this collection.
#[sea_orm(has_one)]
pub content: HasOne<entity::content::collections::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Movie entity
/// Movie entity.
pub mod movies {
use flix_model::id::MovieId;
@@ -49,43 +50,44 @@ pub mod movies {
use crate::entity;
/// The database representation of a flix movie
/// The database representation of a flix movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_info_movies")]
pub struct Model {
/// The movie's ID
/// The movie's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: MovieId,
/// The movie's title
/// The movie's title.
pub title: String,
/// The movie's tagline
/// The movie's tagline.
pub tagline: String,
/// The movie's overview
/// The movie's overview.
pub overview: String,
/// The movie's release date
/// The movie's release date.
#[sea_orm(indexed)]
pub date: NaiveDate,
/// The sortable title
/// The sortable title.
#[sea_orm(indexed)]
pub sort_title: String,
/// The filesystem-safe slug
/// The filesystem-safe slug.
#[sea_orm(indexed, unique)]
pub fs_slug: String,
/// The url-safe slug
/// The url-safe slug.
#[sea_orm(indexed, unique)]
pub web_slug: String,
/// Potential content for this movie
/// Potential content for this movie.
#[sea_orm(has_one)]
pub content: HasOne<entity::content::movies::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Show entity
/// Show entity.
pub mod shows {
use flix_model::id::ShowId;
@@ -94,50 +96,51 @@ pub mod shows {
use crate::entity;
/// The database representation of a flix show
/// The database representation of a flix show.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_info_shows")]
pub struct Model {
/// The show's ID
/// The show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: ShowId,
/// The show's title
/// The show's title.
pub title: String,
/// The show's tagline
/// The show's tagline.
pub tagline: String,
/// The show's overview
/// The show's overview.
pub overview: String,
/// The show's air date
/// The show's air date.
#[sea_orm(indexed)]
pub date: NaiveDate,
/// The sortable title
/// The sortable title.
#[sea_orm(indexed)]
pub sort_title: String,
/// The filesystem-safe slug
/// The filesystem-safe slug.
#[sea_orm(indexed, unique)]
pub fs_slug: String,
/// The url-safe slug
/// The url-safe slug.
#[sea_orm(indexed, unique)]
pub web_slug: String,
/// Seasons that are part of this show
/// Seasons that are part of this show.
#[sea_orm(has_many)]
pub seasons: HasMany<super::seasons::Entity>,
/// Episodes that are part of this show
/// Episodes that are part of this show.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
/// Potential content for this show
/// Potential content for this show.
#[sea_orm(has_one)]
pub content: HasOne<entity::content::shows::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Season entity
/// Season entity.
pub mod seasons {
use flix_model::id::ShowId;
use flix_model::numbers::SeasonNumber;
@@ -147,26 +150,26 @@ pub mod seasons {
use crate::entity;
/// The database representation of a flix season
/// The database representation of a flix season.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_info_seasons")]
pub struct Model {
/// The season's show's ID
/// The season's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The season's number
/// The season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The season's title
/// The season's title.
pub title: String,
/// The season's overview
/// The season's overview.
pub overview: String,
/// The season's air date
/// The season's air date.
#[sea_orm(indexed)]
pub date: NaiveDate,
/// The show this season belongs to
/// The show this season belongs to.
#[sea_orm(
belongs_to,
from = "show_id",
@@ -175,19 +178,20 @@ pub mod seasons {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// Episodes that are part of this season
/// Episodes that are part of this season.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
/// Potential content for this season
/// Potential content for this season.
#[sea_orm(has_one)]
pub content: HasOne<entity::content::seasons::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Episode entity
/// Episode entity.
pub mod episodes {
use flix_model::id::ShowId;
use flix_model::numbers::{EpisodeNumber, SeasonNumber};
@@ -197,29 +201,29 @@ pub mod episodes {
use crate::entity;
/// The database representation of a flix episode
/// The database representation of a flix episode.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_info_episodes")]
pub struct Model {
/// The episode's show's ID
/// The episode's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The episode's season's number
/// The episode's season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The episode's number
/// The episode's number.
#[sea_orm(primary_key, auto_increment = false)]
pub episode_number: EpisodeNumber,
/// The episode's title
/// The episode's title.
pub title: String,
/// The episode's overview
/// The episode's overview.
pub overview: String,
/// The episode's air date
/// The episode's air date.
#[sea_orm(indexed)]
pub date: NaiveDate,
/// The show this episode belongs to
/// The show this episode belongs to.
#[sea_orm(
belongs_to,
from = "show_id",
@@ -228,7 +232,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// The season this episode belongs to
/// The season this episode belongs to.
#[sea_orm(
belongs_to,
from = "(show_id, season_number)",
@@ -238,101 +242,112 @@ pub mod episodes {
)]
pub season: HasOne<super::seasons::Entity>,
/// Potential content for this episode
/// Potential content for this episode.
#[sea_orm(has_one)]
pub content: HasOne<entity::content::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Macros for creating info entities
/// Macros for creating info entities.
#[cfg(test)]
pub mod test {
macro_rules! make_info_collection {
($db:expr, $id:expr) => {
$crate::entity::info::collections::ActiveModel {
id: Set(::flix_model::id::CollectionId::from_raw($id)),
title: Set(::std::string::String::new()),
overview: Set(::std::string::String::new()),
sort_title: Set(::std::string::String::new()),
fs_slug: Set(format!("C FS {}", $id)),
web_slug: Set(format!("C Web {}", $id)),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::info::collections::ActiveModel {
id: Set(::flix_model::id::CollectionId::from_raw($id)),
title: Set(::alloc::string::String::new()),
overview: Set(::alloc::string::String::new()),
sort_title: Set(::alloc::string::String::new()),
fs_slug: Set(format!("C FS {}", $id)),
web_slug: Set(format!("C Web {}", $id)),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_info_collection;
macro_rules! make_info_movie {
($db:expr, $id:expr) => {
$crate::entity::info::movies::ActiveModel {
id: Set(::flix_model::id::MovieId::from_raw($id)),
title: Set(::std::string::String::new()),
tagline: Set(::std::string::String::new()),
overview: Set(::std::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
sort_title: Set(::std::string::String::new()),
fs_slug: Set(format!("M FS {}", $id)),
web_slug: Set(format!("M Web {}", $id)),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::info::movies::ActiveModel {
id: Set(::flix_model::id::MovieId::from_raw($id)),
title: Set(::alloc::string::String::new()),
tagline: Set(::alloc::string::String::new()),
overview: Set(::alloc::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
sort_title: Set(::alloc::string::String::new()),
fs_slug: Set(format!("M FS {}", $id)),
web_slug: Set(format!("M Web {}", $id)),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_info_movie;
macro_rules! make_info_show {
($db:expr, $id:expr) => {
$crate::entity::info::shows::ActiveModel {
id: Set(::flix_model::id::ShowId::from_raw($id)),
title: Set(::std::string::String::new()),
tagline: Set(::std::string::String::new()),
overview: Set(::std::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
sort_title: Set(::std::string::String::new()),
fs_slug: Set(format!("S FS {}", $id)),
web_slug: Set(format!("S Web {}", $id)),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::info::shows::ActiveModel {
id: Set(::flix_model::id::ShowId::from_raw($id)),
title: Set(::alloc::string::String::new()),
tagline: Set(::alloc::string::String::new()),
overview: Set(::alloc::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
sort_title: Set(::alloc::string::String::new()),
fs_slug: Set(format!("S FS {}", $id)),
web_slug: Set(format!("S Web {}", $id)),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_info_show;
macro_rules! make_info_season {
($db:expr, $show:expr, $season:expr) => {
$crate::entity::info::seasons::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
title: Set(::std::string::String::new()),
overview: Set(::std::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::info::seasons::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
title: Set(::alloc::string::String::new()),
overview: Set(::alloc::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_info_season;
macro_rules! make_info_episode {
($db:expr, $show:expr, $season:expr, $episode:expr) => {
$crate::entity::info::episodes::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
title: Set(::std::string::String::new()),
overview: Set(::std::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
}
.insert($db)
.await
.expect("insert");
drop(
$crate::entity::info::episodes::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
title: Set(::alloc::string::String::new()),
overview: Set(::alloc::string::String::new()),
date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
}
.insert($db)
.await
.expect("insert"),
);
};
}
pub(crate) use make_info_episode;
@@ -355,7 +370,10 @@ mod tests {
make_info_collection, make_info_episode, make_info_movie, make_info_season, make_info_show,
};
#[cfg(not(miri))]
#[tokio::test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn use_test_macros() {
let db = new_initialized_memory_db().await;
@@ -366,8 +384,10 @@ mod tests {
make_info_episode!(&db, 1, 1, 1);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_collections() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn round_trip_collections() {
let db = new_initialized_memory_db().await;
macro_rules! assert_collection {
@@ -409,8 +429,10 @@ mod tests {
assert_collection!(&db, 8, NotNullViolation; web_slug);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_movies() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn round_trip_movies() {
let db = new_initialized_memory_db().await;
macro_rules! assert_movie {
@@ -458,8 +480,10 @@ mod tests {
assert_movie!(&db, 10, NotNullViolation; web_slug);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_shows() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn round_trip_shows() {
let db = new_initialized_memory_db().await;
macro_rules! assert_show {
@@ -510,8 +534,11 @@ mod tests {
assert_show!(&db, 10, NotNullViolation; web_slug);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_seasons() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_seasons() {
let db = new_initialized_memory_db().await;
macro_rules! assert_season {
@@ -561,8 +588,11 @@ mod tests {
assert_season!(&db, 1, 7, NotNullViolation; date);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_episodes() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_episodes() {
let db = new_initialized_memory_db().await;
macro_rules! assert_episode {
+113 -63
View File
@@ -1,4 +1,10 @@
//! Entity structs for interacting with the database
//! Entity structs for interacting with the database.
#![expect(clippy::exhaustive_enums, reason = "reflects the database schema")]
#![expect(clippy::exhaustive_structs, reason = "reflects the database schema")]
#![expect(clippy::derive_partial_eq_without_eq, reason = "#[sea_orm::model]")]
#![expect(clippy::impl_trait_in_params, reason = "#[sea_orm::model]")]
#![expect(clippy::same_name_method, reason = "#[sea_orm::model]")]
pub mod content;
pub mod info;
@@ -13,8 +19,6 @@ mod tests {
use flix_tmdb::model::id::TmdbRepr as TmdbReprId;
use sea_orm::ActiveValue::Set;
use sea_orm::DatabaseConnection;
use sea_orm::DbErr;
use sea_orm::entity::prelude::*;
use sea_orm::sqlx::error::ErrorKind;
use seamantic::model::id::SeaOrmRepr as SeaOrmReprId;
@@ -32,35 +36,36 @@ mod tests {
use crate::tests::new_initialized_memory_db;
#[derive(Debug)]
pub enum ErrorKindError {
NotRuntimeError,
NotSqlxError,
NotDatabaseError,
pub(crate) enum ErrorKindNot {
Runtime,
Sqlx,
Database,
}
pub fn get_error_kind(error: DbErr) -> Result<ErrorKind, ErrorKindError> {
let runtime_err = match error {
DbErr::Conn(runtime_err) => runtime_err,
DbErr::Exec(runtime_err) => runtime_err,
DbErr::Query(runtime_err) => runtime_err,
_ => return Err(ErrorKindError::NotRuntimeError),
/// Extract the error kind from a [`DbErr`].
///
/// # Errors
/// If the [`DbErr`] internals diverge from [`SqlxError::Database`].
pub(crate) fn get_error_kind(error: DbErr) -> Result<ErrorKind, ErrorKindNot> {
let (DbErr::Conn(runtime_err) | DbErr::Exec(runtime_err) | DbErr::Query(runtime_err)) =
error
else {
return Err(ErrorKindNot::Runtime);
};
let sqlx_err = match runtime_err {
sea_orm::RuntimeErr::SqlxError(sqlx_err) => sqlx_err,
_ => return Err(ErrorKindError::NotSqlxError),
let RuntimeErr::SqlxError(sqlx_err) = runtime_err else {
return Err(ErrorKindNot::Sqlx);
};
let database_err = match sqlx_err.as_ref() {
sea_orm::SqlxError::Database(database_err) => database_err,
_ => return Err(ErrorKindError::NotDatabaseError),
let SqlxError::Database(database_err) = sqlx_err.as_ref() else {
return Err(ErrorKindNot::Database);
};
Ok(database_err.kind())
}
/// Helper macro for writing tests for `ActiveModel` structs where
/// toggling [sea_orm::ActiveValue] is needed
/// toggling [`sea_orm::ActiveValue`] is needed.
macro_rules! notsettable {
($field:ident, $value:expr $(, $($skip:ident),+)?) => {
if notsettable!(@skip, $field $(, $($skip),+)?) {
@@ -76,7 +81,7 @@ mod tests {
pub(super) use notsettable;
/// Helper macro for writing tests for `ActiveModel` structs where
/// toggling [sea_orm::ActiveValue] is needed
/// toggling [`sea_orm::ActiveValue`] is needed.
macro_rules! noneable {
($field:ident, $value:expr $(, $($skip:ident),+)?) => {
if noneable!(@skip, $field $(, $($skip),+)?) {
@@ -148,6 +153,11 @@ mod tests {
}
}
/// Initialize a database with data to be deleted.
///
/// # Panics
/// If any database operation fails, since this is only for testing.
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn initialize_deletion_test_database(id: &DbId) -> DatabaseConnection {
let db = new_initialized_memory_db().await;
@@ -284,12 +294,14 @@ mod tests {
db
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_info_collection() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_info_collection() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::info::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::info::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.collection,
))
.exec(&db)
@@ -368,12 +380,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_info_movie() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_info_movie() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::info::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::info::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.movie,
))
.exec(&db)
@@ -452,15 +466,19 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_info_show() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_info_show() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::info::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw(id.flix.show))
.exec(&db)
.await
.expect("Entity::delete_by_id");
_ = entity::info::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.show,
))
.exec(&db)
.await
.expect("Entity::delete_by_id");
assert_eq!(
Ok(1),
@@ -534,12 +552,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_info_season() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_info_season() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::info::seasons::Entity::delete_by_id((
_ = entity::info::seasons::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.show),
SeasonNumber::new(id.flix.season),
))
@@ -619,12 +639,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_info_episodes() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_info_episodes() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::info::episodes::Entity::delete_by_id((
_ = entity::info::episodes::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.show),
SeasonNumber::new(id.flix.season),
EpisodeNumber::new(id.flix.episode),
@@ -705,13 +727,15 @@ mod tests {
);
}
#[cfg(not(miri))]
#[cfg(feature = "tmdb")]
#[tokio::test]
async fn test_delete_tmdb_collection() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_tmdb_collection() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::tmdb::collections::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(
_ = entity::tmdb::collections::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(
id.tmdb.collection,
))
.exec(&db)
@@ -790,13 +814,15 @@ mod tests {
);
}
#[cfg(not(miri))]
#[cfg(feature = "tmdb")]
#[tokio::test]
async fn test_delete_tmdb_movie() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_tmdb_movie() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::tmdb::movies::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(
_ = entity::tmdb::movies::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(
id.tmdb.movie,
))
.exec(&db)
@@ -875,16 +901,20 @@ mod tests {
);
}
#[cfg(not(miri))]
#[cfg(feature = "tmdb")]
#[tokio::test]
async fn test_delete_tmdb_show() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_tmdb_show() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::tmdb::shows::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(id.tmdb.show))
.exec(&db)
.await
.expect("Entity::delete_by_id");
_ = entity::tmdb::shows::Entity::delete_by_id(flix_tmdb::model::id::Id::from_raw(
id.tmdb.show,
))
.exec(&db)
.await
.expect("Entity::delete_by_id");
assert_eq!(
Ok(1),
@@ -958,13 +988,15 @@ mod tests {
);
}
#[cfg(not(miri))]
#[cfg(feature = "tmdb")]
#[tokio::test]
async fn test_delete_tmdb_season() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_tmdb_season() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::tmdb::seasons::Entity::delete_by_id((
_ = entity::tmdb::seasons::Entity::delete_by_id((
flix_tmdb::model::id::Id::from_raw(id.tmdb.show),
SeasonNumber::new(id.tmdb.season),
))
@@ -1044,13 +1076,15 @@ mod tests {
);
}
#[cfg(not(miri))]
#[cfg(feature = "tmdb")]
#[tokio::test]
async fn test_delete_tmdb_episode() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_tmdb_episode() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::tmdb::episodes::Entity::delete_by_id((
_ = entity::tmdb::episodes::Entity::delete_by_id((
flix_tmdb::model::id::Id::from_raw(id.tmdb.show),
SeasonNumber::new(id.tmdb.season),
EpisodeNumber::new(id.tmdb.episode),
@@ -1131,12 +1165,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_library() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_library() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::libraries::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::content::libraries::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.content.library,
))
.exec(&db)
@@ -1215,12 +1251,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_collection() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_collection() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::content::collections::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.collection,
))
.exec(&db)
@@ -1299,12 +1337,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_movie() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_movie() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::content::movies::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.movie,
))
.exec(&db)
@@ -1383,12 +1423,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_show() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_show() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
_ = entity::content::shows::Entity::delete_by_id(seamantic::model::id::Id::from_raw(
id.flix.show,
))
.exec(&db)
@@ -1467,12 +1509,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_season() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_season() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::seasons::Entity::delete_by_id((
_ = entity::content::seasons::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.show),
SeasonNumber::new(id.flix.season),
))
@@ -1552,12 +1596,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_content_episode() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_content_episode() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::content::episodes::Entity::delete_by_id((
_ = entity::content::episodes::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.show),
SeasonNumber::new(id.flix.season),
EpisodeNumber::new(id.flix.episode),
@@ -1638,12 +1684,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_watched_movie() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_watched_movie() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::watched::movies::Entity::delete_by_id((
_ = entity::watched::movies::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.movie),
id.watch.user,
))
@@ -1723,12 +1771,14 @@ mod tests {
);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_delete_watched_episode() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
async fn delete_watched_episode() {
let id = DbId::default();
let db = initialize_deletion_test_database(&id).await;
entity::watched::episodes::Entity::delete_by_id((
_ = entity::watched::episodes::Entity::delete_by_id((
seamantic::model::id::Id::from_raw(id.flix.show),
SeasonNumber::new(id.flix.season),
EpisodeNumber::new(id.flix.episode),
+84 -61
View File
@@ -1,6 +1,6 @@
//! This module contains entities for storing dynamic data from TMDB
//! This module contains entities for storing dynamic data from TMDB.
/// Collection entity
/// Collection entity.
pub mod collections {
use flix_model::id::CollectionId as FlixId;
use flix_tmdb::model::id::CollectionId;
@@ -10,23 +10,23 @@ pub mod collections {
use crate::entity;
/// The database representation of a tmdb collection
/// The database representation of a tmdb collection.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_tmdb_collections")]
pub struct Model {
/// The collection's TMDB ID
/// The collection's TMDB ID.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_id: CollectionId,
/// The collection's ID
/// The collection's ID.
#[sea_orm(unique)]
pub flix_id: FlixId,
/// The date of the last update
/// The date of the last update.
pub last_update: DateTime<Utc>,
/// The number of movies in the collection
/// The number of movies in the collection.
pub movie_count: u16,
/// The info for this collection
/// The info for this collection.
#[sea_orm(
belongs_to,
from = "flix_id",
@@ -36,15 +36,16 @@ pub mod collections {
)]
pub info: HasOne<entity::info::collections::Entity>,
/// Movies that are in this collection
/// Movies that are in this collection.
#[sea_orm(has_many)]
pub movies: HasMany<super::movies::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Movie entity
/// Movie entity.
pub mod movies {
use flix_model::id::MovieId as FlixId;
use flix_tmdb::model::id::{CollectionId, MovieId};
@@ -56,26 +57,26 @@ pub mod movies {
use crate::entity;
/// The database representation of a tmdb movie
/// The database representation of a tmdb movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_tmdb_movies")]
pub struct Model {
/// The movie's TMDB ID
/// The movie's TMDB ID.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_id: MovieId,
/// The movie's ID
/// The movie's ID.
#[sea_orm(unique)]
pub flix_id: FlixId,
/// The date of the last update
/// The date of the last update.
pub last_update: DateTime<Utc>,
/// The movie's runtime in seconds
/// The movie's runtime in seconds.
pub runtime: Seconds,
/// The TMDB ID of the collection this movie belongs to
/// The TMDB ID of the collection this movie belongs to.
#[sea_orm(indexed)]
pub collection_id: Option<CollectionId>,
/// The collection this movie belongs to
/// The collection this movie belongs to.
#[sea_orm(
belongs_to,
from = "collection_id",
@@ -84,7 +85,7 @@ pub mod movies {
on_delete = "Cascade"
)]
pub collection: HasOne<super::collections::Entity>,
/// The info for this movie
/// The info for this movie.
#[sea_orm(
belongs_to,
from = "flix_id",
@@ -95,10 +96,11 @@ pub mod movies {
pub info: HasOne<entity::info::movies::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Show entity
/// Show entity.
pub mod shows {
use flix_model::id::ShowId as FlixId;
use flix_tmdb::model::id::ShowId;
@@ -108,23 +110,23 @@ pub mod shows {
use crate::entity;
/// The database representation of a tmdb show
/// The database representation of a tmdb show.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_tmdb_shows")]
pub struct Model {
/// The show's TMDB ID
/// The show's TMDB ID.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_id: ShowId,
/// The show's ID
/// The show's ID.
#[sea_orm(unique)]
pub flix_id: FlixId,
/// The movie's runtime in seconds
/// The movie's runtime in seconds.
pub last_update: DateTime<Utc>,
/// The number of seasons the show has
/// The number of seasons the show has.
pub number_of_seasons: u32,
/// The info for this show
/// The info for this show.
#[sea_orm(
belongs_to,
from = "flix_id",
@@ -134,18 +136,19 @@ pub mod shows {
)]
pub info: HasOne<entity::info::shows::Entity>,
/// Seasons that are part of this show
/// Seasons that are part of this show.
#[sea_orm(has_many)]
pub seasons: HasMany<super::seasons::Entity>,
/// Episodes that are part of this show
/// Episodes that are part of this show.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Season entity
/// Season entity.
pub mod seasons {
use flix_model::id::ShowId as FlixId;
use flix_model::numbers::SeasonNumber;
@@ -156,27 +159,27 @@ pub mod seasons {
use crate::entity;
/// The database representation of a tmdb season
/// The database representation of a tmdb season.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_tmdb_seasons")]
pub struct Model {
/// The season's show's TMDB ID
/// The season's show's TMDB ID.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_show: ShowId,
/// The season's TMDB season number
/// The season's TMDB season number.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_season: SeasonNumber,
/// The season's show's ID
/// The season's show's ID.
#[sea_orm(unique_key = "flix")]
pub flix_show: FlixId,
/// The season's number
/// The season's number.
#[sea_orm(unique_key = "flix")]
pub flix_season: SeasonNumber,
/// The date of the last update
/// The date of the last update.
pub last_update: DateTime<Utc>,
/// The show this season belongs to
/// The show this season belongs to.
#[sea_orm(
belongs_to,
from = "tmdb_show",
@@ -185,7 +188,7 @@ pub mod seasons {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// The info for this season
/// The info for this season.
#[sea_orm(
belongs_to,
from = "(flix_show, flix_season)",
@@ -195,15 +198,16 @@ pub mod seasons {
)]
pub info: HasOne<entity::info::seasons::Entity>,
/// Episodes that are part of this season
/// Episodes that are part of this season.
#[sea_orm(has_many)]
pub episodes: HasMany<super::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Season entity
/// Season entity.
pub mod episodes {
use flix_model::id::ShowId as FlixId;
use flix_model::numbers::{EpisodeNumber, SeasonNumber};
@@ -215,35 +219,35 @@ pub mod episodes {
use crate::entity;
/// The database representation of a tmdb episode
/// The database representation of a tmdb episode.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_tmdb_episodes")]
pub struct Model {
/// The episode's show's TMDB ID
/// The episode's show's TMDB ID.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_show: ShowId,
/// The episode's season's TMDB season number
/// The episode's season's TMDB season number.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_season: SeasonNumber,
/// The episode's TMDB episode number
/// The episode's TMDB episode number.
#[sea_orm(primary_key, auto_increment = false)]
pub tmdb_episode: EpisodeNumber,
/// The episode's show's ID
/// The episode's show's ID.
#[sea_orm(unique_key = "flix")]
pub flix_show: FlixId,
/// The episode's season's number
/// The episode's season's number.
#[sea_orm(unique_key = "flix")]
pub flix_season: SeasonNumber,
/// The episode's number
/// The episode's number.
#[sea_orm(unique_key = "flix")]
pub flix_episode: EpisodeNumber,
/// The date of the last update
/// The date of the last update.
pub last_update: DateTime<Utc>,
/// The episode's runtime in seconds
/// The episode's runtime in seconds.
pub runtime: Seconds,
/// The show this episode belongs to
/// The show this episode belongs to.
#[sea_orm(
belongs_to,
from = "tmdb_show",
@@ -252,7 +256,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub show: HasOne<super::shows::Entity>,
/// The season this episode belongs to
/// The season this episode belongs to.
#[sea_orm(
belongs_to,
from = "(tmdb_show, tmdb_season)",
@@ -261,7 +265,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub season: HasOne<super::seasons::Entity>,
/// The info for this episode
/// The info for this episode.
#[sea_orm(
belongs_to,
from = "(flix_show, flix_season, flix_episode)",
@@ -272,15 +276,16 @@ pub mod episodes {
pub info: HasOne<entity::info::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Macros for creating tmdb entities
/// Macros for creating tmdb entities.
#[cfg(test)]
pub mod test {
macro_rules! make_tmdb_collection {
($db:expr, $id:expr, $flix_id:expr) => {
$crate::entity::tmdb::collections::ActiveModel {
_ = $crate::entity::tmdb::collections::ActiveModel {
tmdb_id: Set(::flix_tmdb::model::id::CollectionId::from_raw($id)),
flix_id: Set(::flix_model::id::CollectionId::from_raw($flix_id)),
last_update: Set(::chrono::Utc::now()),
@@ -295,7 +300,7 @@ pub mod test {
macro_rules! make_tmdb_movie {
($db:expr, $id:expr, $flix_id:expr) => {
$crate::entity::tmdb::movies::ActiveModel {
_ = $crate::entity::tmdb::movies::ActiveModel {
tmdb_id: Set(::flix_tmdb::model::id::MovieId::from_raw($id)),
flix_id: Set(::flix_model::id::MovieId::from_raw($flix_id)),
last_update: Set(::chrono::Utc::now()),
@@ -311,7 +316,7 @@ pub mod test {
macro_rules! make_tmdb_show {
($db:expr, $id:expr, $flix_id:expr) => {
$crate::entity::tmdb::shows::ActiveModel {
_ = $crate::entity::tmdb::shows::ActiveModel {
tmdb_id: Set(::flix_tmdb::model::id::ShowId::from_raw($id)),
flix_id: Set(::flix_model::id::ShowId::from_raw($flix_id)),
last_update: Set(::chrono::Utc::now()),
@@ -326,7 +331,7 @@ pub mod test {
macro_rules! make_tmdb_season {
($db:expr, $show:expr, $season:expr, $flix_show:expr, $flix_season:expr) => {
$crate::entity::tmdb::seasons::ActiveModel {
_ = $crate::entity::tmdb::seasons::ActiveModel {
tmdb_show: Set(::flix_tmdb::model::id::ShowId::from_raw($show)),
tmdb_season: Set(::flix_model::numbers::SeasonNumber::new($season)),
flix_show: Set(::flix_model::id::ShowId::from_raw($flix_show)),
@@ -342,7 +347,7 @@ pub mod test {
macro_rules! make_tmdb_episode {
($db:expr, $show:expr, $season:expr, $episode:expr, $flix_show:expr, $flix_season:expr, $flix_episode:expr) => {
$crate::entity::tmdb::episodes::ActiveModel {
_ = $crate::entity::tmdb::episodes::ActiveModel {
tmdb_show: Set(::flix_tmdb::model::id::ShowId::from_raw($show)),
tmdb_season: Set(::flix_model::numbers::SeasonNumber::new($season)),
tmdb_episode: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
@@ -385,7 +390,10 @@ mod tests {
make_tmdb_collection, make_tmdb_episode, make_tmdb_movie, make_tmdb_season, make_tmdb_show,
};
#[cfg(not(miri))]
#[tokio::test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn use_test_macros() {
let db = new_initialized_memory_db().await;
@@ -402,8 +410,11 @@ mod tests {
make_tmdb_episode!(&db, 1, 1, 1, 1, 1, 1);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_collections() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_collections() {
let db = new_initialized_memory_db().await;
macro_rules! assert_collection {
@@ -449,8 +460,11 @@ mod tests {
assert_collection!(&db, 6, 6, NotNullViolation; movie_count);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_movies() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_movies() {
let db = new_initialized_memory_db().await;
macro_rules! assert_movie {
@@ -502,8 +516,11 @@ mod tests {
assert_movie!(&db, 7, 7, None, ForeignKeyViolation; collection_id);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_shows() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_shows() {
let db = new_initialized_memory_db().await;
macro_rules! assert_show {
@@ -552,8 +569,11 @@ mod tests {
assert_show!(&db, 6, 6, NotNullViolation; number_of_seasons);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_seasons() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_seasons() {
let db = new_initialized_memory_db().await;
macro_rules! assert_season {
@@ -607,8 +627,11 @@ mod tests {
assert_season!(&db, 1, 7, 1, 7, NotNullViolation; last_update);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_episodes() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_episodes() {
let db = new_initialized_memory_db().await;
macro_rules! assert_episode {
+73 -50
View File
@@ -1,6 +1,6 @@
//! This module contains entities for storing watched information
//! This module contains entities for storing watched information.
/// Collection entity
/// Collection entity.
pub mod collections {
use flix_model::id::{CollectionId, RawId};
@@ -9,21 +9,21 @@ pub mod collections {
use crate::entity;
/// The database representation of a watched movie
/// The database representation of a watched movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_watched_collections")]
pub struct Model {
/// The collection's ID
/// The collection's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: CollectionId,
/// The user's ID
/// The user's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: RawId,
/// The date this collection was watched
/// The date this collection was watched.
pub watched_date: DateTime<Utc>,
/// The info for this collection
/// The info for this collection.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -33,15 +33,16 @@ pub mod collections {
on_delete = "Cascade"
)]
pub info: HasOne<entity::info::collections::Entity>,
/// The content for this collection
/// The content for this collection.
#[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
pub content: HasOne<entity::content::collections::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Movie entity
/// Movie entity.
pub mod movies {
use flix_model::id::{MovieId, RawId};
@@ -50,21 +51,21 @@ pub mod movies {
use crate::entity;
/// The database representation of a watched movie
/// The database representation of a watched movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_watched_movies")]
pub struct Model {
/// The movie's ID
/// The movie's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: MovieId,
/// The user's ID
/// The user's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: RawId,
/// The date this movie was watched
/// The date this movie was watched.
pub watched_date: DateTime<Utc>,
/// The info for this movie
/// The info for this movie.
#[sea_orm(
belongs_to,
from = "id",
@@ -73,15 +74,16 @@ pub mod movies {
on_delete = "Cascade"
)]
pub info: HasOne<entity::info::movies::Entity>,
/// The content for this movie
/// The content for this movie.
#[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
pub content: HasOne<entity::content::movies::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Show entity
/// Show entity.
pub mod shows {
use flix_model::id::{RawId, ShowId};
@@ -90,21 +92,21 @@ pub mod shows {
use crate::entity;
/// The database representation of a watched movie
/// The database representation of a watched movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_watched_shows")]
pub struct Model {
/// The show's ID
/// The show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub id: ShowId,
/// The user's ID
/// The user's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: RawId,
/// The date this show was watched
/// The date this show was watched.
pub watched_date: DateTime<Utc>,
/// The info for this show
/// The info for this show.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -114,15 +116,16 @@ pub mod shows {
on_delete = "Cascade"
)]
pub info: HasOne<entity::info::shows::Entity>,
/// The content for this show
/// The content for this show.
#[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
pub content: HasOne<entity::content::shows::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Season entity
/// Season entity.
pub mod seasons {
use flix_model::id::{RawId, ShowId};
use flix_model::numbers::SeasonNumber;
@@ -132,24 +135,24 @@ pub mod seasons {
use crate::entity;
/// The database representation of a watched movie
/// The database representation of a watched movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_watched_seasons")]
pub struct Model {
/// The season's show's ID
/// The season's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The season's number
/// The season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The user's ID
/// The user's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: RawId,
/// The date this season was watched
/// The date this season was watched.
pub watched_date: DateTime<Utc>,
/// The info for this season
/// The info for this season.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -159,7 +162,7 @@ pub mod seasons {
on_delete = "Cascade"
)]
pub info: HasOne<entity::info::seasons::Entity>,
/// The content for this season
/// The content for this season.
#[sea_orm(
belongs_to,
relation_enum = "Content",
@@ -170,10 +173,11 @@ pub mod seasons {
pub content: HasOne<entity::content::seasons::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Episode entity
/// Episode entity.
pub mod episodes {
use flix_model::id::{RawId, ShowId};
use flix_model::numbers::{EpisodeNumber, SeasonNumber};
@@ -183,27 +187,27 @@ pub mod episodes {
use crate::entity;
/// The database representation of a watched movie
/// The database representation of a watched movie.
#[sea_orm::model]
#[derive(Debug, Clone, DeriveEntityModel)]
#[sea_orm(table_name = "flix_watched_episodes")]
pub struct Model {
/// The episode's show's ID
/// The episode's show's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub show_id: ShowId,
/// The episode's season's number
/// The episode's season's number.
#[sea_orm(primary_key, auto_increment = false)]
pub season_number: SeasonNumber,
/// The episode's number
/// The episode's number.
#[sea_orm(primary_key, auto_increment = false)]
pub episode_number: EpisodeNumber,
/// The user's ID
/// The user's ID.
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: RawId,
/// The date this episode was watched
/// The date this episode was watched.
pub watched_date: DateTime<Utc>,
/// The info for this episode
/// The info for this episode.
#[sea_orm(
belongs_to,
relation_enum = "Info",
@@ -213,7 +217,7 @@ pub mod episodes {
on_delete = "Cascade"
)]
pub info: HasOne<entity::info::episodes::Entity>,
/// The content for this episode
/// The content for this episode.
#[sea_orm(
belongs_to,
relation_enum = "Content",
@@ -224,15 +228,16 @@ pub mod episodes {
pub content: HasOne<entity::content::episodes::Entity>,
}
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl ActiveModelBehavior for ActiveModel {}
}
/// Macros for creating watched entities
/// Macros for creating watched entities.
#[cfg(test)]
pub mod test {
macro_rules! make_watched_movie {
($db:expr, $id:expr, $user:expr) => {
$crate::entity::watched::movies::ActiveModel {
_ = $crate::entity::watched::movies::ActiveModel {
id: Set(::flix_model::id::MovieId::from_raw($id)),
user_id: Set($user),
watched_date: Set(::chrono::Utc::now()),
@@ -246,7 +251,7 @@ pub mod test {
macro_rules! make_watched_episode {
($db:expr, $show:expr, $season:expr, $episode:expr, $user:expr) => {
$crate::entity::watched::episodes::ActiveModel {
_ = $crate::entity::watched::episodes::ActiveModel {
show_id: Set(::flix_model::id::ShowId::from_raw($show)),
season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
@@ -284,7 +289,10 @@ mod tests {
use super::super::tests::notsettable;
use super::test::{make_watched_episode, make_watched_movie};
#[cfg(not(miri))]
#[tokio::test]
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn use_test_macros() {
let db = new_initialized_memory_db().await;
@@ -297,8 +305,11 @@ mod tests {
make_watched_episode!(&db, 1, 1, 1, 1);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_movies() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_movies() {
let db = new_initialized_memory_db().await;
macro_rules! assert_movie {
@@ -340,8 +351,11 @@ mod tests {
assert_movie!(&db, 5, 1, NotNullViolation; watched_date);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_round_trip_episodes() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn round_trip_episodes() {
let db = new_initialized_memory_db().await;
macro_rules! assert_episode {
@@ -394,13 +408,16 @@ mod tests {
assert_episode!(&db, 7, 1, 1, 1, NotNullViolation; watched_date);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_query_seasons() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn query_seasons() {
let db = new_initialized_memory_db().await;
macro_rules! assert_season {
($db:expr, $show:literal, $season:literal, $uid:literal, Watched) => {
assert_season!(@find, $db, $show, $season, $uid)
_ = assert_season!(@find, $db, $show, $season, $uid)
.ok_or(())
.expect("is none");
};
@@ -462,13 +479,16 @@ mod tests {
assert_season!(&db, 1, 2, 3, Unwatched);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_query_shows() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn query_shows() {
let db = new_initialized_memory_db().await;
macro_rules! assert_show {
($db:expr, $show:literal, $uid:literal, Watched) => {
assert_show!(@find, $db, $show, $uid)
_ = assert_show!(@find, $db, $show, $uid)
.ok_or(())
.expect("is none");
};
@@ -517,13 +537,16 @@ mod tests {
assert_show!(&db, 1, 2, Unwatched);
}
#[cfg(not(miri))]
#[tokio::test]
async fn test_query_collections() {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
#[expect(clippy::default_numeric_fallback, reason = "unit test")]
async fn query_collections() {
let db = new_initialized_memory_db().await;
macro_rules! assert_collection {
($db:expr, $id:literal, $uid:literal, Watched) => {
assert_collection!(@find, $db, $id, $uid)
_ = assert_collection!(@find, $db, $id, $uid)
.ok_or(())
.expect("is none");
};
+6 -2
View File
@@ -1,7 +1,10 @@
//! flix-db provides types for storing persistent data about media
//! flix-db provides types for storing persistent data about media.
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(test)]
extern crate alloc;
pub mod connection;
pub mod entity;
pub mod migration;
@@ -12,7 +15,8 @@ mod tests {
use crate::connection::Connection;
pub async fn new_initialized_memory_db() -> DatabaseConnection {
#[expect(clippy::missing_panics_doc, reason = "unit test")]
pub(crate) async fn new_initialized_memory_db() -> DatabaseConnection {
let options = ConnectOptions::new("sqlite::memory:");
let db = Database::connect(options)
@@ -1,18 +1,22 @@
//! Custom views for collections.
use sea_orm::prelude::*;
use sea_orm::sea_query::Table;
use sea_orm::{ConnectionTrait, DbBackend, Statement};
use sea_orm::{DbBackend, Statement};
use sea_orm_migration::prelude::*;
pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
/// # Errors
/// Database operations can fail.
pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table("flix_watched_collections").to_owned())
.await?;
manager
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
CREATE VIEW flix_watched_collections AS
WITH RECURSIVE
watched_items AS (
@@ -81,22 +85,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
)
GROUP BY ci.parent_id, wi.user_id
;
"#,
",
))
.await?;
Ok(())
}
pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
/// # Errors
/// Database operations can fail.
pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
DROP VIEW flix_watched_collections
;
"#,
",
))
.await?;
@@ -11,10 +11,16 @@ mod collections;
mod seasons;
mod shows;
/// The migration entry point.
#[derive(DeriveMigrationName)]
pub(super) struct Migration;
#[async_trait::async_trait]
#[expect(
elided_lifetimes_in_paths,
reason = "async_trait causes lifetimes to be strange"
)]
#[expect(clippy::missing_trait_methods, reason = "accept default methods")]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
seasons::up(manager).await?;
+15 -9
View File
@@ -1,18 +1,22 @@
//! Custom views for seasons.
use sea_orm::prelude::*;
use sea_orm::sea_query::Table;
use sea_orm::{ConnectionTrait, DbBackend, Statement};
use sea_orm::{DbBackend, Statement};
use sea_orm_migration::prelude::*;
pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
/// # Errors
/// Database operations can fail.
pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table("flix_watched_seasons").to_owned())
.await?;
manager
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
CREATE VIEW flix_watched_seasons AS
SELECT
w.show_id,
@@ -36,22 +40,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
)
GROUP BY w.show_id, w.season_number, w.user_id
;
"#,
",
))
.await?;
Ok(())
}
pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
/// # Errors
/// Database operations can fail.
pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
DROP VIEW flix_watched_seasons
;
"#,
",
))
.await?;
+15 -9
View File
@@ -1,18 +1,22 @@
//! Custom views for shows.
use sea_orm::prelude::*;
use sea_orm::sea_query::Table;
use sea_orm::{ConnectionTrait, DbBackend, Statement};
use sea_orm::{DbBackend, Statement};
use sea_orm_migration::prelude::*;
pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
/// # Errors
/// Database operations can fail.
pub(crate) async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table("flix_watched_shows").to_owned())
.await?;
manager
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
CREATE VIEW flix_watched_shows AS
SELECT
w.show_id as id,
@@ -33,22 +37,24 @@ pub async fn up(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
)
GROUP BY w.show_id, w.user_id
;
"#,
",
))
.await?;
Ok(())
}
pub async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
manager
/// # Errors
/// Database operations can fail.
pub(crate) async fn down(manager: &SchemaManager<'_>) -> Result<(), DbErr> {
_ = manager
.get_connection()
.execute_raw(Statement::from_string(
DbBackend::Sqlite,
r#"
"
DROP VIEW flix_watched_shows
;
"#,
",
))
.await?;
+1 -1
View File
@@ -1,4 +1,4 @@
//! Migrations for maintaining the database schema
//! Migrations for maintaining the database schema.
seamantic::migrations! {
"seaql_migrations_flix";