1pub mod collections {
6 use flix_model::id::CollectionId;
7
8 use sea_orm::entity::prelude::*;
9
10 use crate::entity;
11
12 #[sea_orm::model]
14 #[derive(Debug, Clone, DeriveEntityModel)]
15 #[sea_orm(table_name = "flix_info_collections")]
16 pub struct Model {
17 #[sea_orm(primary_key, auto_increment = false)]
19 pub id: CollectionId,
20 pub title: String,
22 pub overview: String,
24
25 #[sea_orm(indexed)]
27 pub sort_title: String,
28 #[sea_orm(indexed, unique)]
30 pub fs_slug: String,
31 #[sea_orm(indexed, unique)]
33 pub web_slug: String,
34
35 #[sea_orm(has_one)]
37 pub content: HasOne<entity::content::collections::Entity>,
38 }
39
40 impl ActiveModelBehavior for ActiveModel {}
41}
42
43pub mod movies {
45 use flix_model::id::MovieId;
46
47 use chrono::NaiveDate;
48 use sea_orm::entity::prelude::*;
49
50 use crate::entity;
51
52 #[sea_orm::model]
54 #[derive(Debug, Clone, DeriveEntityModel)]
55 #[sea_orm(table_name = "flix_info_movies")]
56 pub struct Model {
57 #[sea_orm(primary_key, auto_increment = false)]
59 pub id: MovieId,
60 pub title: String,
62 pub tagline: String,
64 pub overview: String,
66 #[sea_orm(indexed)]
68 pub date: NaiveDate,
69
70 #[sea_orm(indexed)]
72 pub sort_title: String,
73 #[sea_orm(indexed, unique)]
75 pub fs_slug: String,
76 #[sea_orm(indexed, unique)]
78 pub web_slug: String,
79
80 #[sea_orm(has_one)]
82 pub content: HasOne<entity::content::movies::Entity>,
83 }
84
85 impl ActiveModelBehavior for ActiveModel {}
86}
87
88pub mod shows {
90 use flix_model::id::ShowId;
91
92 use chrono::NaiveDate;
93 use sea_orm::entity::prelude::*;
94
95 use crate::entity;
96
97 #[sea_orm::model]
99 #[derive(Debug, Clone, DeriveEntityModel)]
100 #[sea_orm(table_name = "flix_info_shows")]
101 pub struct Model {
102 #[sea_orm(primary_key, auto_increment = false)]
104 pub id: ShowId,
105 pub title: String,
107 pub tagline: String,
109 pub overview: String,
111 #[sea_orm(indexed)]
113 pub date: NaiveDate,
114
115 #[sea_orm(indexed)]
117 pub sort_title: String,
118 #[sea_orm(indexed, unique)]
120 pub fs_slug: String,
121 #[sea_orm(indexed, unique)]
123 pub web_slug: String,
124
125 #[sea_orm(has_many)]
127 pub seasons: HasMany<super::seasons::Entity>,
128 #[sea_orm(has_many)]
130 pub episodes: HasMany<super::episodes::Entity>,
131
132 #[sea_orm(has_one)]
134 pub content: HasOne<entity::content::shows::Entity>,
135 }
136
137 impl ActiveModelBehavior for ActiveModel {}
138}
139
140pub mod seasons {
142 use flix_model::id::ShowId;
143 use flix_model::numbers::SeasonNumber;
144
145 use chrono::NaiveDate;
146 use sea_orm::entity::prelude::*;
147
148 use crate::entity;
149
150 #[sea_orm::model]
152 #[derive(Debug, Clone, DeriveEntityModel)]
153 #[sea_orm(table_name = "flix_info_seasons")]
154 pub struct Model {
155 #[sea_orm(primary_key, auto_increment = false)]
157 pub show_id: ShowId,
158 #[sea_orm(primary_key, auto_increment = false)]
160 pub season_number: SeasonNumber,
161 pub title: String,
163 pub overview: String,
165 #[sea_orm(indexed)]
167 pub date: NaiveDate,
168
169 #[sea_orm(
171 belongs_to,
172 from = "show_id",
173 to = "id",
174 on_update = "Cascade",
175 on_delete = "Cascade"
176 )]
177 pub show: HasOne<super::shows::Entity>,
178 #[sea_orm(has_many)]
180 pub episodes: HasMany<super::episodes::Entity>,
181
182 #[sea_orm(has_one)]
184 pub content: HasOne<entity::content::seasons::Entity>,
185 }
186
187 impl ActiveModelBehavior for ActiveModel {}
188}
189
190pub mod episodes {
192 use flix_model::id::ShowId;
193 use flix_model::numbers::{EpisodeNumber, SeasonNumber};
194
195 use chrono::NaiveDate;
196 use sea_orm::entity::prelude::*;
197
198 use crate::entity;
199
200 #[sea_orm::model]
202 #[derive(Debug, Clone, DeriveEntityModel)]
203 #[sea_orm(table_name = "flix_info_episodes")]
204 pub struct Model {
205 #[sea_orm(primary_key, auto_increment = false)]
207 pub show_id: ShowId,
208 #[sea_orm(primary_key, auto_increment = false)]
210 pub season_number: SeasonNumber,
211 #[sea_orm(primary_key, auto_increment = false)]
213 pub episode_number: EpisodeNumber,
214 pub title: String,
216 pub overview: String,
218 #[sea_orm(indexed)]
220 pub date: NaiveDate,
221
222 #[sea_orm(
224 belongs_to,
225 from = "show_id",
226 to = "id",
227 on_update = "Cascade",
228 on_delete = "Cascade"
229 )]
230 pub show: HasOne<super::shows::Entity>,
231 #[sea_orm(
233 belongs_to,
234 from = "(show_id, season_number)",
235 to = "(show_id, season_number)",
236 on_update = "Cascade",
237 on_delete = "Cascade"
238 )]
239 pub season: HasOne<super::seasons::Entity>,
240
241 #[sea_orm(has_one)]
243 pub content: HasOne<entity::content::episodes::Entity>,
244 }
245
246 impl ActiveModelBehavior for ActiveModel {}
247}
248
249#[cfg(test)]
251pub mod test {
252 macro_rules! make_info_collection {
253 ($db:expr, $id:expr) => {
254 $crate::entity::info::collections::ActiveModel {
255 id: Set(::flix_model::id::CollectionId::from_raw($id)),
256 title: Set(::std::string::String::new()),
257 overview: Set(::std::string::String::new()),
258 sort_title: Set(::std::string::String::new()),
259 fs_slug: Set(format!("C FS {}", $id)),
260 web_slug: Set(format!("C Web {}", $id)),
261 }
262 .insert($db)
263 .await
264 .expect("insert");
265 };
266 }
267 pub(crate) use make_info_collection;
268
269 macro_rules! make_info_movie {
270 ($db:expr, $id:expr) => {
271 $crate::entity::info::movies::ActiveModel {
272 id: Set(::flix_model::id::MovieId::from_raw($id)),
273 title: Set(::std::string::String::new()),
274 tagline: Set(::std::string::String::new()),
275 overview: Set(::std::string::String::new()),
276 date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
277 sort_title: Set(::std::string::String::new()),
278 fs_slug: Set(format!("M FS {}", $id)),
279 web_slug: Set(format!("M Web {}", $id)),
280 }
281 .insert($db)
282 .await
283 .expect("insert");
284 };
285 }
286 pub(crate) use make_info_movie;
287
288 macro_rules! make_info_show {
289 ($db:expr, $id:expr) => {
290 $crate::entity::info::shows::ActiveModel {
291 id: Set(::flix_model::id::ShowId::from_raw($id)),
292 title: Set(::std::string::String::new()),
293 tagline: Set(::std::string::String::new()),
294 overview: Set(::std::string::String::new()),
295 date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
296 sort_title: Set(::std::string::String::new()),
297 fs_slug: Set(format!("S FS {}", $id)),
298 web_slug: Set(format!("S Web {}", $id)),
299 }
300 .insert($db)
301 .await
302 .expect("insert");
303 };
304 }
305 pub(crate) use make_info_show;
306
307 macro_rules! make_info_season {
308 ($db:expr, $show:expr, $season:expr) => {
309 $crate::entity::info::seasons::ActiveModel {
310 show_id: Set(::flix_model::id::ShowId::from_raw($show)),
311 season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
312 title: Set(::std::string::String::new()),
313 overview: Set(::std::string::String::new()),
314 date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
315 }
316 .insert($db)
317 .await
318 .expect("insert");
319 };
320 }
321 pub(crate) use make_info_season;
322
323 macro_rules! make_info_episode {
324 ($db:expr, $show:expr, $season:expr, $episode:expr) => {
325 $crate::entity::info::episodes::ActiveModel {
326 show_id: Set(::flix_model::id::ShowId::from_raw($show)),
327 season_number: Set(::flix_model::numbers::SeasonNumber::new($season)),
328 episode_number: Set(::flix_model::numbers::EpisodeNumber::new($episode)),
329 title: Set(::std::string::String::new()),
330 overview: Set(::std::string::String::new()),
331 date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
332 }
333 .insert($db)
334 .await
335 .expect("insert");
336 };
337 }
338 pub(crate) use make_info_episode;
339}
340
341#[cfg(test)]
342mod tests {
343 use flix_model::id::{CollectionId, MovieId, ShowId};
344
345 use chrono::NaiveDate;
346 use sea_orm::ActiveValue::{NotSet, Set};
347 use sea_orm::entity::prelude::*;
348 use sea_orm::sqlx::error::ErrorKind;
349
350 use crate::tests::new_initialized_memory_db;
351
352 use super::super::tests::get_error_kind;
353 use super::super::tests::notsettable;
354 use super::test::{
355 make_info_collection, make_info_episode, make_info_movie, make_info_season, make_info_show,
356 };
357
358 #[tokio::test]
359 async fn use_test_macros() {
360 let db = new_initialized_memory_db().await;
361
362 make_info_collection!(&db, 1);
363 make_info_movie!(&db, 1);
364 make_info_show!(&db, 1);
365 make_info_season!(&db, 1, 1);
366 make_info_episode!(&db, 1, 1, 1);
367 }
368
369 #[tokio::test]
370 async fn test_round_trip_collections() {
371 let db = new_initialized_memory_db().await;
372
373 macro_rules! assert_collection {
374 ($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
375 let model = assert_collection!(@insert, $db, $id $(; $($skip),+)?)
376 .expect("insert");
377
378 assert_eq!(model.id, CollectionId::from_raw($id));
379 assert_eq!(model.title, concat!("C Title ", $id));
380 assert_eq!(model.overview, concat!("C Overview ", $id));
381 };
382 ($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
383 let model = assert_collection!(@insert, $db, $id $(; $($skip),+)?)
384 .expect_err("insert");
385
386 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
387 };
388 (@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
389 super::collections::ActiveModel {
390 id: notsettable!(id, CollectionId::from_raw($id) $(, $($skip),+)?),
391 title: notsettable!(title, concat!("C Title ", $id).to_string() $(, $($skip),+)?),
392 overview: notsettable!(overview, concat!("C Overview ", $id).to_string() $(, $($skip),+)?),
393 sort_title: notsettable!(sort_title, concat!("C Sort Title ", $id).to_string() $(, $($skip),+)?),
394 fs_slug: notsettable!(fs_slug, concat!("C FS Slug ", $id).to_string() $(, $($skip),+)?),
395 web_slug: notsettable!(web_slug, concat!("C Web Slug ", $id).to_string() $(, $($skip),+)?),
396 }.insert($db).await
397 };
398 }
399
400 assert_collection!(&db, 1, Success);
401 assert_collection!(&db, 1, UniqueViolation);
402 assert_collection!(&db, 2, Success);
403
404 assert_collection!(&db, 3, Success; id);
405 assert_collection!(&db, 4, NotNullViolation; title);
406 assert_collection!(&db, 5, NotNullViolation; overview);
407 assert_collection!(&db, 6, NotNullViolation; sort_title);
408 assert_collection!(&db, 7, NotNullViolation; fs_slug);
409 assert_collection!(&db, 8, NotNullViolation; web_slug);
410 }
411
412 #[tokio::test]
413 async fn test_round_trip_movies() {
414 let db = new_initialized_memory_db().await;
415
416 macro_rules! assert_movie {
417 ($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
418 let model = assert_movie!(@insert, $db, $id $(; $($skip),+)?)
419 .expect("insert");
420
421 assert_eq!(model.id, MovieId::from_raw($id));
422 assert_eq!(model.title, concat!("M Title ", $id));
423 assert_eq!(model.tagline, concat!("M Tagline ", $id));
424 assert_eq!(model.overview, concat!("M Overview ", $id));
425 assert_eq!(model.date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt"));
426 };
427 ($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
428 let model = assert_movie!(@insert, $db, $id $(; $($skip),+)?)
429 .expect_err("insert");
430
431 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
432 };
433 (@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
434 super::movies::ActiveModel {
435 id: notsettable!(id, MovieId::from_raw($id) $(, $($skip),+)?),
436 title: notsettable!(title, concat!("M Title ", $id).to_string() $(, $($skip),+)?),
437 tagline: notsettable!(tagline, concat!("M Tagline ", $id).to_string() $(, $($skip),+)?),
438 overview: notsettable!(overview, concat!("M Overview ", $id).to_string() $(, $($skip),+)?),
439 date: notsettable!(date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt") $(, $($skip),+)?),
440 sort_title: notsettable!(sort_title, concat!("M Sort Title ", $id).to_string() $(, $($skip),+)?),
441 fs_slug: notsettable!(fs_slug, concat!("M FS Slug ", $id).to_string() $(, $($skip),+)?),
442 web_slug: notsettable!(web_slug, concat!("M Web Slug ", $id).to_string() $(, $($skip),+)?),
443 }.insert($db).await
444 };
445 }
446
447 assert_movie!(&db, 1, Success);
448 assert_movie!(&db, 1, UniqueViolation);
449 assert_movie!(&db, 2, Success);
450
451 assert_movie!(&db, 3, Success; id);
452 assert_movie!(&db, 4, NotNullViolation; title);
453 assert_movie!(&db, 5, NotNullViolation; tagline);
454 assert_movie!(&db, 6, NotNullViolation; overview);
455 assert_movie!(&db, 7, NotNullViolation; date);
456 assert_movie!(&db, 8, NotNullViolation; sort_title);
457 assert_movie!(&db, 9, NotNullViolation; fs_slug);
458 assert_movie!(&db, 10, NotNullViolation; web_slug);
459 }
460
461 #[tokio::test]
462 async fn test_round_trip_shows() {
463 let db = new_initialized_memory_db().await;
464
465 macro_rules! assert_show {
466 ($db:expr, $id:literal, Success $(; $($skip:ident),+)?) => {
467 let model = assert_show!(@insert, $db, $id $(; $($skip),+)?)
468 .expect("insert");
469
470 assert_eq!(model.id, ShowId::from_raw($id));
471 assert_eq!(model.title, concat!("S Title ", $id));
472 assert_eq!(model.tagline, concat!("S Tagline ", $id));
473 assert_eq!(model.overview, concat!("S Overview ", $id));
474 assert_eq!(model.date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt"));
475 };
476 ($db:expr, $id:literal, $error:ident $(; $($skip:ident),+)?) => {
477 let model = assert_show!(@insert, $db, $id $(; $($skip),+)?)
478 .expect_err("insert");
479
480 assert_eq!(
481 get_error_kind(model).expect("get_error_kind"),
482 ErrorKind::$error
483 );
484 };
485 (@insert, $db:expr, $id:literal $(; $($skip:ident),+)?) => {
486 super::shows::ActiveModel {
487 id: notsettable!(id, ShowId::from_raw($id) $(, $($skip),+)?),
488 title: notsettable!(title, concat!("S Title ", $id).to_string() $(, $($skip),+)?),
489 tagline: notsettable!(tagline, concat!("S Tagline ", $id).to_string() $(, $($skip),+)?),
490 overview: notsettable!(overview, concat!("S Overview ", $id).to_string() $(, $($skip),+)?),
491 date: notsettable!(date, NaiveDate::from_yo_opt($id, 1).expect("from_yo_opt") $(, $($skip),+)?),
492 sort_title: notsettable!(sort_title, concat!("S Sort Title ", $id).to_string() $(, $($skip),+)?),
493 fs_slug: notsettable!(fs_slug, concat!("S FS Slug ", $id).to_string() $(, $($skip),+)?),
494 web_slug: notsettable!(web_slug, concat!("S Web Slug ", $id).to_string() $(, $($skip),+)?),
495 }.insert($db).await
496 };
497 }
498
499 assert_show!(&db, 1, Success);
500 assert_show!(&db, 1, UniqueViolation);
501 assert_show!(&db, 2, Success);
502
503 assert_show!(&db, 3, Success; id);
504 assert_show!(&db, 4, NotNullViolation; title);
505 assert_show!(&db, 5, NotNullViolation; tagline);
506 assert_show!(&db, 6, NotNullViolation; overview);
507 assert_show!(&db, 7, NotNullViolation; date);
508 assert_show!(&db, 8, NotNullViolation; sort_title);
509 assert_show!(&db, 9, NotNullViolation; fs_slug);
510 assert_show!(&db, 10, NotNullViolation; web_slug);
511 }
512
513 #[tokio::test]
514 async fn test_round_trip_seasons() {
515 let db = new_initialized_memory_db().await;
516
517 macro_rules! assert_season {
518 ($db:expr, $show:literal, $season:literal, Success $(; $($skip:ident),+)?) => {
519 let model = assert_season!(@insert, $db, $show, $season $(; $($skip),+)?)
520 .expect("insert");
521
522 assert_eq!(model.show_id, ShowId::from_raw($show));
523 assert_eq!(model.season_number, ::flix_model::numbers::SeasonNumber::new($season));
524 assert_eq!(model.title, concat!("SS Title ", $show, ",", $season));
525 assert_eq!(model.overview, concat!("SS Overview ", $show, ",", $season));
526 assert_eq!(model.date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt"));
527 };
528 ($db:expr, $show:literal, $season:literal, $error:ident $(; $($skip:ident),+)?) => {
529 let model = assert_season!(@insert, $db, $show, $season $(; $($skip),+)?)
530 .expect_err("insert");
531
532 assert_eq!(
533 get_error_kind(model).expect("get_error_kind"),
534 ErrorKind::$error
535 );
536 };
537 (@insert, $db:expr, $show:literal, $season:literal $(; $($skip:ident),+)?) => {
538 super::seasons::ActiveModel {
539 show_id: notsettable!(show_id, ShowId::from_raw($show) $(, $($skip),+)?),
540 season_number: notsettable!(season_number, ::flix_model::numbers::SeasonNumber::new($season) $(, $($skip),+)?),
541 title: notsettable!(title, concat!("SS Title ", $show, ",", $season).to_string() $(, $($skip),+)?),
542 overview: notsettable!(overview, concat!("SS Overview ", $show, ",", $season).to_string() $(, $($skip),+)?),
543 date: notsettable!(date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt") $(, $($skip),+)?),
544 }.insert($db).await
545 };
546 }
547
548 assert_season!(&db, 1, 1, ForeignKeyViolation);
549 make_info_show!(&db, 1);
550 make_info_show!(&db, 2);
551
552 assert_season!(&db, 1, 1, Success);
553 assert_season!(&db, 1, 1, UniqueViolation);
554 assert_season!(&db, 2, 1, Success);
555 assert_season!(&db, 1, 2, Success);
556
557 assert_season!(&db, 1, 3, NotNullViolation; show_id);
558 assert_season!(&db, 1, 4, NotNullViolation; season_number);
559 assert_season!(&db, 1, 5, NotNullViolation; title);
560 assert_season!(&db, 1, 6, NotNullViolation; overview);
561 assert_season!(&db, 1, 7, NotNullViolation; date);
562 }
563
564 #[tokio::test]
565 async fn test_round_trip_episodes() {
566 let db = new_initialized_memory_db().await;
567
568 macro_rules! assert_episode {
569 ($db:expr, $show:literal, $season:literal, $episode:literal, Success $(; $($skip:ident),+)?) => {
570 let model = assert_episode!(@insert, $db, $show, $season, $episode $(; $($skip),+)?)
571 .expect("insert");
572
573 assert_eq!(model.show_id, ShowId::from_raw($show));
574 assert_eq!(model.season_number, ::flix_model::numbers::SeasonNumber::new($season));
575 assert_eq!(model.episode_number, ::flix_model::numbers::EpisodeNumber::new($episode));
576 assert_eq!(model.title, concat!("SSE Title ", $show, ",", $season, ",", $episode));
577 assert_eq!(model.overview, concat!("SSE Overview ", $show, ",", $season, ",", $episode));
578 assert_eq!(model.date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt"));
579 };
580 ($db:expr, $show:literal, $season:literal, $episode:literal, $error:ident $(; $($skip:ident),+)?) => {
581 let model = assert_episode!(@insert, $db, $show, $season, $episode $(; $($skip),+)?)
582 .expect_err("insert");
583
584 assert_eq!(
585 get_error_kind(model).expect("get_error_kind"),
586 ErrorKind::$error
587 );
588 };
589 (@insert, $db:expr, $show:literal, $season:literal, $episode:literal $(; $($skip:ident),+)?) => {
590 super::episodes::ActiveModel {
591 show_id: notsettable!(show_id, ShowId::from_raw($show) $(, $($skip),+)?),
592 season_number: notsettable!(season_number, ::flix_model::numbers::SeasonNumber::new($season) $(, $($skip),+)?),
593 episode_number: notsettable!(episode_number, ::flix_model::numbers::EpisodeNumber::new($episode) $(, $($skip),+)?),
594 title: notsettable!(title, concat!("SSE Title ", $show, ",", $season, ",", $episode).to_string() $(, $($skip),+)?),
595 overview: notsettable!(overview, concat!("SSE Overview ", $show, ",", $season, ",", $episode).to_string() $(, $($skip),+)?),
596 date: notsettable!(date, NaiveDate::from_yo_opt($show + $season, 1).expect("from_yo_opt") $(, $($skip),+)?),
597 }.insert($db).await
598 };
599 }
600
601 assert_episode!(&db, 1, 1, 1, ForeignKeyViolation);
602 make_info_show!(&db, 1);
603 make_info_show!(&db, 2);
604 assert_episode!(&db, 1, 1, 1, ForeignKeyViolation);
605 make_info_season!(&db, 1, 1);
606 make_info_season!(&db, 1, 2);
607 make_info_season!(&db, 2, 1);
608
609 assert_episode!(&db, 1, 1, 1, Success);
610 assert_episode!(&db, 1, 1, 1, UniqueViolation);
611 assert_episode!(&db, 2, 1, 1, Success);
612 assert_episode!(&db, 1, 2, 1, Success);
613 assert_episode!(&db, 1, 1, 2, Success);
614
615 assert_episode!(&db, 1, 1, 3, NotNullViolation; show_id);
616 assert_episode!(&db, 1, 1, 4, NotNullViolation; season_number);
617 assert_episode!(&db, 1, 1, 4, NotNullViolation; episode_number);
618 assert_episode!(&db, 1, 1, 5, NotNullViolation; title);
619 assert_episode!(&db, 1, 1, 6, NotNullViolation; overview);
620 assert_episode!(&db, 1, 1, 7, NotNullViolation; date);
621 }
622}