diff --git a/api/src/models/entry/movie-entry.ts b/api/src/models/entry/movie-entry.ts index befda2d0..9432b483 100644 --- a/api/src/models/entry/movie-entry.ts +++ b/api/src/models/entry/movie-entry.ts @@ -46,7 +46,7 @@ export const MovieEntry = t.Composite([ MovieEntryTranslation, BaseMovieEntry, t.Object({ - videos: t.Optional(t.Array(EmbeddedVideo)), + videos: t.Array(EmbeddedVideo), progress: Progress, }), DbMetadata, diff --git a/front/src/models/entry.ts b/front/src/models/entry.ts index 07ea0071..e97462f0 100644 --- a/front/src/models/entry.ts +++ b/front/src/models/entry.ts @@ -1,7 +1,82 @@ import { z } from "zod/v4"; +import { KImage } from "./utils/images"; +import { Metadata } from "./utils/metadata"; +import { zdate } from "./utils/utils"; -export const Entry = z.object({ +const Base = z.object({ id: z.string(), slug: z.string(), + order: z.number(), + + name: z.string().nullable(), + description: z.string().nullable(), + airDate: zdate().nullable(), + runtime: z.number().nullable(), + thumbnail: KImage.nullable(), + + createdAt: zdate(), + updatedAt: zdate(), + + videos: z.array( + z.object({ + id: z.string(), + slug: z.string(), + path: z.string(), + rendering: z.string(), + part: z.int().nullable(), + version: z.int(), + }), + ), + progress: z.object({ + percent: z.int().min(0).max(100), + time: z.int().min(0).nullable(), + playedDate: zdate().nullable(), + videoId: z.string().nullable(), + }), }); + +export const Episode = Base.extend({ + kind: z.literal("episode"), + seasonNumber: z.int().gte(0), + episodeNumber: z.int().gte(0), + externalId: z.record( + z.string(), + z.object({ + serieId: z.string(), + season: z.int().nullable(), + episode: z.int(), + link: z.string().nullable(), + }), + ), +}); +export type Episode = z.infer; + +export const MovieEntry = Base.extend({ + kind: z.literal("movie"), + tagline: z.string().nullable(), + poster: KImage.nullable(), + externalId: Metadata, +}); +export type MovieEntry = z.infer; + +export const Special = Base.extend({ + kind: z.literal("special"), + number: z.int(), + externalId: z.record( + z.string(), + z.object({ + serieId: z.string(), + season: z.int().nullable(), + episode: z.int(), + link: z.string().nullable(), + }), + ), +}); +export type Special = z.infer; + +export const Entry = z.discriminatedUnion("kind", [ + Episode, + MovieEntry, + Special, +]); export type Entry = z.infer; diff --git a/front/src/models/extra.ts b/front/src/models/extra.ts new file mode 100644 index 00000000..1c2d69dd --- /dev/null +++ b/front/src/models/extra.ts @@ -0,0 +1,29 @@ +import { z } from "zod/v4"; +import { KImage } from "./utils/images"; +import { zdate } from "./utils/utils"; + +export const Extra = z.object({ + kind: z.enum([ + "other", + "trailer", + "interview", + "behind-the-scene", + "deleted-scene", + "blooper", + ]), + id: z.string(), + slug: z.string(), + name: z.string(), + runtime: z.number().nullable(), + thumbnail: KImage.nullable(), + + createdAt: zdate(), + updatedAt: zdate(), + + progress: z.object({ + percent: z.int().min(0).max(100), + time: z.int().min(0).nullable(), + playedDate: zdate().nullable(), + }), +}); +export type Extra = z.infer; diff --git a/front/src/models/index.ts b/front/src/models/index.ts index b9ed0e8f..0422787f 100644 --- a/front/src/models/index.ts +++ b/front/src/models/index.ts @@ -1,5 +1,6 @@ export * from "./collection"; export * from "./entry"; +export * from "./extra"; export * from "./kyoo-error"; export * from "./movie"; export * from "./serie"; diff --git a/front/src/models/resources/episode.base.ts b/front/src/models/resources/episode.base.ts deleted file mode 100644 index 117ba2e6..00000000 --- a/front/src/models/resources/episode.base.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { z } from "zod"; -import { ImagesP, ResourceP } from "../traits"; -import { zdate } from "../utils"; - -export const BaseEpisodeP = ResourceP("episode") - .merge(ImagesP) - .extend({ - /** - * The season in witch this episode is in. - */ - seasonNumber: z.number().nullable(), - /** - * The number of this episode in it's season. - */ - episodeNumber: z.number().nullable(), - /** - * The absolute number of this episode. It's an episode number that is not reset to 1 after a new - * season. - */ - absoluteNumber: z.number().nullable(), - /** - * The title of this episode. - */ - name: z.string().nullable(), - /** - * The overview of this episode. - */ - overview: z.string().nullable(), - /** - * How long is this movie? (in minutes). - */ - runtime: z.number().int().nullable(), - /** - * The release date of this episode. It can be null if unknown. - */ - releaseDate: zdate().nullable(), - /** - * The links to see a movie or an episode. - */ - links: z.object({ - /** - * The direct link to the unprocessed video (pristine quality). - */ - direct: z.string(), - /** - * The link to an HLS master playlist containing all qualities available for this video. - */ - hls: z.string().nullable(), - }), - /** - * The id of the show containing this episode - */ - showId: z.string(), - }) - .transform((x) => ({ - ...x, - runtime: x.runtime === 0 ? null : x.runtime, - })) - .transform((x) => ({ - ...x, - href: `/watch/${x.slug}`, - })); diff --git a/front/src/models/resources/episode.ts b/front/src/models/resources/episode.ts deleted file mode 100644 index f9570990..00000000 --- a/front/src/models/resources/episode.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import { BaseEpisodeP } from "./episode.base"; -import { ShowP } from "./show"; -import { WatchStatusP } from "./watch-status"; - -export const EpisodeP = BaseEpisodeP.and( - z.object({ - /** - * The episode that come before this one if you follow usual watch orders. If this is the first - * episode, it will be null. - */ - previousEpisode: BaseEpisodeP.nullable().optional(), - /** - * The episode that come after this one if you follow usual watch orders. If this is the last - * aired episode, it will be null. - */ - nextEpisode: BaseEpisodeP.nullable().optional(), - - show: ShowP.optional(), - /** - * Metadata of what an user as started/planned to watch. - */ - watchStatus: WatchStatusP.optional().nullable(), - }), -).transform((x) => { - if (x.show && !x.thumbnail && x.show.thumbnail) - x.thumbnail = x.show.thumbnail; - return x; -}); - -/** - * A class to represent a single show's episode. - */ -export type Episode = z.infer; diff --git a/front/src/models/resources/news.ts b/front/src/models/resources/news.ts deleted file mode 100644 index cb7f4cc5..00000000 --- a/front/src/models/resources/news.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from "zod"; -import { EpisodeP } from "./episode"; -import { MovieP } from "./movie"; - -export const NewsP = z.union([ - /* - * Either an episode - */ - EpisodeP, - /* - * Or a Movie - */ - MovieP, -]); - -/** - * A new item added to kyoo. - */ -export type News = z.infer; diff --git a/front/src/models/resources/watchlist.ts b/front/src/models/resources/watchlist.ts deleted file mode 100644 index 550cf01f..00000000 --- a/front/src/models/resources/watchlist.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from "zod"; -import { MovieP } from "./movie"; -import { ShowP } from "./show"; - -export const WatchlistP = z.union([ - /* - * Either a show - */ - ShowP, - /* - * Or a Movie - */ - MovieP, -]); - -/** - * A item in the user's watchlist. - */ -export type Watchlist = z.infer; diff --git a/front/src/models/season.ts b/front/src/models/season.ts index f945ebc9..a3cf5962 100644 --- a/front/src/models/season.ts +++ b/front/src/models/season.ts @@ -5,10 +5,11 @@ import { zdate } from "./utils/utils"; export const Season = z.object({ id: z.string(), slug: z.string(), - seasonNumber: z.number().gte(0), + seasonNumber: z.int().gte(0), name: z.string().nullable(), description: z.string().nullable(), - entryCount: z.number(), + entryCount: z.int().gte(0), + availableCount: z.int().gte(0), startAir: zdate().nullable(), endAir: zdate().nullable(), externalId: z.record( diff --git a/front/src/models/serie.ts b/front/src/models/serie.ts index bc32f8b8..f947388d 100644 --- a/front/src/models/serie.ts +++ b/front/src/models/serie.ts @@ -34,8 +34,8 @@ export const Serie = z logo: KImage.nullable(), trailerUrl: z.string().nullable(), - entriesCount: z.number().int(), - availableCount: z.number().int(), + entriesCount: z.int().gte(0), + availableCount: z.int().gte(0), createdAt: zdate(), updatedAt: zdate(),