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