@twreporter-academy/api-types 0.0.2 → 0.0.3
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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/openapi.d.ts +4 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +5 -1
- package/dist/openapi.js.map +1 -1
- package/dist/schemas/course.d.ts +89 -0
- package/dist/schemas/course.d.ts.map +1 -0
- package/dist/schemas/course.js +147 -0
- package/dist/schemas/course.js.map +1 -0
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from './api-variables.js'\nexport * from './openapi.js'\nexport * from './rest.js'\nexport * from './schemas/member.js'\nexport * from './schemas/tag.js'\n"],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,cAAc;AAC5B,cAAc,WAAW;AACzB,cAAc,qBAAqB;AACnC,cAAc,kBAAkB","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from './api-variables.js'\nexport * from './openapi.js'\nexport * from './rest.js'\nexport * from './schemas/course.js'\nexport * from './schemas/member.js'\nexport * from './schemas/tag.js'\n"],"mappings":"AAAA,cAAc,oBAAoB;AAClC,cAAc,cAAc;AAC5B,cAAc,WAAW;AACzB,cAAc,qBAAqB;AACnC,cAAc,qBAAqB;AACnC,cAAc,kBAAkB","ignoreList":[]}
|
package/dist/openapi.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import './schemas/course.js';
|
|
1
2
|
import './schemas/member.js';
|
|
2
3
|
export declare function getOpenApiDocument(): {
|
|
3
4
|
tags: ({
|
|
@@ -9,6 +10,9 @@ export declare function getOpenApiDocument(): {
|
|
|
9
10
|
} | {
|
|
10
11
|
readonly name: "Tags";
|
|
11
12
|
readonly description: "Tag metadata and course feeds by slug.";
|
|
13
|
+
} | {
|
|
14
|
+
readonly name: "Courses";
|
|
15
|
+
readonly description: "Course metadata and classroom detail.";
|
|
12
16
|
})[];
|
|
13
17
|
openapi: string;
|
|
14
18
|
info: import("openapi3-ts/oas30").InfoObject;
|
package/dist/openapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AACA,OAAO,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"AACA,OAAO,qBAAqB,CAAA;AAC5B,OAAO,qBAAqB,CAAA;AAa5B,wBAAgB,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAcjC"}
|
package/dist/openapi.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Side-effect imports: ensure OpenAPI paths are registered before generation.
|
|
2
|
+
import './schemas/course.js';
|
|
2
3
|
import './schemas/member.js';
|
|
3
4
|
import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
|
|
4
5
|
import { registry } from './schemas/tag.js';
|
|
@@ -11,6 +12,9 @@ const openApiRootTags = [{
|
|
|
11
12
|
}, {
|
|
12
13
|
name: 'Tags',
|
|
13
14
|
description: 'Tag metadata and course feeds by slug.'
|
|
15
|
+
}, {
|
|
16
|
+
name: 'Courses',
|
|
17
|
+
description: 'Course metadata and classroom detail.'
|
|
14
18
|
}];
|
|
15
19
|
export function getOpenApiDocument() {
|
|
16
20
|
const generator = new OpenApiGeneratorV3(registry.definitions);
|
|
@@ -20,7 +24,7 @@ export function getOpenApiDocument() {
|
|
|
20
24
|
info: {
|
|
21
25
|
title: 'TWReporter Academy API',
|
|
22
26
|
version: '0.0.1',
|
|
23
|
-
description: 'Academy content API. Serves tag metadata and
|
|
27
|
+
description: 'Academy content API. Serves tag metadata, course feeds, and classroom detail.'
|
|
24
28
|
}
|
|
25
29
|
}),
|
|
26
30
|
tags: [...openApiRootTags]
|
package/dist/openapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.js","names":["OpenApiGeneratorV3","registry","openApiRootTags","name","description","getOpenApiDocument","generator","definitions","generateDocument","openapi","info","title","version","tags"],"sources":["../src/openapi.ts"],"sourcesContent":["// Side-effect imports: ensure OpenAPI paths are registered before generation.\nimport './schemas/member.js'\n\nimport { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'\n\nimport { registry } from './schemas/tag.js'\n\nconst openApiRootTags = [\n { name: 'Auth', description: 'Token exchange and session management.' },\n { name: 'Members', description: 'Authenticated member profile.' },\n { name: 'Tags', description: 'Tag metadata and course feeds by slug.' },\n] as const\n\nexport function getOpenApiDocument() {\n const generator = new OpenApiGeneratorV3(registry.definitions)\n return {\n ...generator.generateDocument({\n openapi: '3.0.3',\n info: {\n title: 'TWReporter Academy API',\n version: '0.0.1',\n description:\n 'Academy content API. Serves tag metadata and
|
|
1
|
+
{"version":3,"file":"openapi.js","names":["OpenApiGeneratorV3","registry","openApiRootTags","name","description","getOpenApiDocument","generator","definitions","generateDocument","openapi","info","title","version","tags"],"sources":["../src/openapi.ts"],"sourcesContent":["// Side-effect imports: ensure OpenAPI paths are registered before generation.\nimport './schemas/course.js'\nimport './schemas/member.js'\n\nimport { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi'\n\nimport { registry } from './schemas/tag.js'\n\nconst openApiRootTags = [\n { name: 'Auth', description: 'Token exchange and session management.' },\n { name: 'Members', description: 'Authenticated member profile.' },\n { name: 'Tags', description: 'Tag metadata and course feeds by slug.' },\n { name: 'Courses', description: 'Course metadata and classroom detail.' },\n] as const\n\nexport function getOpenApiDocument() {\n const generator = new OpenApiGeneratorV3(registry.definitions)\n return {\n ...generator.generateDocument({\n openapi: '3.0.3',\n info: {\n title: 'TWReporter Academy API',\n version: '0.0.1',\n description:\n 'Academy content API. Serves tag metadata, course feeds, and classroom detail.',\n },\n }),\n tags: [...openApiRootTags],\n }\n}\n"],"mappings":"AAAA;AACA,OAAO,qBAAqB;AAC5B,OAAO,qBAAqB;AAE5B,SAASA,kBAAkB,QAAQ,gCAAgC;AAEnE,SAASC,QAAQ,QAAQ,kBAAkB;AAE3C,MAAMC,eAAe,GAAG,CACtB;EAAEC,IAAI,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAyC,CAAC,EACvE;EAAED,IAAI,EAAE,SAAS;EAAEC,WAAW,EAAE;AAAgC,CAAC,EACjE;EAAED,IAAI,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAyC,CAAC,EACvE;EAAED,IAAI,EAAE,SAAS;EAAEC,WAAW,EAAE;AAAwC,CAAC,CACjE;AAEV,OAAO,SAASC,kBAAkBA,CAAA,EAAG;EACnC,MAAMC,SAAS,GAAG,IAAIN,kBAAkB,CAACC,QAAQ,CAACM,WAAW,CAAC;EAC9D,OAAO;IACL,GAAGD,SAAS,CAACE,gBAAgB,CAAC;MAC5BC,OAAO,EAAE,OAAO;MAChBC,IAAI,EAAE;QACJC,KAAK,EAAE,wBAAwB;QAC/BC,OAAO,EAAE,OAAO;QAChBR,WAAW,EACT;MACJ;IACF,CAAC,CAAC;IACFS,IAAI,EAAE,CAAC,GAAGX,eAAe;EAC3B,CAAC;AACH","ignoreList":[]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const V1CourseSlugPathParamsSchema: z.ZodObject<{
|
|
3
|
+
slug: z.ZodString;
|
|
4
|
+
}, z.core.$strip>;
|
|
5
|
+
export declare const V1CourseBySlugMetaResponseSchema: z.ZodObject<{
|
|
6
|
+
ogTitle: z.ZodString;
|
|
7
|
+
ogDescription: z.ZodString;
|
|
8
|
+
ogImage: z.ZodNullable<z.ZodObject<{
|
|
9
|
+
resized: z.ZodObject<{
|
|
10
|
+
small: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const V1CourseLessonSchema: z.ZodObject<{
|
|
15
|
+
id: z.ZodString;
|
|
16
|
+
order: z.ZodNumber;
|
|
17
|
+
title: z.ZodString;
|
|
18
|
+
videoId: z.ZodNullable<z.ZodString>;
|
|
19
|
+
durationSeconds: z.ZodNullable<z.ZodNumber>;
|
|
20
|
+
isFree: z.ZodBoolean;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export declare const V1CourseInstructorSchema: z.ZodObject<{
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
slug: z.ZodString;
|
|
25
|
+
name: z.ZodString;
|
|
26
|
+
title: z.ZodString;
|
|
27
|
+
bio: z.ZodString;
|
|
28
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export declare const V1CourseResourceSchema: z.ZodObject<{
|
|
31
|
+
id: z.ZodString;
|
|
32
|
+
title: z.ZodString;
|
|
33
|
+
description: z.ZodString;
|
|
34
|
+
downloadUrl: z.ZodString;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export declare const V1CourseFurtherReadingSchema: z.ZodObject<{
|
|
37
|
+
id: z.ZodString;
|
|
38
|
+
title: z.ZodString;
|
|
39
|
+
excerpt: z.ZodString;
|
|
40
|
+
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
41
|
+
url: z.ZodString;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
export declare const V1CourseStatusSchema: z.ZodEnum<{
|
|
44
|
+
published: "published";
|
|
45
|
+
coming_soon: "coming_soon";
|
|
46
|
+
}>;
|
|
47
|
+
export declare const V1CourseBySlugResponseSchema: z.ZodObject<{
|
|
48
|
+
slug: z.ZodString;
|
|
49
|
+
title: z.ZodString;
|
|
50
|
+
subtitle: z.ZodString;
|
|
51
|
+
summary: z.ZodString;
|
|
52
|
+
description: z.ZodString;
|
|
53
|
+
status: z.ZodEnum<{
|
|
54
|
+
published: "published";
|
|
55
|
+
coming_soon: "coming_soon";
|
|
56
|
+
}>;
|
|
57
|
+
tags: z.ZodArray<z.ZodString>;
|
|
58
|
+
previewVideoId: z.ZodNullable<z.ZodString>;
|
|
59
|
+
lessons: z.ZodArray<z.ZodObject<{
|
|
60
|
+
id: z.ZodString;
|
|
61
|
+
order: z.ZodNumber;
|
|
62
|
+
title: z.ZodString;
|
|
63
|
+
videoId: z.ZodNullable<z.ZodString>;
|
|
64
|
+
durationSeconds: z.ZodNullable<z.ZodNumber>;
|
|
65
|
+
isFree: z.ZodBoolean;
|
|
66
|
+
}, z.core.$strip>>;
|
|
67
|
+
instructors: z.ZodArray<z.ZodObject<{
|
|
68
|
+
id: z.ZodString;
|
|
69
|
+
slug: z.ZodString;
|
|
70
|
+
name: z.ZodString;
|
|
71
|
+
title: z.ZodString;
|
|
72
|
+
bio: z.ZodString;
|
|
73
|
+
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
74
|
+
}, z.core.$strip>>;
|
|
75
|
+
resources: z.ZodArray<z.ZodObject<{
|
|
76
|
+
id: z.ZodString;
|
|
77
|
+
title: z.ZodString;
|
|
78
|
+
description: z.ZodString;
|
|
79
|
+
downloadUrl: z.ZodString;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
furtherReading: z.ZodArray<z.ZodObject<{
|
|
82
|
+
id: z.ZodString;
|
|
83
|
+
title: z.ZodString;
|
|
84
|
+
excerpt: z.ZodString;
|
|
85
|
+
thumbnailUrl: z.ZodNullable<z.ZodString>;
|
|
86
|
+
url: z.ZodString;
|
|
87
|
+
}, z.core.$strip>>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
//# sourceMappingURL=course.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course.d.ts","sourceRoot":"","sources":["../../src/schemas/course.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,eAAO,MAAM,4BAA4B;;iBAEvC,CAAA;AAEF,eAAO,MAAM,gCAAgC;;;;;;;;iBAI3C,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;iBAYP,CAAA;AAE1B,eAAO,MAAM,wBAAwB;;;;;;;iBASP,CAAA;AAE9B,eAAO,MAAM,sBAAsB;;;;;iBAQP,CAAA;AAE5B,eAAO,MAAM,4BAA4B;;;;;;iBAQP,CAAA;AAElC,eAAO,MAAM,oBAAoB;;;EAAuC,CAAA;AAExE,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiBf,CAAA"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { RestErrorBodySchema } from '../rest.js';
|
|
3
|
+
import { CourseThumbnailSchema, registry } from './tag.js';
|
|
4
|
+
export const V1CourseSlugPathParamsSchema = z.object({
|
|
5
|
+
slug: z.string().min(1).max(200)
|
|
6
|
+
});
|
|
7
|
+
export const V1CourseBySlugMetaResponseSchema = z.object({
|
|
8
|
+
ogTitle: z.string(),
|
|
9
|
+
ogDescription: z.string(),
|
|
10
|
+
ogImage: CourseThumbnailSchema.nullable()
|
|
11
|
+
});
|
|
12
|
+
export const V1CourseLessonSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
order: z.number().int(),
|
|
15
|
+
title: z.string(),
|
|
16
|
+
/** Cloudflare stream id for this lesson; null when not yet set */
|
|
17
|
+
videoId: z.string().nullable(),
|
|
18
|
+
/** Video length in seconds (from CMS); null when not yet set */
|
|
19
|
+
durationSeconds: z.number().int().nullable(),
|
|
20
|
+
/** CMS 免費試看 flag; playback access is decided by user role (out of scope here) */
|
|
21
|
+
isFree: z.boolean()
|
|
22
|
+
}).openapi('CourseLesson');
|
|
23
|
+
export const V1CourseInstructorSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
slug: z.string(),
|
|
26
|
+
name: z.string(),
|
|
27
|
+
title: z.string(),
|
|
28
|
+
bio: z.string(),
|
|
29
|
+
avatarUrl: z.string().nullable()
|
|
30
|
+
}).openapi('CourseInstructor');
|
|
31
|
+
export const V1CourseResourceSchema = z.object({
|
|
32
|
+
id: z.string(),
|
|
33
|
+
title: z.string(),
|
|
34
|
+
description: z.string(),
|
|
35
|
+
/** GCS file URL with ?download=true; empty when no PDF uploaded */
|
|
36
|
+
downloadUrl: z.string()
|
|
37
|
+
}).openapi('CourseResource');
|
|
38
|
+
export const V1CourseFurtherReadingSchema = z.object({
|
|
39
|
+
id: z.string(),
|
|
40
|
+
title: z.string(),
|
|
41
|
+
excerpt: z.string(),
|
|
42
|
+
thumbnailUrl: z.string().nullable(),
|
|
43
|
+
url: z.string()
|
|
44
|
+
}).openapi('CourseFurtherReading');
|
|
45
|
+
export const V1CourseStatusSchema = z.enum(['published', 'coming_soon']);
|
|
46
|
+
export const V1CourseBySlugResponseSchema = z.object({
|
|
47
|
+
slug: z.string(),
|
|
48
|
+
title: z.string(),
|
|
49
|
+
subtitle: z.string(),
|
|
50
|
+
summary: z.string(),
|
|
51
|
+
description: z.string(),
|
|
52
|
+
/** published: full payload; coming_soon: lessons/resources/furtherReading/previewVideoId stripped */
|
|
53
|
+
status: V1CourseStatusSchema,
|
|
54
|
+
tags: z.array(z.string()),
|
|
55
|
+
/** Trial/preview stream only; null when unconfigured or course is coming_soon */
|
|
56
|
+
previewVideoId: z.string().nullable(),
|
|
57
|
+
lessons: z.array(V1CourseLessonSchema),
|
|
58
|
+
instructors: z.array(V1CourseInstructorSchema),
|
|
59
|
+
resources: z.array(V1CourseResourceSchema),
|
|
60
|
+
furtherReading: z.array(V1CourseFurtherReadingSchema)
|
|
61
|
+
}).openapi('CourseDetail');
|
|
62
|
+
|
|
63
|
+
// --- OpenAPI path registration ---
|
|
64
|
+
|
|
65
|
+
const internalServerErrorResponse = {
|
|
66
|
+
500: {
|
|
67
|
+
description: 'Internal server error',
|
|
68
|
+
content: {
|
|
69
|
+
'application/json': {
|
|
70
|
+
schema: RestErrorBodySchema
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
registry.registerPath({
|
|
76
|
+
method: 'get',
|
|
77
|
+
path: '/v1/courses/{slug}/meta',
|
|
78
|
+
tags: ['Courses'],
|
|
79
|
+
description: 'OG metadata for a course page.',
|
|
80
|
+
request: {
|
|
81
|
+
params: V1CourseSlugPathParamsSchema
|
|
82
|
+
},
|
|
83
|
+
responses: {
|
|
84
|
+
200: {
|
|
85
|
+
description: 'Success',
|
|
86
|
+
content: {
|
|
87
|
+
'application/json': {
|
|
88
|
+
schema: V1CourseBySlugMetaResponseSchema
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
400: {
|
|
93
|
+
description: 'Bad request',
|
|
94
|
+
content: {
|
|
95
|
+
'application/json': {
|
|
96
|
+
schema: RestErrorBodySchema
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
404: {
|
|
101
|
+
description: 'Course not found',
|
|
102
|
+
content: {
|
|
103
|
+
'application/json': {
|
|
104
|
+
schema: RestErrorBodySchema
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
...internalServerErrorResponse
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
registry.registerPath({
|
|
112
|
+
method: 'get',
|
|
113
|
+
path: '/v1/courses/{slug}',
|
|
114
|
+
tags: ['Courses'],
|
|
115
|
+
description: 'Full course detail for the classroom page. When status is coming_soon, lessons, resources, furtherReading, and previewVideoId are empty/null while title, description, tags, and instructors remain visible.',
|
|
116
|
+
request: {
|
|
117
|
+
params: V1CourseSlugPathParamsSchema
|
|
118
|
+
},
|
|
119
|
+
responses: {
|
|
120
|
+
200: {
|
|
121
|
+
description: 'Success',
|
|
122
|
+
content: {
|
|
123
|
+
'application/json': {
|
|
124
|
+
schema: V1CourseBySlugResponseSchema
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
400: {
|
|
129
|
+
description: 'Bad request',
|
|
130
|
+
content: {
|
|
131
|
+
'application/json': {
|
|
132
|
+
schema: RestErrorBodySchema
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
404: {
|
|
137
|
+
description: 'Course not found',
|
|
138
|
+
content: {
|
|
139
|
+
'application/json': {
|
|
140
|
+
schema: RestErrorBodySchema
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
...internalServerErrorResponse
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=course.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course.js","names":["z","RestErrorBodySchema","CourseThumbnailSchema","registry","V1CourseSlugPathParamsSchema","object","slug","string","min","max","V1CourseBySlugMetaResponseSchema","ogTitle","ogDescription","ogImage","nullable","V1CourseLessonSchema","id","order","number","int","title","videoId","durationSeconds","isFree","boolean","openapi","V1CourseInstructorSchema","name","bio","avatarUrl","V1CourseResourceSchema","description","downloadUrl","V1CourseFurtherReadingSchema","excerpt","thumbnailUrl","url","V1CourseStatusSchema","enum","V1CourseBySlugResponseSchema","subtitle","summary","status","tags","array","previewVideoId","lessons","instructors","resources","furtherReading","internalServerErrorResponse","content","schema","registerPath","method","path","request","params","responses"],"sources":["../../src/schemas/course.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport { RestErrorBodySchema } from '../rest.js'\nimport { CourseThumbnailSchema, registry } from './tag.js'\n\nexport const V1CourseSlugPathParamsSchema = z.object({\n slug: z.string().min(1).max(200),\n})\n\nexport const V1CourseBySlugMetaResponseSchema = z.object({\n ogTitle: z.string(),\n ogDescription: z.string(),\n ogImage: CourseThumbnailSchema.nullable(),\n})\n\nexport const V1CourseLessonSchema = z\n .object({\n id: z.string(),\n order: z.number().int(),\n title: z.string(),\n /** Cloudflare stream id for this lesson; null when not yet set */\n videoId: z.string().nullable(),\n /** Video length in seconds (from CMS); null when not yet set */\n durationSeconds: z.number().int().nullable(),\n /** CMS 免費試看 flag; playback access is decided by user role (out of scope here) */\n isFree: z.boolean(),\n })\n .openapi('CourseLesson')\n\nexport const V1CourseInstructorSchema = z\n .object({\n id: z.string(),\n slug: z.string(),\n name: z.string(),\n title: z.string(),\n bio: z.string(),\n avatarUrl: z.string().nullable(),\n })\n .openapi('CourseInstructor')\n\nexport const V1CourseResourceSchema = z\n .object({\n id: z.string(),\n title: z.string(),\n description: z.string(),\n /** GCS file URL with ?download=true; empty when no PDF uploaded */\n downloadUrl: z.string(),\n })\n .openapi('CourseResource')\n\nexport const V1CourseFurtherReadingSchema = z\n .object({\n id: z.string(),\n title: z.string(),\n excerpt: z.string(),\n thumbnailUrl: z.string().nullable(),\n url: z.string(),\n })\n .openapi('CourseFurtherReading')\n\nexport const V1CourseStatusSchema = z.enum(['published', 'coming_soon'])\n\nexport const V1CourseBySlugResponseSchema = z\n .object({\n slug: z.string(),\n title: z.string(),\n subtitle: z.string(),\n summary: z.string(),\n description: z.string(),\n /** published: full payload; coming_soon: lessons/resources/furtherReading/previewVideoId stripped */\n status: V1CourseStatusSchema,\n tags: z.array(z.string()),\n /** Trial/preview stream only; null when unconfigured or course is coming_soon */\n previewVideoId: z.string().nullable(),\n lessons: z.array(V1CourseLessonSchema),\n instructors: z.array(V1CourseInstructorSchema),\n resources: z.array(V1CourseResourceSchema),\n furtherReading: z.array(V1CourseFurtherReadingSchema),\n })\n .openapi('CourseDetail')\n\n// --- OpenAPI path registration ---\n\nconst internalServerErrorResponse = {\n 500: {\n description: 'Internal server error',\n content: { 'application/json': { schema: RestErrorBodySchema } },\n },\n} as const\n\nregistry.registerPath({\n method: 'get',\n path: '/v1/courses/{slug}/meta',\n tags: ['Courses'],\n description: 'OG metadata for a course page.',\n request: { params: V1CourseSlugPathParamsSchema },\n responses: {\n 200: {\n description: 'Success',\n content: {\n 'application/json': { schema: V1CourseBySlugMetaResponseSchema },\n },\n },\n 400: {\n description: 'Bad request',\n content: { 'application/json': { schema: RestErrorBodySchema } },\n },\n 404: {\n description: 'Course not found',\n content: { 'application/json': { schema: RestErrorBodySchema } },\n },\n ...internalServerErrorResponse,\n },\n})\n\nregistry.registerPath({\n method: 'get',\n path: '/v1/courses/{slug}',\n tags: ['Courses'],\n description:\n 'Full course detail for the classroom page. When status is coming_soon, lessons, resources, furtherReading, and previewVideoId are empty/null while title, description, tags, and instructors remain visible.',\n request: { params: V1CourseSlugPathParamsSchema },\n responses: {\n 200: {\n description: 'Success',\n content: {\n 'application/json': { schema: V1CourseBySlugResponseSchema },\n },\n },\n 400: {\n description: 'Bad request',\n content: { 'application/json': { schema: RestErrorBodySchema } },\n },\n 404: {\n description: 'Course not found',\n content: { 'application/json': { schema: RestErrorBodySchema } },\n },\n ...internalServerErrorResponse,\n },\n})\n"],"mappings":"AAAA,SAASA,CAAC,QAAQ,KAAK;AAEvB,SAASC,mBAAmB,QAAQ,YAAY;AAChD,SAASC,qBAAqB,EAAEC,QAAQ,QAAQ,UAAU;AAE1D,OAAO,MAAMC,4BAA4B,GAAGJ,CAAC,CAACK,MAAM,CAAC;EACnDC,IAAI,EAAEN,CAAC,CAACO,MAAM,CAAC,CAAC,CAACC,GAAG,CAAC,CAAC,CAAC,CAACC,GAAG,CAAC,GAAG;AACjC,CAAC,CAAC;AAEF,OAAO,MAAMC,gCAAgC,GAAGV,CAAC,CAACK,MAAM,CAAC;EACvDM,OAAO,EAAEX,CAAC,CAACO,MAAM,CAAC,CAAC;EACnBK,aAAa,EAAEZ,CAAC,CAACO,MAAM,CAAC,CAAC;EACzBM,OAAO,EAAEX,qBAAqB,CAACY,QAAQ,CAAC;AAC1C,CAAC,CAAC;AAEF,OAAO,MAAMC,oBAAoB,GAAGf,CAAC,CAClCK,MAAM,CAAC;EACNW,EAAE,EAAEhB,CAAC,CAACO,MAAM,CAAC,CAAC;EACdU,KAAK,EAAEjB,CAAC,CAACkB,MAAM,CAAC,CAAC,CAACC,GAAG,CAAC,CAAC;EACvBC,KAAK,EAAEpB,CAAC,CAACO,MAAM,CAAC,CAAC;EACjB;EACAc,OAAO,EAAErB,CAAC,CAACO,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;EAC9B;EACAQ,eAAe,EAAEtB,CAAC,CAACkB,MAAM,CAAC,CAAC,CAACC,GAAG,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;EAC5C;EACAS,MAAM,EAAEvB,CAAC,CAACwB,OAAO,CAAC;AACpB,CAAC,CAAC,CACDC,OAAO,CAAC,cAAc,CAAC;AAE1B,OAAO,MAAMC,wBAAwB,GAAG1B,CAAC,CACtCK,MAAM,CAAC;EACNW,EAAE,EAAEhB,CAAC,CAACO,MAAM,CAAC,CAAC;EACdD,IAAI,EAAEN,CAAC,CAACO,MAAM,CAAC,CAAC;EAChBoB,IAAI,EAAE3B,CAAC,CAACO,MAAM,CAAC,CAAC;EAChBa,KAAK,EAAEpB,CAAC,CAACO,MAAM,CAAC,CAAC;EACjBqB,GAAG,EAAE5B,CAAC,CAACO,MAAM,CAAC,CAAC;EACfsB,SAAS,EAAE7B,CAAC,CAACO,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC;AACjC,CAAC,CAAC,CACDW,OAAO,CAAC,kBAAkB,CAAC;AAE9B,OAAO,MAAMK,sBAAsB,GAAG9B,CAAC,CACpCK,MAAM,CAAC;EACNW,EAAE,EAAEhB,CAAC,CAACO,MAAM,CAAC,CAAC;EACda,KAAK,EAAEpB,CAAC,CAACO,MAAM,CAAC,CAAC;EACjBwB,WAAW,EAAE/B,CAAC,CAACO,MAAM,CAAC,CAAC;EACvB;EACAyB,WAAW,EAAEhC,CAAC,CAACO,MAAM,CAAC;AACxB,CAAC,CAAC,CACDkB,OAAO,CAAC,gBAAgB,CAAC;AAE5B,OAAO,MAAMQ,4BAA4B,GAAGjC,CAAC,CAC1CK,MAAM,CAAC;EACNW,EAAE,EAAEhB,CAAC,CAACO,MAAM,CAAC,CAAC;EACda,KAAK,EAAEpB,CAAC,CAACO,MAAM,CAAC,CAAC;EACjB2B,OAAO,EAAElC,CAAC,CAACO,MAAM,CAAC,CAAC;EACnB4B,YAAY,EAAEnC,CAAC,CAACO,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;EACnCsB,GAAG,EAAEpC,CAAC,CAACO,MAAM,CAAC;AAChB,CAAC,CAAC,CACDkB,OAAO,CAAC,sBAAsB,CAAC;AAElC,OAAO,MAAMY,oBAAoB,GAAGrC,CAAC,CAACsC,IAAI,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;AAExE,OAAO,MAAMC,4BAA4B,GAAGvC,CAAC,CAC1CK,MAAM,CAAC;EACNC,IAAI,EAAEN,CAAC,CAACO,MAAM,CAAC,CAAC;EAChBa,KAAK,EAAEpB,CAAC,CAACO,MAAM,CAAC,CAAC;EACjBiC,QAAQ,EAAExC,CAAC,CAACO,MAAM,CAAC,CAAC;EACpBkC,OAAO,EAAEzC,CAAC,CAACO,MAAM,CAAC,CAAC;EACnBwB,WAAW,EAAE/B,CAAC,CAACO,MAAM,CAAC,CAAC;EACvB;EACAmC,MAAM,EAAEL,oBAAoB;EAC5BM,IAAI,EAAE3C,CAAC,CAAC4C,KAAK,CAAC5C,CAAC,CAACO,MAAM,CAAC,CAAC,CAAC;EACzB;EACAsC,cAAc,EAAE7C,CAAC,CAACO,MAAM,CAAC,CAAC,CAACO,QAAQ,CAAC,CAAC;EACrCgC,OAAO,EAAE9C,CAAC,CAAC4C,KAAK,CAAC7B,oBAAoB,CAAC;EACtCgC,WAAW,EAAE/C,CAAC,CAAC4C,KAAK,CAAClB,wBAAwB,CAAC;EAC9CsB,SAAS,EAAEhD,CAAC,CAAC4C,KAAK,CAACd,sBAAsB,CAAC;EAC1CmB,cAAc,EAAEjD,CAAC,CAAC4C,KAAK,CAACX,4BAA4B;AACtD,CAAC,CAAC,CACDR,OAAO,CAAC,cAAc,CAAC;;AAE1B;;AAEA,MAAMyB,2BAA2B,GAAG;EAClC,GAAG,EAAE;IACHnB,WAAW,EAAE,uBAAuB;IACpCoB,OAAO,EAAE;MAAE,kBAAkB,EAAE;QAAEC,MAAM,EAAEnD;MAAoB;IAAE;EACjE;AACF,CAAU;AAEVE,QAAQ,CAACkD,YAAY,CAAC;EACpBC,MAAM,EAAE,KAAK;EACbC,IAAI,EAAE,yBAAyB;EAC/BZ,IAAI,EAAE,CAAC,SAAS,CAAC;EACjBZ,WAAW,EAAE,gCAAgC;EAC7CyB,OAAO,EAAE;IAAEC,MAAM,EAAErD;EAA6B,CAAC;EACjDsD,SAAS,EAAE;IACT,GAAG,EAAE;MACH3B,WAAW,EAAE,SAAS;MACtBoB,OAAO,EAAE;QACP,kBAAkB,EAAE;UAAEC,MAAM,EAAE1C;QAAiC;MACjE;IACF,CAAC;IACD,GAAG,EAAE;MACHqB,WAAW,EAAE,aAAa;MAC1BoB,OAAO,EAAE;QAAE,kBAAkB,EAAE;UAAEC,MAAM,EAAEnD;QAAoB;MAAE;IACjE,CAAC;IACD,GAAG,EAAE;MACH8B,WAAW,EAAE,kBAAkB;MAC/BoB,OAAO,EAAE;QAAE,kBAAkB,EAAE;UAAEC,MAAM,EAAEnD;QAAoB;MAAE;IACjE,CAAC;IACD,GAAGiD;EACL;AACF,CAAC,CAAC;AAEF/C,QAAQ,CAACkD,YAAY,CAAC;EACpBC,MAAM,EAAE,KAAK;EACbC,IAAI,EAAE,oBAAoB;EAC1BZ,IAAI,EAAE,CAAC,SAAS,CAAC;EACjBZ,WAAW,EACT,8MAA8M;EAChNyB,OAAO,EAAE;IAAEC,MAAM,EAAErD;EAA6B,CAAC;EACjDsD,SAAS,EAAE;IACT,GAAG,EAAE;MACH3B,WAAW,EAAE,SAAS;MACtBoB,OAAO,EAAE;QACP,kBAAkB,EAAE;UAAEC,MAAM,EAAEb;QAA6B;MAC7D;IACF,CAAC;IACD,GAAG,EAAE;MACHR,WAAW,EAAE,aAAa;MAC1BoB,OAAO,EAAE;QAAE,kBAAkB,EAAE;UAAEC,MAAM,EAAEnD;QAAoB;MAAE;IACjE,CAAC;IACD,GAAG,EAAE;MACH8B,WAAW,EAAE,kBAAkB;MAC/BoB,OAAO,EAAE;QAAE,kBAAkB,EAAE;UAAEC,MAAM,EAAEnD;QAAoB;MAAE;IACjE,CAAC;IACD,GAAGiD;EACL;AACF,CAAC,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twreporter-academy/api-types",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "./scripts/build.sh",
|
|
20
20
|
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
21
|
-
"lint:check": "eslint src
|
|
22
|
-
"lint:fix": "eslint src --
|
|
21
|
+
"lint:check": "yarn run -T eslint src",
|
|
22
|
+
"lint:fix": "yarn run -T eslint src --fix",
|
|
23
23
|
"prepack": "yarn build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|