diff --git a/projects/api/src/contracts/_internal/response/progressResponseSchema.ts b/projects/api/src/contracts/_internal/response/progressResponseSchema.ts index ea12dcbb..04bc7382 100644 --- a/projects/api/src/contracts/_internal/response/progressResponseSchema.ts +++ b/projects/api/src/contracts/_internal/response/progressResponseSchema.ts @@ -7,7 +7,7 @@ export const progressResponseSchema = z.object({ completed: z.number().int(), last_watched_at: z.string().datetime().nullish(), reset_at: z.null(), - next_episode: episodeResponseSchema, + next_episode: episodeResponseSchema.or(z.null()), last_episode: episodeResponseSchema.or(z.null()), /*** * Available if requesting include_stats `true`. diff --git a/projects/api/src/contracts/scrobble/index.ts b/projects/api/src/contracts/scrobble/index.ts index da4a8182..5d768ea9 100644 --- a/projects/api/src/contracts/scrobble/index.ts +++ b/projects/api/src/contracts/scrobble/index.ts @@ -1,70 +1,36 @@ import { authMetadata, builder } from '../_internal/builder.ts'; import { extendedQuerySchemaFactory } from '../_internal/request/extendedQuerySchemaFactory.ts'; -import type { z } from '../_internal/z.ts'; +import { z } from '../_internal/z.ts'; import { episodeScrobbleRequestSchema } from './schema/request/episodeScrobbleRequestSchema.ts'; import { movieScrobbleRequestSchema } from './schema/request/movieScrobbleRequestSchema.ts'; import { episodeScrobbleResponseSchema } from './schema/response/episodeScrobbleResponseSchema.ts'; import { movieScrobbleResponseSchema } from './schema/response/movieScrobbleResponseSchema.ts'; export const scrobble = builder.router({ - movie: builder.router({ - start: { - path: '/start', - method: 'POST', - body: movieScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: movieScrobbleResponseSchema, - }, + start: { + path: '/start', + method: 'POST', + body: z.union([movieScrobbleRequestSchema, episodeScrobbleRequestSchema]), + responses: { + 201: z.union([movieScrobbleResponseSchema, episodeScrobbleResponseSchema]), }, - pause: { - path: '/pause', - method: 'POST', - body: movieScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: movieScrobbleResponseSchema, - }, + }, + pause: { + path: '/pause', + method: 'POST', + body: z.union([movieScrobbleRequestSchema, episodeScrobbleRequestSchema]), + responses: { + 201: z.union([movieScrobbleResponseSchema, episodeScrobbleResponseSchema]), }, - stop: { - path: '/stop', - method: 'POST', - body: movieScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: movieScrobbleResponseSchema, - }, + }, + stop: { + path: '/stop', + method: 'POST', + body: z.union([movieScrobbleRequestSchema, episodeScrobbleRequestSchema]), + responses: { + 201: z.union([movieScrobbleResponseSchema, episodeScrobbleResponseSchema]), }, - }), - episode: builder.router({ - start: { - path: '/start', - method: 'POST', - body: episodeScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: episodeScrobbleResponseSchema, - }, - }, - pause: { - path: '/pause', - method: 'POST', - body: episodeScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: episodeScrobbleResponseSchema, - }, - }, - stop: { - path: '/stop', - method: 'POST', - body: episodeScrobbleRequestSchema, - query: extendedQuerySchemaFactory<['full', 'images']>(), - responses: { - 201: episodeScrobbleResponseSchema, - }, - }, - }), + }, }, { pathPrefix: '/scrobble', metadata: authMetadata('required'), @@ -78,10 +44,6 @@ export { }; export type MovieScrobbleRequest = z.infer; -export type MovieScrobbleResponse = z.infer; -export type EpisodeScrobbleRequest = z.infer< - typeof episodeScrobbleRequestSchema ->; -export type EpisodeScrobbleResponse = z.infer< - typeof episodeScrobbleRequestSchema ->; +export type MovieScrobbleResponse = z.infer; +export type EpisodeScrobbleRequest = z.infer; +export type EpisodeScrobbleResponse = z.infer; diff --git a/projects/api/src/contracts/scrobble/schema/request/episodeScrobbleRequestSchema.ts b/projects/api/src/contracts/scrobble/schema/request/episodeScrobbleRequestSchema.ts index de127f5d..ac9b6f15 100644 --- a/projects/api/src/contracts/scrobble/schema/request/episodeScrobbleRequestSchema.ts +++ b/projects/api/src/contracts/scrobble/schema/request/episodeScrobbleRequestSchema.ts @@ -1,11 +1,11 @@ import { episodeIdsRequestSchema, } from '../../../_internal/request/idsRequestSchema.ts'; -import { z } from '../../../_internal/z.ts'; +import {float, z} from '../../../_internal/z.ts'; export const episodeScrobbleRequestSchema = z.object({ - progress: z.number().int(), + progress: float(z.number()), episode: z.object({ ids: episodeIdsRequestSchema, - }), + }).nullish(), }); diff --git a/projects/api/src/contracts/scrobble/schema/request/movieScrobbleRequestSchema.ts b/projects/api/src/contracts/scrobble/schema/request/movieScrobbleRequestSchema.ts index 3e0a0fde..3305bd22 100644 --- a/projects/api/src/contracts/scrobble/schema/request/movieScrobbleRequestSchema.ts +++ b/projects/api/src/contracts/scrobble/schema/request/movieScrobbleRequestSchema.ts @@ -1,7 +1,7 @@ import { movieIdsRequestSchema, } from '../../../_internal/request/idsRequestSchema.ts'; -import { z } from '../../../_internal/z.ts'; +import {float, z} from '../../../_internal/z.ts'; /* FIXME: verify data structure of the standard media @@ -9,10 +9,10 @@ import { z } from '../../../_internal/z.ts'; */ export const movieScrobbleRequestSchema = z.object({ - progress: z.number().int(), + progress: float(z.number()), movie: z.object({ title: z.string(), year: z.number().int(), ids: movieIdsRequestSchema, - }), + }).nullish(), }); diff --git a/projects/api/src/contracts/scrobble/schema/response/episodeScrobbleResponseSchema.ts b/projects/api/src/contracts/scrobble/schema/response/episodeScrobbleResponseSchema.ts index 698173c3..e268aac0 100644 --- a/projects/api/src/contracts/scrobble/schema/response/episodeScrobbleResponseSchema.ts +++ b/projects/api/src/contracts/scrobble/schema/response/episodeScrobbleResponseSchema.ts @@ -1,10 +1,11 @@ import { episodeResponseSchema } from '../../../_internal/response/episodeResponseSchema.ts'; import { showResponseSchema } from '../../../_internal/response/showResponseSchema.ts'; -import { asString, z } from '../../../_internal/z.ts'; +import { asString, float, int64, z } from '../../../_internal/z.ts'; export const episodeScrobbleResponseSchema = z.object({ - id: z.number().int(), + id: int64(z.number().int()), + progress: float(z.number()), action: asString(z.enum(['start', 'pause', 'stop'])), - episode: episodeResponseSchema, - show: showResponseSchema, + episode: episodeResponseSchema.nullish(), + show: showResponseSchema.nullish(), }); diff --git a/projects/api/src/contracts/scrobble/schema/response/movieScrobbleResponseSchema.ts b/projects/api/src/contracts/scrobble/schema/response/movieScrobbleResponseSchema.ts index 4f5462d8..a069381a 100644 --- a/projects/api/src/contracts/scrobble/schema/response/movieScrobbleResponseSchema.ts +++ b/projects/api/src/contracts/scrobble/schema/response/movieScrobbleResponseSchema.ts @@ -1,8 +1,9 @@ import { movieResponseSchema } from '../../../_internal/response/movieResponseSchema.ts'; -import { asString, z } from '../../../_internal/z.ts'; +import { asString, float, int64, z } from '../../../_internal/z.ts'; export const movieScrobbleResponseSchema = z.object({ - id: z.number().int(), + id: int64(z.number().int()), + progress: float(z.number()), action: asString(z.enum(['start', 'pause', 'stop'])), - movie: movieResponseSchema, + movie: movieResponseSchema.nullish(), }); diff --git a/projects/api/src/contracts/sync/index.ts b/projects/api/src/contracts/sync/index.ts index ad065eb6..1f56c335 100644 --- a/projects/api/src/contracts/sync/index.ts +++ b/projects/api/src/contracts/sync/index.ts @@ -53,6 +53,7 @@ const progress = builder.router({ method: 'GET', path: '/progress/up_next_nitro', query: pageQuerySchema + .merge(sortQuerySchema) .merge(upNextIntentQuerySchema), responses: { 200: upNextResponseSchema.array(), diff --git a/projects/api/src/contracts/sync/schema/request/playbackIdParamsSchema.ts b/projects/api/src/contracts/sync/schema/request/playbackIdParamsSchema.ts index 83d33e21..445ee0d4 100644 --- a/projects/api/src/contracts/sync/schema/request/playbackIdParamsSchema.ts +++ b/projects/api/src/contracts/sync/schema/request/playbackIdParamsSchema.ts @@ -1,7 +1,5 @@ import { z } from '../../../_internal/z.ts'; export const playbackIdParamsSchema = z.object({ - id: z.number().int().or(z.string()).describe( - 'ID of the playback entry', - ), + id: z.number().int().describe('ID of the playback entry'), }); diff --git a/projects/api/src/contracts/users/schema/request/coverRequestSchema.ts b/projects/api/src/contracts/users/schema/request/coverRequestSchema.ts index 3182afc7..353ed833 100644 --- a/projects/api/src/contracts/users/schema/request/coverRequestSchema.ts +++ b/projects/api/src/contracts/users/schema/request/coverRequestSchema.ts @@ -2,5 +2,5 @@ import { z } from '../../../_internal/z.ts'; export const coverRequestSchema = z.object({ cover_type: z.enum(['movie', 'show', 'episode']), - cover_id: z.number(), + cover_id: z.number().int(), }); diff --git a/projects/api/src/contracts/users/schema/request/filterIdParamsSchema.ts b/projects/api/src/contracts/users/schema/request/filterIdParamsSchema.ts index c69fd0f2..48f6ef18 100644 --- a/projects/api/src/contracts/users/schema/request/filterIdParamsSchema.ts +++ b/projects/api/src/contracts/users/schema/request/filterIdParamsSchema.ts @@ -1,7 +1,5 @@ import { z } from '../../../_internal/z.ts'; export const filterIdParamsSchema = z.object({ - id: z.number().int().or(z.string()).describe( - 'ID of the saved filter', - ), + id: z.number().int().describe('ID of the saved filter'), }); diff --git a/projects/api/src/contracts/users/schema/response/activityHistoryResponseSchema.ts b/projects/api/src/contracts/users/schema/response/activityHistoryResponseSchema.ts index 7574112c..c1c6a769 100644 --- a/projects/api/src/contracts/users/schema/response/activityHistoryResponseSchema.ts +++ b/projects/api/src/contracts/users/schema/response/activityHistoryResponseSchema.ts @@ -1,9 +1,27 @@ -import { z } from '../../../_internal/z.ts'; +import { episodeResponseSchema } from '../../../_internal/response/episodeResponseSchema.ts'; +import { movieResponseSchema } from '../../../_internal/response/movieResponseSchema.ts'; +import { showResponseSchema } from '../../../_internal/response/showResponseSchema.ts'; +import { int64, z } from '../../../_internal/z.ts'; +import { historyActionSchema } from './historyActionSchema.ts'; -import { episodeActivityHistoryResponseSchema } from './episodeActivityHistoryResponseSchema.ts'; -import { movieActivityHistoryResponseSchema } from './movieActivityHistoryResponseSchema.ts'; +const historyBaseSchema = z.object({ + id: int64(z.number().int()), + watched_at: z.string().datetime(), + action: historyActionSchema, +}); -export const activityHistoryResponseSchema = z.discriminatedUnion('type', [ - movieActivityHistoryResponseSchema, - episodeActivityHistoryResponseSchema, +const historyEpisodeSchema = z.object({ + type: z.literal('episode'), + episode: episodeResponseSchema.nullish(), + show: showResponseSchema.nullish(), +}); + +const historyMovieSchema = z.object({ + type: z.literal('movie'), + movie: movieResponseSchema.nullish(), +}); + +export const activityHistoryResponseSchema = z.union([ + historyBaseSchema.merge(historyEpisodeSchema), + historyBaseSchema.merge(historyMovieSchema), ]); diff --git a/projects/api/src/contracts/users/subroutes/watched.ts b/projects/api/src/contracts/users/subroutes/watched.ts index 05831dbe..293c1668 100644 --- a/projects/api/src/contracts/users/subroutes/watched.ts +++ b/projects/api/src/contracts/users/subroutes/watched.ts @@ -1,5 +1,6 @@ import { builder } from '../../_internal/builder.ts'; import { extendedQuerySchemaFactory } from '../../_internal/request/extendedQuerySchemaFactory.ts'; +import { pageQuerySchema } from '../../_internal/request/pageQuerySchema.ts'; import { z } from '../../_internal/z.ts'; import { showQueryParamsSchema } from '../../shows/schema/request/showQueryParamsSchema.ts'; import { minimalParamSchema } from '../../sync/schema/request/minimalParamSchema.ts'; @@ -33,7 +34,7 @@ export const watched = builder.router({ path: '/movies', method: 'GET', pathParams: profileParamsSchema, - query: minimalParamSchema, + query: minimalParamSchema.merge(pageQuerySchema), responses: { 200: watchedMoviesMinimalResponseSchema, }, @@ -42,11 +43,13 @@ export const watched = builder.router({ path: '/shows', method: 'GET', pathParams: profileParamsSchema, - query: minimalParamSchema.merge( - showQueryParamsSchema.pick({ specials: true }), - ).extend({ - season_numbers: z.boolean().nullish(), - }), + query: minimalParamSchema + .merge(pageQuerySchema) + .merge( + showQueryParamsSchema.pick({ specials: true }), + ).extend({ + season_numbers: z.boolean().nullish(), + }), responses: { 200: watchedShowsMinimalResponseSchema, },