1pub mod collections {
5 use flix_model::id::{CollectionId, RawId};
6
7 use chrono::NaiveDate;
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_watched_collections")]
16 pub struct Model {
17 #[sea_orm(primary_key, auto_increment = false)]
19 pub id: CollectionId,
20 #[sea_orm(primary_key, auto_increment = false)]
22 pub user_id: RawId,
23 pub watched_date: NaiveDate,
25
26 #[sea_orm(belongs_to, relation_enum = "Info", from = "id", to = "id")]
28 pub info: HasOne<entity::info::collections::Entity>,
29 #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
31 pub content: HasOne<entity::content::collections::Entity>,
32 }
33
34 impl ActiveModelBehavior for ActiveModel {}
35}
36
37pub mod movies {
39 use flix_model::id::{MovieId, RawId};
40
41 use chrono::NaiveDate;
42 use sea_orm::entity::prelude::*;
43
44 use crate::entity;
45
46 #[sea_orm::model]
48 #[derive(Debug, Clone, DeriveEntityModel)]
49 #[sea_orm(table_name = "flix_watched_movies")]
50 pub struct Model {
51 #[sea_orm(primary_key, auto_increment = false)]
53 pub id: MovieId,
54 #[sea_orm(primary_key, auto_increment = false)]
56 pub user_id: RawId,
57 pub watched_date: NaiveDate,
59
60 #[sea_orm(belongs_to, from = "id", to = "id")]
62 pub info: HasOne<entity::info::movies::Entity>,
63 #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
65 pub content: HasOne<entity::content::movies::Entity>,
66 }
67
68 impl ActiveModelBehavior for ActiveModel {}
69}
70
71pub mod shows {
73 use flix_model::id::{RawId, ShowId};
74
75 use chrono::NaiveDate;
76 use sea_orm::entity::prelude::*;
77
78 use crate::entity;
79
80 #[sea_orm::model]
82 #[derive(Debug, Clone, DeriveEntityModel)]
83 #[sea_orm(table_name = "flix_watched_shows")]
84 pub struct Model {
85 #[sea_orm(primary_key, auto_increment = false)]
87 pub id: ShowId,
88 #[sea_orm(primary_key, auto_increment = false)]
90 pub user_id: RawId,
91 pub watched_date: NaiveDate,
93
94 #[sea_orm(belongs_to, relation_enum = "Info", from = "id", to = "id")]
96 pub info: HasOne<entity::info::shows::Entity>,
97 #[sea_orm(belongs_to, relation_enum = "Content", from = "id", to = "id", skip_fk)]
99 pub content: HasOne<entity::content::shows::Entity>,
100 }
101
102 impl ActiveModelBehavior for ActiveModel {}
103}
104
105pub mod seasons {
107 use flix_model::id::{RawId, ShowId};
108 use flix_model::numbers::SeasonNumber;
109
110 use chrono::NaiveDate;
111 use sea_orm::entity::prelude::*;
112
113 use crate::entity;
114
115 #[sea_orm::model]
117 #[derive(Debug, Clone, DeriveEntityModel)]
118 #[sea_orm(table_name = "flix_watched_seasons")]
119 pub struct Model {
120 #[sea_orm(primary_key, auto_increment = false)]
122 pub show_id: ShowId,
123 #[sea_orm(primary_key, auto_increment = false)]
125 pub season_number: SeasonNumber,
126 #[sea_orm(primary_key, auto_increment = false)]
128 pub user_id: RawId,
129 pub watched_date: NaiveDate,
131
132 #[sea_orm(
134 belongs_to,
135 relation_enum = "Info",
136 from = "(show_id, season_number)",
137 to = "(show_id, season_number)"
138 )]
139 pub info: HasOne<entity::info::seasons::Entity>,
140 #[sea_orm(
142 belongs_to,
143 relation_enum = "Content",
144 from = "(show_id, season_number)",
145 to = "(show_id, season_number)",
146 skip_fk
147 )]
148 pub content: HasOne<entity::content::seasons::Entity>,
149 }
150
151 impl ActiveModelBehavior for ActiveModel {}
152}
153
154pub mod episodes {
156 use flix_model::id::{RawId, ShowId};
157 use flix_model::numbers::{EpisodeNumber, SeasonNumber};
158
159 use chrono::NaiveDate;
160 use sea_orm::entity::prelude::*;
161
162 use crate::entity;
163
164 #[sea_orm::model]
166 #[derive(Debug, Clone, DeriveEntityModel)]
167 #[sea_orm(table_name = "flix_watched_episodes")]
168 pub struct Model {
169 #[sea_orm(primary_key, auto_increment = false)]
171 pub show_id: ShowId,
172 #[sea_orm(primary_key, auto_increment = false)]
174 pub season_number: SeasonNumber,
175 #[sea_orm(primary_key, auto_increment = false)]
177 pub episode_number: EpisodeNumber,
178 #[sea_orm(primary_key, auto_increment = false)]
180 pub user_id: RawId,
181 pub watched_date: NaiveDate,
183
184 #[sea_orm(
186 belongs_to,
187 relation_enum = "Info",
188 from = "(show_id, season_number, episode_number)",
189 to = "(show_id, season_number, episode_number)"
190 )]
191 pub info: HasOne<entity::info::episodes::Entity>,
192 #[sea_orm(
194 belongs_to,
195 relation_enum = "Content",
196 from = "(show_id, season_number, episode_number)",
197 to = "(show_id, season_number, episode_number)",
198 skip_fk
199 )]
200 pub content: HasOne<entity::content::episodes::Entity>,
201 }
202
203 impl ActiveModelBehavior for ActiveModel {}
204}
205
206#[cfg(test)]
208pub mod test {
209 macro_rules! make_watched_movie {
210 ($db:expr, $id:literal, $user:literal) => {
211 $crate::entity::watched::movies::ActiveModel {
212 id: Set(::flix_model::id::MovieId::from_raw($id)),
213 user_id: Set($user),
214 watched_date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
215 }
216 .insert($db)
217 .await
218 .expect("insert");
219 };
220 }
221 pub(crate) use make_watched_movie;
222
223 macro_rules! make_watched_episode {
224 ($db:expr, $show:literal, $season:literal, $episode:literal, $user:literal) => {
225 $crate::entity::watched::episodes::ActiveModel {
226 show_id: Set(::flix_model::id::ShowId::from_raw($show)),
227 season_number: Set($season),
228 episode_number: Set($episode),
229 user_id: Set($user),
230 watched_date: Set(::chrono::NaiveDate::from_yo_opt(1, 1).expect("from_yo_opt")),
231 }
232 .insert($db)
233 .await
234 .expect("insert");
235 };
236 }
237 pub(crate) use make_watched_episode;
238}
239
240#[cfg(test)]
241mod tests {
242 use flix_model::id::{MovieId, ShowId};
243
244 use chrono::NaiveDate;
245 use sea_orm::ActiveValue::{NotSet, Set};
246 use sea_orm::Condition;
247 use sea_orm::entity::prelude::*;
248 use sea_orm::sqlx::error::ErrorKind;
249
250 use crate::entity::content::test::{
251 make_content_collection, make_content_episode, make_content_library, make_content_movie,
252 make_content_season, make_content_show,
253 };
254 use crate::entity::info::test::{
255 make_info_episode, make_info_movie, make_info_season, make_info_show,
256 };
257 use crate::tests::new_initialized_memory_db;
258
259 use super::super::tests::get_error_kind;
260 use super::super::tests::notsettable;
261 use super::test::{make_watched_episode, make_watched_movie};
262
263 #[tokio::test]
264 async fn use_test_macros() {
265 let db = new_initialized_memory_db().await;
266
267 make_info_movie!(&db, 1);
268 make_info_show!(&db, 1);
269 make_info_season!(&db, 1, 1);
270 make_info_episode!(&db, 1, 1, 1);
271
272 make_watched_movie!(&db, 1, 1);
273 make_watched_episode!(&db, 1, 1, 1, 1);
274 }
275
276 #[tokio::test]
277 async fn test_round_trip_movies() {
278 let db = new_initialized_memory_db().await;
279
280 macro_rules! assert_movie {
281 ($db:expr, $id:literal, $uid:literal, Success $(; $($skip:ident),+)?) => {
282 let model = assert_movie!(@insert, $db, $id, $uid $(; $($skip),+)?)
283 .expect("insert");
284
285 assert_eq!(model.id, MovieId::from_raw($id));
286 assert_eq!(model.user_id, $uid);
287 assert_eq!(model.watched_date, NaiveDate::from_yo_opt($uid, 1).expect("from_yo_opt"));
288 };
289 ($db:expr, $id:literal, $uid:literal, $error:ident $(; $($skip:ident),+)?) => {
290 let model = assert_movie!(@insert, $db, $id, $uid $(; $($skip),+)?)
291 .expect_err("insert");
292
293 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
294 };
295 (@insert, $db:expr, $id:literal, $uid:literal $(; $($skip:ident),+)?) => {
296 super::movies::ActiveModel {
297 id: notsettable!(id, MovieId::from_raw($id) $(, $($skip),+)?),
298 user_id: notsettable!(user_id, $uid $(, $($skip),+)?),
299 watched_date: notsettable!(watched_date, NaiveDate::from_yo_opt($uid, 1).expect("from_yo_opt") $(, $($skip),+)?),
300 }.insert($db).await
301 };
302 }
303
304 assert_movie!(&db, 1, 1, ForeignKeyViolation);
305 make_info_movie!(&db, 1);
306 assert_movie!(&db, 1, 1, Success);
307 assert_movie!(&db, 1, 2, Success);
308
309 assert_movie!(&db, 1, 1, UniqueViolation);
310 make_info_movie!(&db, 2);
311 assert_movie!(&db, 2, 1, Success);
312 assert_movie!(&db, 2, 2, Success);
313
314 assert_movie!(&db, 3, 1, NotNullViolation; id);
315 assert_movie!(&db, 4, 1, NotNullViolation; user_id);
316 assert_movie!(&db, 5, 1, NotNullViolation; watched_date);
317 }
318
319 #[tokio::test]
320 async fn test_round_trip_episodes() {
321 let db = new_initialized_memory_db().await;
322
323 macro_rules! assert_episode {
324 ($db:expr, $show:literal, $season:literal, $episode:literal, $uid:literal, Success $(; $($skip:ident),+)?) => {
325 let model = assert_episode!(@insert, $db, $show, $season, $episode, $uid $(; $($skip),+)?)
326 .expect("insert");
327
328 assert_eq!(model.show_id, ShowId::from_raw($show));
329 assert_eq!(model.season_number, $season);
330 assert_eq!(model.episode_number, $episode);
331 assert_eq!(model.user_id, $uid);
332 assert_eq!(model.watched_date, NaiveDate::from_yo_opt($uid, 1).expect("from_yo_opt"));
333 };
334 ($db:expr, $show:literal, $season:literal, $episode:literal, $uid:literal, $error:ident $(; $($skip:ident),+)?) => {
335 let model = assert_episode!(@insert, $db, $show, $season, $episode, $uid $(; $($skip),+)?)
336 .expect_err("insert");
337
338 assert_eq!(get_error_kind(model).expect("get_error_kind"), ErrorKind::$error);
339 };
340 (@insert, $db:expr, $show:literal, $season:literal, $episode:literal, $uid:literal $(; $($skip:ident),+)?) => {
341 super::episodes::ActiveModel {
342 show_id: notsettable!(show_id, ShowId::from_raw($show) $(, $($skip),+)?),
343 season_number: notsettable!(season_number, $season $(, $($skip),+)?),
344 episode_number: notsettable!(episode_number, $episode $(, $($skip),+)?),
345 user_id: notsettable!(user_id, $uid $(, $($skip),+)?),
346 watched_date: notsettable!(watched_date, NaiveDate::from_yo_opt($uid, 1).expect("from_yo_opt") $(, $($skip),+)?),
347 }.insert($db).await
348 };
349 }
350
351 make_info_show!(&db, 1);
352 make_info_season!(&db, 1, 1);
353 make_info_show!(&db, 2);
354 make_info_season!(&db, 2, 1);
355
356 assert_episode!(&db, 1, 1, 1, 1, ForeignKeyViolation);
357 assert_episode!(&db, 2, 1, 1, 1, ForeignKeyViolation);
358 make_info_episode!(&db, 1, 1, 1);
359 make_info_episode!(&db, 2, 1, 1);
360 assert_episode!(&db, 1, 1, 1, 1, Success);
361 assert_episode!(&db, 1, 1, 1, 2, Success);
362 assert_episode!(&db, 2, 1, 1, 1, Success);
363
364 assert_episode!(&db, 1, 1, 1, 1, UniqueViolation);
365
366 assert_episode!(&db, 3, 1, 1, 1, NotNullViolation; show_id);
367 assert_episode!(&db, 4, 1, 1, 1, NotNullViolation; season_number);
368 assert_episode!(&db, 5, 1, 1, 1, NotNullViolation; episode_number);
369 assert_episode!(&db, 6, 1, 1, 1, NotNullViolation; user_id);
370 assert_episode!(&db, 7, 1, 1, 1, NotNullViolation; watched_date);
371 }
372
373 #[tokio::test]
374 async fn test_query_seasons() {
375 let db = new_initialized_memory_db().await;
376
377 macro_rules! assert_season {
378 ($db:expr, $show:literal, $season:literal, $uid:literal, Watched) => {
379 assert_season!(@find, $db, $show, $season, $uid)
380 .ok_or(())
381 .expect("is none");
382 };
383 ($db:expr, $show:literal, $season:literal, $uid:literal, Unwatched) => {
384 assert_season!(@find, $db, $show, $season, $uid)
385 .ok_or(())
386 .expect_err("is some");
387 };
388 (@find, $db:expr, $show:literal, $season:literal, $uid:literal) => {
389 super::seasons::Entity::find()
390 .filter(
391 Condition::all()
392 .add(super::seasons::Column::ShowId.eq($show))
393 .add(super::seasons::Column::SeasonNumber.eq($season))
394 .add(super::seasons::Column::UserId.eq($uid)),
395 )
396 .one(&db)
397 .await
398 .expect("find.filter.one")
399 };
400 }
401
402 make_content_library!(&db, 1);
403 make_content_show!(&db, 1, 1, None);
404
405 make_content_season!(&db, 1, 1, 1);
406 assert_season!(&db, 1, 1, 1, Unwatched);
407 make_content_episode!(&db, 1, 1, 1, 1);
408 assert_season!(&db, 1, 1, 1, Unwatched);
409 make_watched_episode!(&db, 1, 1, 1, 1);
410 assert_season!(&db, 1, 1, 1, Watched);
411 assert_season!(&db, 1, 1, 2, Unwatched);
412 make_content_episode!(&db, 1, 1, 1, 2);
413 assert_season!(&db, 1, 1, 1, Unwatched);
414
415 make_content_season!(&db, 1, 1, 2);
416 make_content_episode!(&db, 1, 1, 2, 1);
417 make_content_episode!(&db, 1, 1, 2, 2, >1);
418 make_info_episode!(&db, 1, 2, 3);
419 make_content_episode!(&db, 1, 1, 2, 4);
420 assert_season!(&db, 1, 2, 1, Unwatched);
421 assert_season!(&db, 1, 2, 2, Unwatched);
422 assert_season!(&db, 1, 2, 3, Unwatched);
423 make_watched_episode!(&db, 1, 2, 1, 1);
424 make_watched_episode!(&db, 1, 2, 1, 2);
425 make_watched_episode!(&db, 1, 2, 2, 3);
426 assert_season!(&db, 1, 2, 1, Unwatched);
427 assert_season!(&db, 1, 2, 2, Unwatched);
428 assert_season!(&db, 1, 2, 3, Unwatched);
429 make_watched_episode!(&db, 1, 2, 2, 1);
430 make_watched_episode!(&db, 1, 2, 2, 2);
431 make_watched_episode!(&db, 1, 2, 1, 3);
432 assert_season!(&db, 1, 2, 1, Unwatched);
433 assert_season!(&db, 1, 2, 2, Unwatched);
434 assert_season!(&db, 1, 2, 3, Unwatched);
435 make_watched_episode!(&db, 1, 2, 4, 1);
436 assert_season!(&db, 1, 2, 1, Watched);
437 assert_season!(&db, 1, 2, 2, Unwatched);
438 assert_season!(&db, 1, 2, 3, Unwatched);
439 }
440
441 #[tokio::test]
442 async fn test_query_shows() {
443 let db = new_initialized_memory_db().await;
444
445 macro_rules! assert_show {
446 ($db:expr, $show:literal, $uid:literal, Watched) => {
447 assert_show!(@find, $db, $show, $uid)
448 .ok_or(())
449 .expect("is none");
450 };
451 ($db:expr, $show:literal, $uid:literal, Unwatched) => {
452 assert_show!(@find, $db, $show, $uid)
453 .ok_or(())
454 .expect_err("is some");
455 };
456 (@find, $db:expr, $show:literal, $uid:literal) => {
457 super::shows::Entity::find()
458 .filter(
459 Condition::all()
460 .add(super::shows::Column::Id.eq($show))
461 .add(super::shows::Column::UserId.eq($uid)),
462 )
463 .one(&db)
464 .await
465 .expect("find.filter.one")
466 };
467 }
468
469 make_content_library!(&db, 1);
470 make_content_show!(&db, 1, 1, None);
471
472 assert_show!(&db, 1, 1, Unwatched);
473 make_content_season!(&db, 1, 1, 1);
474 assert_show!(&db, 1, 1, Unwatched);
475 make_content_episode!(&db, 1, 1, 1, 1);
476 assert_show!(&db, 1, 1, Unwatched);
477 make_watched_episode!(&db, 1, 1, 1, 1);
478 assert_show!(&db, 1, 1, Watched);
479 assert_show!(&db, 1, 2, Unwatched);
480 make_content_episode!(&db, 1, 1, 1, 2);
481 assert_show!(&db, 1, 1, Unwatched);
482 assert_show!(&db, 1, 2, Unwatched);
483 make_watched_episode!(&db, 1, 1, 2, 1);
484
485 make_content_season!(&db, 1, 1, 2);
486 assert_show!(&db, 1, 1, Unwatched);
487 assert_show!(&db, 1, 2, Unwatched);
488 make_content_episode!(&db, 1, 1, 2, 1);
489 assert_show!(&db, 1, 1, Unwatched);
490 assert_show!(&db, 1, 2, Unwatched);
491 make_watched_episode!(&db, 1, 2, 1, 1);
492 assert_show!(&db, 1, 1, Watched);
493 assert_show!(&db, 1, 2, Unwatched);
494 }
495
496 #[tokio::test]
497 async fn test_query_collections() {
498 let db = new_initialized_memory_db().await;
499
500 macro_rules! assert_collection {
501 ($db:expr, $id:literal, $uid:literal, Watched) => {
502 assert_collection!(@find, $db, $id, $uid)
503 .ok_or(())
504 .expect("is none");
505 };
506 ($db:expr, $id:literal, $uid:literal, Unwatched) => {
507 assert_collection!(@find, $db, $id, $uid)
508 .ok_or(())
509 .expect_err("is some");
510 };
511 (@find, $db:expr, $id:literal, $uid:literal) => {
512 super::collections::Entity::find()
513 .filter(
514 Condition::all()
515 .add(super::collections::Column::Id.eq($id))
516 .add(super::collections::Column::UserId.eq($uid)),
517 )
518 .one(&db)
519 .await
520 .expect("find.filter.one")
521 };
522 }
523
524 make_content_library!(&db, 1);
525 make_content_collection!(&db, 1, 1, None);
526 assert_collection!(&db, 1, 1, Unwatched);
527
528 make_content_movie!(&db, 1, 1, Some(1));
529 assert_collection!(&db, 1, 1, Unwatched);
530 make_info_movie!(&db, 9999);
531 make_watched_movie!(&db, 9999, 1);
532 assert_collection!(&db, 1, 1, Unwatched);
533 make_watched_movie!(&db, 1, 1);
534 assert_collection!(&db, 1, 1, Watched);
535
536 make_content_collection!(&db, 1, 2, Some(1));
537 assert_collection!(&db, 1, 1, Watched);
538 assert_collection!(&db, 2, 1, Unwatched);
539 make_content_movie!(&db, 1, 2, Some(2));
540 assert_collection!(&db, 1, 1, Unwatched);
541 assert_collection!(&db, 2, 1, Unwatched);
542 make_watched_movie!(&db, 2, 1);
543 assert_collection!(&db, 1, 1, Watched);
544 assert_collection!(&db, 2, 1, Watched);
545
546 make_content_show!(&db, 1, 1, Some(2));
547 assert_collection!(&db, 1, 1, Unwatched);
548 assert_collection!(&db, 2, 1, Unwatched);
549 make_content_season!(&db, 1, 1, 1);
550 make_content_episode!(&db, 1, 1, 1, 1);
551 make_watched_episode!(&db, 1, 1, 1, 1);
552 assert_collection!(&db, 1, 1, Watched);
553 assert_collection!(&db, 2, 1, Watched);
554 }
555}