@kids-reporter/api-types 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/openapi.d.ts +43 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +50 -0
- package/dist/openapi.js.map +1 -0
- package/dist/rest.d.ts +57 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +26 -0
- package/dist/rest.js.map +1 -0
- package/dist/schemas/content-feed-queries.d.ts +34 -0
- package/dist/schemas/content-feed-queries.d.ts.map +1 -0
- package/dist/schemas/content-feed-queries.js +21 -0
- package/dist/schemas/content-feed-queries.js.map +1 -0
- package/dist/schemas/content-feed-responses.d.ts +192 -0
- package/dist/schemas/content-feed-responses.d.ts.map +1 -0
- package/dist/schemas/content-feed-responses.js +87 -0
- package/dist/schemas/content-feed-responses.js.map +1 -0
- package/dist/schemas/content-post-detail.d.ts +313 -0
- package/dist/schemas/content-post-detail.d.ts.map +1 -0
- package/dist/schemas/content-post-detail.js +196 -0
- package/dist/schemas/content-post-detail.js.map +1 -0
- package/dist/schemas/content.d.ts +438 -0
- package/dist/schemas/content.d.ts.map +1 -0
- package/dist/schemas/content.js +722 -0
- package/dist/schemas/content.js.map +1 -0
- package/dist/schemas/extra-openapi-paths.d.ts +53 -0
- package/dist/schemas/extra-openapi-paths.d.ts.map +1 -0
- package/dist/schemas/extra-openapi-paths.js +674 -0
- package/dist/schemas/extra-openapi-paths.js.map +1 -0
- package/dist/schemas/member-essay-answers-has-liked.d.ts +16 -0
- package/dist/schemas/member-essay-answers-has-liked.d.ts.map +1 -0
- package/dist/schemas/member-essay-answers-has-liked.js +30 -0
- package/dist/schemas/member-essay-answers-has-liked.js.map +1 -0
- package/dist/schemas/qna.d.ts +145 -0
- package/dist/schemas/qna.d.ts.map +1 -0
- package/dist/schemas/qna.js +632 -0
- package/dist/schemas/qna.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { extendZodWithOpenApi, OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { RestErrorBodySchema } from '../rest.js';
|
|
4
|
+
import { V1PostDetailBodySchema, V1PostEssayQuestionsBodySchema, V1PostMetaBodySchema, V1PostsEssayAnswersWithLikesResponseSchema, V1ProjectDetailRelatedPostOrderedItemSchema } from './content-post-detail.js';
|
|
5
|
+
extendZodWithOpenApi(z);
|
|
6
|
+
export const registry = new OpenAPIRegistry();
|
|
7
|
+
export const PhotoResizedSchema = z.object({
|
|
8
|
+
small: z.string().default('').openapi({
|
|
9
|
+
example: 'https://kids.twreporter.org/resized/abc-800.webp'
|
|
10
|
+
})
|
|
11
|
+
}).openapi('ResizedPhoto');
|
|
12
|
+
export const PostHeroImageSchema = z.object({
|
|
13
|
+
resized: PhotoResizedSchema
|
|
14
|
+
}).openapi('PostHeroImage');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Lightweight sub-subcategory shape for **feeds and list payloads** (`PostContentSchema`,
|
|
18
|
+
* e.g. `/v1/posts`, editor picks). Omits sub-subcategory / subcategory slugs on purpose —
|
|
19
|
+
* callers only need labels + category slug for cards. For the **full tree with slugs**
|
|
20
|
+
* (article detail, related-post cards), see `SubSubcategoryFullItemSchema` in
|
|
21
|
+
* `content-post-detail.ts`.
|
|
22
|
+
*/
|
|
23
|
+
export const PostSubSubcategorySchema = z.object({
|
|
24
|
+
name: z.string(),
|
|
25
|
+
subcategory: z.object({
|
|
26
|
+
name: z.string(),
|
|
27
|
+
category: z.object({
|
|
28
|
+
slug: z.string(),
|
|
29
|
+
themeColor: z.string().nullable().optional()
|
|
30
|
+
}).nullable().optional()
|
|
31
|
+
}).nullable().optional()
|
|
32
|
+
}).openapi('PostSubSubcategory');
|
|
33
|
+
export const PostContentSchema = z.object({
|
|
34
|
+
title: z.string(),
|
|
35
|
+
slug: z.string(),
|
|
36
|
+
ogDescription: z.string().nullable().optional(),
|
|
37
|
+
heroImage: PostHeroImageSchema.nullable().optional(),
|
|
38
|
+
subSubcategoriesOrdered: z.array(PostSubSubcategorySchema).default([]),
|
|
39
|
+
publishedDate: z.iso.datetime().nullable().optional()
|
|
40
|
+
}).openapi('PostContent');
|
|
41
|
+
export const SubcategoryItemSchema = z.object({
|
|
42
|
+
id: z.union([z.string(), z.number().int()]),
|
|
43
|
+
name: z.string(),
|
|
44
|
+
slug: z.string(),
|
|
45
|
+
category: z.object({
|
|
46
|
+
slug: z.string()
|
|
47
|
+
}).nullable().optional()
|
|
48
|
+
}).openapi('Subcategory');
|
|
49
|
+
export const CallBaodaozaiIntroItemSchema = z.object({
|
|
50
|
+
id: z.union([z.string(), z.number().int()]),
|
|
51
|
+
page: z.string(),
|
|
52
|
+
content: z.string()
|
|
53
|
+
}).openapi('CallBaodaozaiIntro');
|
|
54
|
+
export const EditorPicksSettingItemSchema = z.object({
|
|
55
|
+
id: z.union([z.string(), z.number().int()]),
|
|
56
|
+
editorPicksOfPostsOrdered: z.array(PostContentSchema).default([]),
|
|
57
|
+
editorPicksOfTags: z.array(z.object({
|
|
58
|
+
name: z.string(),
|
|
59
|
+
slug: z.string()
|
|
60
|
+
})).default([]),
|
|
61
|
+
popularKeywordsOrdered: z.array(z.object({
|
|
62
|
+
name: z.string()
|
|
63
|
+
})).default([])
|
|
64
|
+
}).openapi('EditorPicksSetting');
|
|
65
|
+
export const V1PostsQuerySchema = z.object({
|
|
66
|
+
take: z.coerce.number().int().min(1).max(50).optional(),
|
|
67
|
+
skip: z.coerce.number().int().min(0).max(5000).optional(),
|
|
68
|
+
orderBy: z.enum(['publishedDate:desc']).optional()
|
|
69
|
+
});
|
|
70
|
+
export const V1PostsResponseSchema = z.object({
|
|
71
|
+
posts: z.array(PostContentSchema),
|
|
72
|
+
page: z.object({
|
|
73
|
+
take: z.number().int().min(1).max(50),
|
|
74
|
+
skip: z.number().int().min(0)
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
registry.registerPath({
|
|
78
|
+
method: 'get',
|
|
79
|
+
path: '/v1/posts',
|
|
80
|
+
tags: ['Posts'],
|
|
81
|
+
description: 'List latest published posts.',
|
|
82
|
+
request: {
|
|
83
|
+
query: V1PostsQuerySchema
|
|
84
|
+
},
|
|
85
|
+
responses: {
|
|
86
|
+
200: {
|
|
87
|
+
description: 'Success',
|
|
88
|
+
content: {
|
|
89
|
+
'application/json': {
|
|
90
|
+
schema: V1PostsResponseSchema
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
400: {
|
|
95
|
+
description: 'Bad request',
|
|
96
|
+
content: {
|
|
97
|
+
'application/json': {
|
|
98
|
+
schema: RestErrorBodySchema
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
export const V1SubcategoriesResponseSchema = z.array(SubcategoryItemSchema);
|
|
105
|
+
registry.registerPath({
|
|
106
|
+
method: 'get',
|
|
107
|
+
path: '/v1/subcategories',
|
|
108
|
+
tags: ['Taxonomy'],
|
|
109
|
+
description: 'All subcategories with parent category slug.',
|
|
110
|
+
responses: {
|
|
111
|
+
200: {
|
|
112
|
+
description: 'Success',
|
|
113
|
+
content: {
|
|
114
|
+
'application/json': {
|
|
115
|
+
schema: V1SubcategoriesResponseSchema
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
export const V1AuthorAvatarPathParamsSchema = z.object({
|
|
122
|
+
slug: z.string().min(1)
|
|
123
|
+
});
|
|
124
|
+
export const V1AuthorAvatarResponseSchema = z.object({
|
|
125
|
+
tiny: z.string()
|
|
126
|
+
}).openapi('AuthorAvatarTiny');
|
|
127
|
+
registry.registerPath({
|
|
128
|
+
method: 'get',
|
|
129
|
+
path: '/v1/authors/by-slug/{slug}/avatar',
|
|
130
|
+
tags: ['Taxonomy'],
|
|
131
|
+
description: 'Author avatar tiny image URL by slug. `tiny` is empty when the author or avatar is missing.',
|
|
132
|
+
request: {
|
|
133
|
+
params: V1AuthorAvatarPathParamsSchema
|
|
134
|
+
},
|
|
135
|
+
responses: {
|
|
136
|
+
200: {
|
|
137
|
+
description: 'Success',
|
|
138
|
+
content: {
|
|
139
|
+
'application/json': {
|
|
140
|
+
schema: V1AuthorAvatarResponseSchema
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
export const V1PopularKeywordsResponseSchema = z.array(z.object({
|
|
147
|
+
name: z.string()
|
|
148
|
+
}));
|
|
149
|
+
registry.registerPath({
|
|
150
|
+
method: 'get',
|
|
151
|
+
path: '/v1/popular-keywords',
|
|
152
|
+
tags: ['Editor'],
|
|
153
|
+
description: 'Popular keywords from editor picks ordering.',
|
|
154
|
+
responses: {
|
|
155
|
+
200: {
|
|
156
|
+
description: 'Success',
|
|
157
|
+
content: {
|
|
158
|
+
'application/json': {
|
|
159
|
+
schema: V1PopularKeywordsResponseSchema
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
export const V1CallBaodaozaiIntroPathParamsSchema = z.object({
|
|
166
|
+
page: z.enum(['home', 'all', 'topics', 'news', 'storytelling', 'campus', 'listeningNews', 'classroom'])
|
|
167
|
+
});
|
|
168
|
+
export const V1CallBaodaozaiIntroResponseSchema = CallBaodaozaiIntroItemSchema;
|
|
169
|
+
registry.registerPath({
|
|
170
|
+
method: 'get',
|
|
171
|
+
path: '/v1/call-baodaozai-intros/{page}',
|
|
172
|
+
tags: ['Editor'],
|
|
173
|
+
description: 'Get Call Baodaozai intro content by page.',
|
|
174
|
+
request: {
|
|
175
|
+
params: V1CallBaodaozaiIntroPathParamsSchema
|
|
176
|
+
},
|
|
177
|
+
responses: {
|
|
178
|
+
200: {
|
|
179
|
+
description: 'Success',
|
|
180
|
+
content: {
|
|
181
|
+
'application/json': {
|
|
182
|
+
schema: V1CallBaodaozaiIntroResponseSchema
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
400: {
|
|
187
|
+
description: 'Bad request',
|
|
188
|
+
content: {
|
|
189
|
+
'application/json': {
|
|
190
|
+
schema: RestErrorBodySchema
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
404: {
|
|
195
|
+
description: 'Not found',
|
|
196
|
+
content: {
|
|
197
|
+
'application/json': {
|
|
198
|
+
schema: RestErrorBodySchema
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
/** Query for `GET /v1/projects`. */
|
|
206
|
+
export const V1ProjectsQuerySchema = z.object({
|
|
207
|
+
take: z.coerce.number().int().min(1).max(50).optional(),
|
|
208
|
+
skip: z.coerce.number().int().min(0).max(5000).optional(),
|
|
209
|
+
orderBy: z.enum(['publishedDate:desc']).optional(),
|
|
210
|
+
includeRelatedPosts: z.union([z.literal('true'), z.literal('false')]).optional().transform(v => v === 'true')
|
|
211
|
+
});
|
|
212
|
+
export const V1ProjectsItemSchema = z.object({
|
|
213
|
+
title: z.string(),
|
|
214
|
+
subtitle: z.string(),
|
|
215
|
+
slug: z.string(),
|
|
216
|
+
ogDescription: z.string().nullable().optional(),
|
|
217
|
+
publishedDate: z.iso.datetime().nullable().optional(),
|
|
218
|
+
heroImage: z.object({
|
|
219
|
+
resized: z.object({
|
|
220
|
+
small: z.string(),
|
|
221
|
+
medium: z.string()
|
|
222
|
+
})
|
|
223
|
+
}).nullable().optional(),
|
|
224
|
+
relatedPostsOrdered: z.array(PostContentSchema).optional()
|
|
225
|
+
}).openapi('ProjectsItem');
|
|
226
|
+
export const V1ProjectsResponseSchema = z.object({
|
|
227
|
+
projects: z.array(V1ProjectsItemSchema),
|
|
228
|
+
projectsCount: z.number().int().min(0)
|
|
229
|
+
});
|
|
230
|
+
registry.registerPath({
|
|
231
|
+
method: 'get',
|
|
232
|
+
path: '/v1/projects',
|
|
233
|
+
tags: ['Projects'],
|
|
234
|
+
description: 'List published projects. Response always includes `projects` and total `projectsCount`. Optional `relatedPostsOrdered` on each item appears only when `includeRelatedPosts=true`.',
|
|
235
|
+
request: {
|
|
236
|
+
query: V1ProjectsQuerySchema
|
|
237
|
+
},
|
|
238
|
+
responses: {
|
|
239
|
+
200: {
|
|
240
|
+
description: 'Success',
|
|
241
|
+
content: {
|
|
242
|
+
'application/json': {
|
|
243
|
+
schema: V1ProjectsResponseSchema
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
400: {
|
|
248
|
+
description: 'Bad request',
|
|
249
|
+
content: {
|
|
250
|
+
'application/json': {
|
|
251
|
+
schema: RestErrorBodySchema
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
export const V1EditorPicksSettingsQuerySchema = z.object({
|
|
258
|
+
take: z.coerce.number().int().min(1).max(20).optional().default(5)
|
|
259
|
+
});
|
|
260
|
+
export const V1EditorPicksSettingsResponseSchema = z.array(EditorPicksSettingItemSchema);
|
|
261
|
+
registry.registerPath({
|
|
262
|
+
method: 'get',
|
|
263
|
+
path: '/v1/editor-picks-settings',
|
|
264
|
+
tags: ['Editor'],
|
|
265
|
+
description: 'Editor picks configuration rows.',
|
|
266
|
+
request: {
|
|
267
|
+
query: V1EditorPicksSettingsQuerySchema
|
|
268
|
+
},
|
|
269
|
+
responses: {
|
|
270
|
+
200: {
|
|
271
|
+
description: 'Success',
|
|
272
|
+
content: {
|
|
273
|
+
'application/json': {
|
|
274
|
+
schema: V1EditorPicksSettingsResponseSchema
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
400: {
|
|
279
|
+
description: 'Bad request',
|
|
280
|
+
content: {
|
|
281
|
+
'application/json': {
|
|
282
|
+
schema: RestErrorBodySchema
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
export const V1MemberAvatarSchema = z.object({
|
|
289
|
+
id: z.string(),
|
|
290
|
+
fileUrl: z.string()
|
|
291
|
+
}).openapi('MemberAvatar');
|
|
292
|
+
export const V1MemberProfileSchema = z.object({
|
|
293
|
+
id: z.string(),
|
|
294
|
+
name: z.string(),
|
|
295
|
+
email: z.string(),
|
|
296
|
+
nickname: z.string(),
|
|
297
|
+
contactEmail: z.string(),
|
|
298
|
+
twreporter_user_id: z.string(),
|
|
299
|
+
showBaodaozai: z.boolean(),
|
|
300
|
+
essayQuestionCount: z.number().int().nullable().optional(),
|
|
301
|
+
avatar: V1MemberAvatarSchema.nullable().optional(),
|
|
302
|
+
createdAt: z.iso.datetime().nullable().optional()
|
|
303
|
+
}).openapi('MemberProfile');
|
|
304
|
+
export const V1MemberProfileResponseSchema = V1MemberProfileSchema;
|
|
305
|
+
export const V1MemberProfilePatchBodySchema = z.strictObject({
|
|
306
|
+
name: z.string().optional(),
|
|
307
|
+
nickname: z.string().optional(),
|
|
308
|
+
contactEmail: z.string().optional(),
|
|
309
|
+
showBaodaozai: z.boolean().optional(),
|
|
310
|
+
essayQuestionCount: z.number().int().min(0).max(3).optional()
|
|
311
|
+
}).openapi('MemberProfilePatch');
|
|
312
|
+
|
|
313
|
+
/** Successful `POST /auth/access-token` response JSON. */
|
|
314
|
+
export const V1AccessTokenResponseSchema = z.object({
|
|
315
|
+
accessToken: z.string(),
|
|
316
|
+
twreporterUserId: z.string(),
|
|
317
|
+
expiresAt: z.number()
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
/** GET `/v1/members/me/posts-with-answers` query string. */
|
|
321
|
+
export const V1MemberPostsWithAnswersQuerySchema = z.object({
|
|
322
|
+
take: z.coerce.number().int().min(1).max(50).optional().default(5),
|
|
323
|
+
cursor: z.string().optional()
|
|
324
|
+
});
|
|
325
|
+
registry.registerPath({
|
|
326
|
+
method: 'get',
|
|
327
|
+
path: '/v1/members/me',
|
|
328
|
+
tags: ['Members'],
|
|
329
|
+
description: 'Authenticated member profile (Bearer JWT).',
|
|
330
|
+
responses: {
|
|
331
|
+
200: {
|
|
332
|
+
description: 'Success',
|
|
333
|
+
content: {
|
|
334
|
+
'application/json': {
|
|
335
|
+
schema: V1MemberProfileResponseSchema
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
401: {
|
|
340
|
+
description: 'Unauthorized',
|
|
341
|
+
content: {
|
|
342
|
+
'application/json': {
|
|
343
|
+
schema: RestErrorBodySchema
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
404: {
|
|
348
|
+
description: 'Member not found',
|
|
349
|
+
content: {
|
|
350
|
+
'application/json': {
|
|
351
|
+
schema: RestErrorBodySchema
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
registry.registerPath({
|
|
358
|
+
method: 'patch',
|
|
359
|
+
path: '/v1/members/me',
|
|
360
|
+
tags: ['Members'],
|
|
361
|
+
description: 'Update authenticated member profile (Bearer JWT).',
|
|
362
|
+
request: {
|
|
363
|
+
body: {
|
|
364
|
+
content: {
|
|
365
|
+
'application/json': {
|
|
366
|
+
schema: V1MemberProfilePatchBodySchema
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
responses: {
|
|
372
|
+
200: {
|
|
373
|
+
description: 'Success',
|
|
374
|
+
content: {
|
|
375
|
+
'application/json': {
|
|
376
|
+
schema: V1MemberProfileResponseSchema
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
400: {
|
|
381
|
+
description: 'Bad request',
|
|
382
|
+
content: {
|
|
383
|
+
'application/json': {
|
|
384
|
+
schema: RestErrorBodySchema
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
401: {
|
|
389
|
+
description: 'Unauthorized',
|
|
390
|
+
content: {
|
|
391
|
+
'application/json': {
|
|
392
|
+
schema: RestErrorBodySchema
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
404: {
|
|
397
|
+
description: 'Member not found',
|
|
398
|
+
content: {
|
|
399
|
+
'application/json': {
|
|
400
|
+
schema: RestErrorBodySchema
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
export const V1PostBySlugPathParamsSchema = z.object({
|
|
407
|
+
slug: z.string().min(1)
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
/** Flat query for `GET /v1/posts/by-slug/{slug}` (news-reading order + related-posts filter are fixed server-side). */
|
|
411
|
+
export const V1PostBySlugQuerySchema = z.object({
|
|
412
|
+
take: z.coerce.number().int().min(1).max(50).optional().default(5),
|
|
413
|
+
postEssayQuestionsTake: z.coerce.number().int().min(1).max(50).optional().default(3),
|
|
414
|
+
postChoiceQuestionsTake: z.coerce.number().int().min(1).max(50).optional().default(3)
|
|
415
|
+
});
|
|
416
|
+
export const V1SitemapsQuerySchema = z.object({
|
|
417
|
+
sinceDays: z.coerce.number().int().min(1).max(365).optional().default(60)
|
|
418
|
+
});
|
|
419
|
+
export const V1SitemapEntrySchema = z.object({
|
|
420
|
+
slug: z.string(),
|
|
421
|
+
publishedDate: z.iso.datetime().nullable()
|
|
422
|
+
});
|
|
423
|
+
export const V1SitemapPostsResponseSchema = z.array(V1SitemapEntrySchema);
|
|
424
|
+
export const V1SitemapProjectsResponseSchema = z.array(V1SitemapEntrySchema);
|
|
425
|
+
export const V1ProjectBySlugMetaResponseSchema = z.object({
|
|
426
|
+
publishedDate: z.iso.datetime().optional(),
|
|
427
|
+
ogTitle: z.string(),
|
|
428
|
+
ogDescription: z.string().nullable(),
|
|
429
|
+
ogImage: z.object({
|
|
430
|
+
resized: z.object({
|
|
431
|
+
small: z.string()
|
|
432
|
+
})
|
|
433
|
+
}).nullable()
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
/** Hero image payloads returned by `/v1/projects/by-slug/{slug}` match Prisma-mapper targets. */
|
|
437
|
+
export const V1ProjectDetailPhotoResizedSchema = z.object({
|
|
438
|
+
resized: z.object({
|
|
439
|
+
small: z.string(),
|
|
440
|
+
medium: z.string(),
|
|
441
|
+
large: z.string()
|
|
442
|
+
})
|
|
443
|
+
});
|
|
444
|
+
export const V1ProjectBySlugDetailResponseSchema = z.object({
|
|
445
|
+
title: z.string(),
|
|
446
|
+
titlePosition: z.string().nullable().optional(),
|
|
447
|
+
subtitle: z.string(),
|
|
448
|
+
/** Draft.js JSON etc.; `z.json()` overflows zod-to-openapi union expansion. */
|
|
449
|
+
content: z.unknown().nullable().optional(),
|
|
450
|
+
credits: z.unknown().nullable().optional(),
|
|
451
|
+
publishedDate: z.iso.datetime().optional(),
|
|
452
|
+
heroImage: V1ProjectDetailPhotoResizedSchema.optional(),
|
|
453
|
+
mobileHeroImage: V1ProjectDetailPhotoResizedSchema.optional(),
|
|
454
|
+
relatedPostsOrdered: z.array(V1ProjectDetailRelatedPostOrderedItemSchema)
|
|
455
|
+
});
|
|
456
|
+
export const V1ProjectRelatedPostsCountResponseSchema = z.object({
|
|
457
|
+
relatedPostsCount: z.number().int().min(0)
|
|
458
|
+
});
|
|
459
|
+
export const V1AuthorPostsCountResponseSchema = z.object({
|
|
460
|
+
postsCount: z.number().int().min(0)
|
|
461
|
+
});
|
|
462
|
+
registry.registerPath({
|
|
463
|
+
method: 'get',
|
|
464
|
+
path: '/v1/sitemaps/posts',
|
|
465
|
+
tags: ['Sitemaps'],
|
|
466
|
+
description: 'Post slugs + published dates for sitemap (public posts only). Query `sinceDays` (1–365, default 60): include posts with `publishedDate` on/after start of day `now - sinceDays`.',
|
|
467
|
+
request: {
|
|
468
|
+
query: V1SitemapsQuerySchema
|
|
469
|
+
},
|
|
470
|
+
responses: {
|
|
471
|
+
200: {
|
|
472
|
+
description: 'Success',
|
|
473
|
+
content: {
|
|
474
|
+
'application/json': {
|
|
475
|
+
schema: V1SitemapPostsResponseSchema
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
400: {
|
|
480
|
+
description: 'Bad request',
|
|
481
|
+
content: {
|
|
482
|
+
'application/json': {
|
|
483
|
+
schema: RestErrorBodySchema
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
registry.registerPath({
|
|
490
|
+
method: 'get',
|
|
491
|
+
path: '/v1/sitemaps/projects',
|
|
492
|
+
tags: ['Sitemaps'],
|
|
493
|
+
description: 'Published project slugs + dates for sitemap. Query `sinceDays` (1–365, default 60): include projects with `publishedDate` on/after start of day `now - sinceDays`.',
|
|
494
|
+
request: {
|
|
495
|
+
query: V1SitemapsQuerySchema
|
|
496
|
+
},
|
|
497
|
+
responses: {
|
|
498
|
+
200: {
|
|
499
|
+
description: 'Success',
|
|
500
|
+
content: {
|
|
501
|
+
'application/json': {
|
|
502
|
+
schema: V1SitemapProjectsResponseSchema
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
400: {
|
|
507
|
+
description: 'Bad request',
|
|
508
|
+
content: {
|
|
509
|
+
'application/json': {
|
|
510
|
+
schema: RestErrorBodySchema
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
registry.registerPath({
|
|
517
|
+
method: 'get',
|
|
518
|
+
path: '/v1/projects/by-slug/{slug}',
|
|
519
|
+
tags: ['Projects'],
|
|
520
|
+
description: 'Published project detail for topic page (matches GraphQL `GetProject`).',
|
|
521
|
+
request: {
|
|
522
|
+
params: V1PostBySlugPathParamsSchema
|
|
523
|
+
},
|
|
524
|
+
responses: {
|
|
525
|
+
200: {
|
|
526
|
+
description: 'Success',
|
|
527
|
+
content: {
|
|
528
|
+
'application/json': {
|
|
529
|
+
schema: V1ProjectBySlugDetailResponseSchema
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
404: {
|
|
534
|
+
description: 'Not found',
|
|
535
|
+
content: {
|
|
536
|
+
'application/json': {
|
|
537
|
+
schema: RestErrorBodySchema
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
registry.registerPath({
|
|
544
|
+
method: 'get',
|
|
545
|
+
path: '/v1/projects/by-slug/{slug}/meta',
|
|
546
|
+
tags: ['Projects'],
|
|
547
|
+
description: 'Published project OG metadata (matches GraphQL `GetProjectMeta`).',
|
|
548
|
+
request: {
|
|
549
|
+
params: V1PostBySlugPathParamsSchema
|
|
550
|
+
},
|
|
551
|
+
responses: {
|
|
552
|
+
200: {
|
|
553
|
+
description: 'Success',
|
|
554
|
+
content: {
|
|
555
|
+
'application/json': {
|
|
556
|
+
schema: V1ProjectBySlugMetaResponseSchema
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
404: {
|
|
561
|
+
description: 'Not found',
|
|
562
|
+
content: {
|
|
563
|
+
'application/json': {
|
|
564
|
+
schema: RestErrorBodySchema
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
registry.registerPath({
|
|
571
|
+
method: 'get',
|
|
572
|
+
path: '/v1/projects/by-slug/{slug}/related-posts-count',
|
|
573
|
+
tags: ['Projects'],
|
|
574
|
+
description: 'Count of public posts linked to a published project (matches GraphQL `relatedPostsCount` on project).',
|
|
575
|
+
request: {
|
|
576
|
+
params: V1PostBySlugPathParamsSchema
|
|
577
|
+
},
|
|
578
|
+
responses: {
|
|
579
|
+
200: {
|
|
580
|
+
description: 'Success',
|
|
581
|
+
content: {
|
|
582
|
+
'application/json': {
|
|
583
|
+
schema: V1ProjectRelatedPostsCountResponseSchema
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
404: {
|
|
588
|
+
description: 'Not found',
|
|
589
|
+
content: {
|
|
590
|
+
'application/json': {
|
|
591
|
+
schema: RestErrorBodySchema
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
export const V1PostsEssayAnswersWithLikesQuerySchema = z.object({
|
|
598
|
+
take: z.coerce.number().int().min(1).max(50).optional().default(12),
|
|
599
|
+
skip: z.coerce.number().int().min(0).max(5000).optional().default(0),
|
|
600
|
+
orderBy: z.enum(['publishedDate:desc']).optional().default('publishedDate:desc'),
|
|
601
|
+
answerTake: z.coerce.number().int().min(1).max(50).optional().default(10),
|
|
602
|
+
answerOrderBy: z.enum(['likesCount:desc', 'createdAt:desc']).optional().default('likesCount:desc')
|
|
603
|
+
});
|
|
604
|
+
registry.registerPath({
|
|
605
|
+
method: 'get',
|
|
606
|
+
path: '/v1/posts/by-slug/{slug}',
|
|
607
|
+
tags: ['Posts'],
|
|
608
|
+
description: 'Single post article payload (matches GraphQL `GetPost`). Flat query: `take`, `postEssayQuestionsTake`, `postChoiceQuestionsTake` (1–50 each, defaults 5/3/3). News-reading item order and nested related-posts filter are fixed server-side.',
|
|
609
|
+
request: {
|
|
610
|
+
params: V1PostBySlugPathParamsSchema,
|
|
611
|
+
query: V1PostBySlugQuerySchema
|
|
612
|
+
},
|
|
613
|
+
responses: {
|
|
614
|
+
200: {
|
|
615
|
+
description: 'Success',
|
|
616
|
+
content: {
|
|
617
|
+
'application/json': {
|
|
618
|
+
schema: V1PostDetailBodySchema
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
400: {
|
|
623
|
+
description: 'Bad request',
|
|
624
|
+
content: {
|
|
625
|
+
'application/json': {
|
|
626
|
+
schema: RestErrorBodySchema
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
404: {
|
|
631
|
+
description: 'Not found',
|
|
632
|
+
content: {
|
|
633
|
+
'application/json': {
|
|
634
|
+
schema: RestErrorBodySchema
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
registry.registerPath({
|
|
641
|
+
method: 'get',
|
|
642
|
+
path: '/v1/posts/by-slug/{slug}/meta',
|
|
643
|
+
tags: ['Posts'],
|
|
644
|
+
description: 'Post SEO / Open Graph metadata (matches GraphQL `GetPostMeta`).',
|
|
645
|
+
request: {
|
|
646
|
+
params: V1PostBySlugPathParamsSchema
|
|
647
|
+
},
|
|
648
|
+
responses: {
|
|
649
|
+
200: {
|
|
650
|
+
description: 'Success',
|
|
651
|
+
content: {
|
|
652
|
+
'application/json': {
|
|
653
|
+
schema: V1PostMetaBodySchema
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
404: {
|
|
658
|
+
description: 'Not found',
|
|
659
|
+
content: {
|
|
660
|
+
'application/json': {
|
|
661
|
+
schema: RestErrorBodySchema
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
registry.registerPath({
|
|
668
|
+
method: 'get',
|
|
669
|
+
path: '/v1/posts/by-slug/{slug}/essay-questions',
|
|
670
|
+
tags: ['Posts'],
|
|
671
|
+
description: 'Post card plus essay questions (matches GraphQL `GetPostEssayQuestions`).',
|
|
672
|
+
request: {
|
|
673
|
+
params: V1PostBySlugPathParamsSchema
|
|
674
|
+
},
|
|
675
|
+
responses: {
|
|
676
|
+
200: {
|
|
677
|
+
description: 'Success',
|
|
678
|
+
content: {
|
|
679
|
+
'application/json': {
|
|
680
|
+
schema: V1PostEssayQuestionsBodySchema
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
404: {
|
|
685
|
+
description: 'Not found',
|
|
686
|
+
content: {
|
|
687
|
+
'application/json': {
|
|
688
|
+
schema: RestErrorBodySchema
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
registry.registerPath({
|
|
695
|
+
method: 'get',
|
|
696
|
+
path: '/v1/posts/essay-answers-with-likes',
|
|
697
|
+
tags: ['Posts'],
|
|
698
|
+
description: 'Paged posts with nested essay answers and member avatars (matches GraphQL `GetPostsEssayAnswersWithLikes`). Flat query: `take`, `skip`, `orderBy` (`publishedDate:desc` only), `answerTake`, `answerOrderBy` (`likesCount:desc` | `createdAt:desc`). Post filter (posts with answered essay questions) is fixed server-side.',
|
|
699
|
+
request: {
|
|
700
|
+
query: V1PostsEssayAnswersWithLikesQuerySchema
|
|
701
|
+
},
|
|
702
|
+
responses: {
|
|
703
|
+
200: {
|
|
704
|
+
description: 'Success',
|
|
705
|
+
content: {
|
|
706
|
+
'application/json': {
|
|
707
|
+
schema: V1PostsEssayAnswersWithLikesResponseSchema
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
400: {
|
|
712
|
+
description: 'Bad request',
|
|
713
|
+
content: {
|
|
714
|
+
'application/json': {
|
|
715
|
+
schema: RestErrorBodySchema
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
export * from './content-post-detail.js';
|
|
722
|
+
//# sourceMappingURL=content.js.map
|