Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3baa3d7
refactor: drop unused setOperationId and jsonQuery generator options
vladjerca Oct 17, 2026
0ca0325
refactor(api): delete unused searchListResponseSchema
vladjerca Oct 17, 2026
7d8a9f4
refactor(developer): build member add/remove requests from one path h…
vladjerca Oct 17, 2026
6869a2d
refactor(api): reuse the episode history schema for show history
vladjerca Oct 17, 2026
da6cffa
refactor(api): reuse historyRemoveResponseSchema for hidden removals
vladjerca Oct 17, 2026
c3fb3bf
refactor(api): define each Plex toggle shape once
vladjerca Oct 17, 2026
550dadc
refactor(api): reuse mediaMutationCountsSchema in sync ratings responses
vladjerca Oct 17, 2026
45f136a
refactor(api): share the voted external ratings shape
vladjerca Oct 17, 2026
9797fc6
refactor(api): share the list summary block of list item responses
vladjerca Oct 17, 2026
8acbe6e
refactor(api): build the trending search type param with searchTypePa…
vladjerca Oct 17, 2026
1229743
refactor(developer): use hasMissingExpectedJsonBody in the request ed…
vladjerca Oct 17, 2026
38f0316
refactor(developer): rebuild the request URL through one local function
vladjerca Oct 17, 2026
b3fa4d0
refactor(developer): read the guide contents from GUIDE_SOURCES
vladjerca Oct 17, 2026
f51ee9d
refactor(developer): group the request editor's visually hidden labels
vladjerca Oct 17, 2026
4c088c2
refactor(developer): render the create tile lock icon from one snippet
vladjerca Oct 17, 2026
1024cc7
refactor(developer): call listAccounts directly when refreshing accounts
vladjerca Oct 17, 2026
f0eb90a
refactor(developer): pick content types and format examples in one place
vladjerca Oct 17, 2026
ff158e8
refactor(api): declare repeated route query and path schemas once per…
vladjerca Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .agents/rules/jsr.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ annotations - they are load-bearing:
`UsersRouter = ReturnType<typeof builder.router<...>>` with an
`as unknown as UsersRouter` cast. Keep the internal route groups
(`GLOBAL_LEVEL`, `ENTITY_LEVEL`, `syncs`, `plex`) non-exported.
- The query factories (`extendedQuerySchemaFactory`, `searchTypeParamFactory`,
`trendingSearchTypeParamFactory`) have explicit `z.ZodObject<{...}>` returns
(avoids TS5088 cyclic).
- The query factories (`extendedQuerySchemaFactory`, `searchTypeParamFactory`)
have explicit `z.ZodObject<{...}>` returns (avoids TS5088 cyclic).
- `builder` is typed `ReturnType<typeof initContract>` (avoids TS4023).

If a new symbol trips TS7056/TS5088/TS4023 during `build:types`, give it an
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { listSummaryResponseSchema } from './listSummaryResponseSchema.ts';
import { bulkMediaRequestSchema } from '../request/bulkMediaRequestSchema.ts';
import { z } from '../z.ts';
import { mediaMutationCountsSchema } from './mediaMutationCountsSchema.ts';
Expand All @@ -7,8 +8,5 @@ export const listAddResponseSchema = z.object({
added: mediaMutationCountsSchema,
existing: mediaMutationCountsSchema,
not_found: bulkMediaRequestSchema,
list: z.object({
updated_at: z.string().datetime(),
item_count: z.number().int(),
}),
list: listSummaryResponseSchema,
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { listSummaryResponseSchema } from './listSummaryResponseSchema.ts';
import { bulkMediaRequestSchema } from '../request/bulkMediaRequestSchema.ts';
import { z } from '../z.ts';
import { mediaMutationCountsSchema } from './mediaMutationCountsSchema.ts';
Expand All @@ -6,8 +7,5 @@ import { mediaMutationCountsSchema } from './mediaMutationCountsSchema.ts';
export const listRemoveResponseSchema = z.object({
deleted: mediaMutationCountsSchema,
not_found: bulkMediaRequestSchema,
list: z.object({
updated_at: z.string().datetime(),
item_count: z.number().int(),
}),
list: listSummaryResponseSchema,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from '../z.ts';

/** Zod schema for the list summary returned after a list item change. */
export const listSummaryResponseSchema = z.object({
updated_at: z.string().datetime(),
item_count: z.number().int(),
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { z } from '../z.ts';
import {
extendedRatingsResponseExample,
externalRatingsResponseSchema,
externalVotedRatingsResponseSchema,
ratingsResponseExample,
ratingsResponseSchema,
} from './ratingsResponseSchema.ts';
Expand All @@ -15,16 +14,12 @@ export const movieRatingsResponseSchema = ratingsResponseSchema.extend({
* Letterboxd audience rating on a 0-5 scale. Films only.
* Available if requesting extended `all`.
*/
letterboxd: externalRatingsResponseSchema.extend({
votes: z.number().int().nullish(),
}).nullish(),
letterboxd: externalVotedRatingsResponseSchema.nullish(),
/**
* MyAnimeList audience rating on a 0-10 scale. Anime only.
* Available if requesting extended `all`.
*/
mal: externalRatingsResponseSchema.extend({
votes: z.number().int().nullish(),
}).nullish(),
mal: externalVotedRatingsResponseSchema.nullish(),
}).openapi({
mediaExamples: {
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ export const externalRatingsResponseSchema = z.object({
link: z.string().nullish(),
});

/** Shared shape for an external rating source that also reports its vote count. */
export const externalVotedRatingsResponseSchema = externalRatingsResponseSchema
.extend({
votes: z.number().int().nullish(),
});

/** Zod schema for the ratings response. */
export const ratingsResponseSchema = z.object({
rating: float(z.number()).nullish(),
Expand All @@ -79,15 +85,11 @@ export const ratingsResponseSchema = z.object({
/**
* Available if requesting extended `all`.
*/
tmdb: externalRatingsResponseSchema.extend({
votes: z.number().int().nullish(),
}).nullish(),
tmdb: externalVotedRatingsResponseSchema.nullish(),
/**
* Available if requesting extended `all`.
*/
imdb: externalRatingsResponseSchema.extend({
votes: z.number().int().nullish(),
}).nullish(),
imdb: externalVotedRatingsResponseSchema.nullish(),
/**
* Available if requesting extended `all`.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { z } from '../z.ts';
import {
extendedRatingsResponseExample,
externalRatingsResponseSchema,
externalVotedRatingsResponseSchema,
ratingsResponseExample,
ratingsResponseSchema,
} from './ratingsResponseSchema.ts';
Expand All @@ -15,9 +14,7 @@ export const showRatingsResponseSchema = ratingsResponseSchema.extend({
* MyAnimeList audience rating on a 0-10 scale. Anime only.
* Available if requesting extended `all`.
*/
mal: externalRatingsResponseSchema.extend({
votes: z.number().int().nullish(),
}).nullish(),
mal: externalVotedRatingsResponseSchema.nullish(),
}).openapi({
mediaExamples: {
default: {
Expand Down
58 changes: 22 additions & 36 deletions projects/api/src/contracts/calendars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ const groupQuery = z.object({
}),
});

const filteredCalendarQuerySchema = extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema);

const mediaFeedQuerySchema = extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(z.object({
type: z.enum(['movie', 'show']).optional().openapi({
description:
'Narrow the feed to a single media type. Omit to return both.',
}),
}))
.merge(groupQuery);

const globalCalendarCurationNotice =
'When `target` is `all`, the global calendar uses automatic quality filters to reduce incorrect or low-quality entries. As a result, it may not include every matching item in the database, even when that item has complete information. An omitted item may appear later after it meets the filter requirements.';

Expand Down Expand Up @@ -45,9 +59,7 @@ Returns new shows airing their first season during the requested UTC date range.
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/shows/new/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarShowResponseSchema.array(),
Expand All @@ -61,9 +73,7 @@ Returns season premieres airing during the requested UTC date range. Use \`targe
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/shows/premieres/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarShowResponseSchema.array(),
Expand All @@ -77,9 +87,7 @@ Returns show finales airing during the requested UTC date range. Use \`target\`
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/shows/finales/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarShowResponseSchema.array(),
Expand All @@ -93,9 +101,7 @@ Returns movies with a release date during the requested UTC date range. Use \`ta
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/movies/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarMovieResponseSchema.array(),
Expand All @@ -109,9 +115,7 @@ Returns all movies with a streaming release date during the requested UTC date r
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/streaming/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarMovieResponseSchema.array(),
Expand All @@ -125,9 +129,7 @@ Returns DVD and physical media releases during the requested UTC date range. Use
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/dvd/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(ignoreQuerySchema),
query: filteredCalendarQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: calendarMovieResponseSchema.array(),
Expand All @@ -141,15 +143,7 @@ Returns the merged feed of movies and episodes during the requested UTC date ran
${globalCalendarCurationNotice}`,
method: 'GET',
path: '/:target/media/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(z.object({
type: z.enum(['movie', 'show']).optional().openapi({
description:
'Narrow the feed to a single media type. Omit to return both.',
}),
}))
.merge(groupQuery),
query: mediaFeedQuerySchema,
pathParams: calendarRequestParamsSchema,
responses: {
200: hotReleaseResponseSchema.array(),
Expand All @@ -161,15 +155,7 @@ ${globalCalendarCurationNotice}`,
Returns the merged feed of upcoming movies and episodes during the requested UTC date range that are trending or highly anticipated, ordered by availability date. This is the global feed only; use \`type\` to narrow to a single media type.`,
method: 'GET',
path: '/releases/hot/:start_date/:days',
query: extendedMediaQuerySchema
.merge(mediaFilterParamsSchema)
.merge(z.object({
type: z.enum(['movie', 'show']).optional().openapi({
description:
'Narrow the feed to a single media type. Omit to return both.',
}),
}))
.merge(groupQuery),
query: mediaFeedQuerySchema,
pathParams: calendarRequestParamsSchema.omit({ target: true }),
responses: {
200: hotReleaseResponseSchema.array(),
Expand Down
20 changes: 8 additions & 12 deletions projects/api/src/contracts/comments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const includeRepliesQuerySchema = z.object({
),
});

const commentActivityQuerySchema = extendedProfileQuerySchema
.merge(pageQuerySchema)
.merge(limitlessQuerySchema)
.merge(includeRepliesQuerySchema);

const commentItemResponseSchema = z.union([
movieResponseSchema,
showResponseSchema,
Expand Down Expand Up @@ -277,10 +282,7 @@ const GLOBAL_LEVEL = builder.router({
path: '/trending/:comment_type/:type',
method: 'GET',
pathParams: commentActivityParamsSchema,
query: extendedProfileQuerySchema
.merge(pageQuerySchema)
.merge(limitlessQuerySchema)
.merge(includeRepliesQuerySchema),
query: commentActivityQuerySchema,
responses: {
200: commentResponseSchema.array(),
},
Expand All @@ -292,10 +294,7 @@ const GLOBAL_LEVEL = builder.router({
path: '/recent/:comment_type/:type',
method: 'GET',
pathParams: commentActivityParamsSchema,
query: extendedProfileQuerySchema
.merge(pageQuerySchema)
.merge(limitlessQuerySchema)
.merge(includeRepliesQuerySchema),
query: commentActivityQuerySchema,
responses: {
200: commentResponseSchema.array(),
},
Expand All @@ -307,10 +306,7 @@ const GLOBAL_LEVEL = builder.router({
path: '/updates/:comment_type/:type',
method: 'GET',
pathParams: commentActivityParamsSchema,
query: extendedProfileQuerySchema
.merge(pageQuerySchema)
.merge(limitlessQuerySchema)
.merge(includeRepliesQuerySchema),
query: commentActivityQuerySchema,
responses: {
200: commentResponseSchema.array(),
},
Expand Down
Loading
Loading