rads-db 3.2.21 → 3.2.23
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.
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getOpenApiSpec = getOpenApiSpec;
|
|
7
|
+
function primitiveToOas(type) {
|
|
8
|
+
switch (type) {
|
|
9
|
+
case "string":
|
|
10
|
+
return {
|
|
11
|
+
type: "string"
|
|
12
|
+
};
|
|
13
|
+
case "number":
|
|
14
|
+
return {
|
|
15
|
+
type: "number"
|
|
16
|
+
};
|
|
17
|
+
case "boolean":
|
|
18
|
+
return {
|
|
19
|
+
type: "boolean"
|
|
20
|
+
};
|
|
21
|
+
case "Record<string, string>":
|
|
22
|
+
return {
|
|
23
|
+
type: "object",
|
|
24
|
+
additionalProperties: {
|
|
25
|
+
type: "string"
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
case "Record<string, any>":
|
|
29
|
+
return {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: true
|
|
32
|
+
};
|
|
33
|
+
default:
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function relationToOas(field, schema) {
|
|
38
|
+
const properties = {
|
|
39
|
+
id: {
|
|
40
|
+
type: "string"
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
for (const fname of field.relationDenormFields ?? []) {
|
|
44
|
+
const relField = schema[field.type]?.fields?.[fname];
|
|
45
|
+
properties[fname] = relField ? primitiveToOas(relField.type) : {};
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
type: "object",
|
|
49
|
+
required: ["id"],
|
|
50
|
+
properties
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function fieldToOas(field, schema) {
|
|
54
|
+
const entry = schema[field.type];
|
|
55
|
+
let itemSchema;
|
|
56
|
+
if (field.isRelation) {
|
|
57
|
+
itemSchema = relationToOas(field, schema);
|
|
58
|
+
} else if (entry) {
|
|
59
|
+
itemSchema = entry.enumValues ? {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: Object.keys(entry.enumValues)
|
|
62
|
+
} : {
|
|
63
|
+
$ref: `#/components/schemas/${field.type}`
|
|
64
|
+
};
|
|
65
|
+
} else {
|
|
66
|
+
itemSchema = primitiveToOas(field.type);
|
|
67
|
+
}
|
|
68
|
+
return field.isArray ? {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: itemSchema
|
|
71
|
+
} : itemSchema;
|
|
72
|
+
}
|
|
73
|
+
function buildObjectSchema(entity, schema, allOptional = false) {
|
|
74
|
+
const properties = {};
|
|
75
|
+
const required = [];
|
|
76
|
+
for (const [name, field] of Object.entries(entity.fields ?? {})) {
|
|
77
|
+
if (field.isInverseRelation) continue;
|
|
78
|
+
properties[name] = {
|
|
79
|
+
...fieldToOas(field, schema),
|
|
80
|
+
...(field.comment ? {
|
|
81
|
+
description: field.comment
|
|
82
|
+
} : {})
|
|
83
|
+
};
|
|
84
|
+
if (!allOptional && field.isRequired) required.push(name);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
type: "object",
|
|
88
|
+
...(entity.comment ? {
|
|
89
|
+
description: entity.comment
|
|
90
|
+
} : {}),
|
|
91
|
+
properties,
|
|
92
|
+
...(required.length > 0 ? {
|
|
93
|
+
required
|
|
94
|
+
} : {})
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const whereSchema = {
|
|
98
|
+
type: "object",
|
|
99
|
+
additionalProperties: true
|
|
100
|
+
};
|
|
101
|
+
const includeSchema = {
|
|
102
|
+
type: "object",
|
|
103
|
+
additionalProperties: true
|
|
104
|
+
};
|
|
105
|
+
const paginationProps = {
|
|
106
|
+
cursor: {
|
|
107
|
+
type: "string"
|
|
108
|
+
},
|
|
109
|
+
maxItemCount: {
|
|
110
|
+
type: "integer"
|
|
111
|
+
},
|
|
112
|
+
orderBy: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: 'Single order-by expression, e.g. "name_asc"'
|
|
115
|
+
},
|
|
116
|
+
orderByArray: {
|
|
117
|
+
type: "array",
|
|
118
|
+
items: {
|
|
119
|
+
type: "string"
|
|
120
|
+
},
|
|
121
|
+
description: "Multiple order-by expressions"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const getRequestSchema = {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
where: whereSchema,
|
|
128
|
+
include: includeSchema
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const getManyRequestSchema = {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
where: whereSchema,
|
|
135
|
+
include: includeSchema,
|
|
136
|
+
...paginationProps
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const getAggRequestSchema = {
|
|
140
|
+
type: "object",
|
|
141
|
+
required: ["agg"],
|
|
142
|
+
properties: {
|
|
143
|
+
where: whereSchema,
|
|
144
|
+
agg: {
|
|
145
|
+
type: "array",
|
|
146
|
+
items: {
|
|
147
|
+
type: "string"
|
|
148
|
+
},
|
|
149
|
+
description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
|
|
150
|
+
},
|
|
151
|
+
...paginationProps
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
function jsonBody(s) {
|
|
155
|
+
return {
|
|
156
|
+
required: true,
|
|
157
|
+
content: {
|
|
158
|
+
"application/json": {
|
|
159
|
+
schema: s
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function jsonOk(s, description = "Success") {
|
|
165
|
+
return {
|
|
166
|
+
"200": {
|
|
167
|
+
description,
|
|
168
|
+
content: {
|
|
169
|
+
"application/json": {
|
|
170
|
+
schema: s
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function getOpenApiSpec(options, openApiOptions) {
|
|
177
|
+
const {
|
|
178
|
+
db,
|
|
179
|
+
prefix = "/api/"
|
|
180
|
+
} = options;
|
|
181
|
+
const schema = db._schema;
|
|
182
|
+
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
183
|
+
const paths = {};
|
|
184
|
+
const schemas = {};
|
|
185
|
+
paths[`${base}/me`] = {
|
|
186
|
+
get: {
|
|
187
|
+
summary: "Get current user",
|
|
188
|
+
tags: ["Auth"],
|
|
189
|
+
responses: jsonOk({
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
userId: {
|
|
193
|
+
type: "string",
|
|
194
|
+
nullable: true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
paths[`${base}/uploadFile`] = {
|
|
201
|
+
post: {
|
|
202
|
+
summary: "Upload a file",
|
|
203
|
+
tags: ["Files"],
|
|
204
|
+
requestBody: jsonBody({
|
|
205
|
+
type: "object",
|
|
206
|
+
required: ["blobDataUrl"],
|
|
207
|
+
properties: {
|
|
208
|
+
blobDataUrl: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "Base64 data URL: data:<mime>;base64,<data>"
|
|
211
|
+
},
|
|
212
|
+
filename: {
|
|
213
|
+
type: "string"
|
|
214
|
+
},
|
|
215
|
+
containerName: {
|
|
216
|
+
type: "string"
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}),
|
|
220
|
+
responses: jsonOk({
|
|
221
|
+
type: "object",
|
|
222
|
+
required: ["url"],
|
|
223
|
+
properties: {
|
|
224
|
+
url: {
|
|
225
|
+
type: "string"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
paths[`${base}/radsTunnel`] = {
|
|
232
|
+
post: {
|
|
233
|
+
summary: "Direct database method access",
|
|
234
|
+
tags: ["Tunnel"],
|
|
235
|
+
requestBody: jsonBody({
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
method: {
|
|
239
|
+
type: "string",
|
|
240
|
+
description: "Method to call (getMany, put, _schema, uploadFile, \u2026)"
|
|
241
|
+
},
|
|
242
|
+
entity: {
|
|
243
|
+
type: "string",
|
|
244
|
+
description: "Entity class name, e.g. TcUser"
|
|
245
|
+
},
|
|
246
|
+
args: {
|
|
247
|
+
description: "Arguments forwarded to the method"
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
responses: jsonOk({})
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
for (const key in schema) {
|
|
255
|
+
const entity = schema[key];
|
|
256
|
+
if (!entity.decorators?.entity) continue;
|
|
257
|
+
const {
|
|
258
|
+
name,
|
|
259
|
+
handle,
|
|
260
|
+
handlePlural
|
|
261
|
+
} = entity;
|
|
262
|
+
const tag = name;
|
|
263
|
+
schemas[name] = buildObjectSchema(entity, schema);
|
|
264
|
+
schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
|
|
265
|
+
schemas[`${name}Put`].required = ["id"];
|
|
266
|
+
const ref = n => ({
|
|
267
|
+
$ref: `#/components/schemas/${n}`
|
|
268
|
+
});
|
|
269
|
+
paths[`${base}/${handle}`] = {
|
|
270
|
+
post: {
|
|
271
|
+
summary: `Get ${name}`,
|
|
272
|
+
tags: [tag],
|
|
273
|
+
requestBody: jsonBody(getRequestSchema),
|
|
274
|
+
responses: jsonOk(ref(name), `${name} object`)
|
|
275
|
+
},
|
|
276
|
+
put: {
|
|
277
|
+
summary: `Create or update ${name}`,
|
|
278
|
+
tags: [tag],
|
|
279
|
+
requestBody: jsonBody({
|
|
280
|
+
type: "object",
|
|
281
|
+
required: ["data"],
|
|
282
|
+
properties: {
|
|
283
|
+
data: ref(`${name}Put`)
|
|
284
|
+
}
|
|
285
|
+
}),
|
|
286
|
+
responses: jsonOk(ref(name), `Updated ${name}`)
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
paths[`${base}/${handlePlural}`] = {
|
|
290
|
+
post: {
|
|
291
|
+
summary: `Get many ${name} objects`,
|
|
292
|
+
tags: [tag],
|
|
293
|
+
requestBody: jsonBody(getManyRequestSchema),
|
|
294
|
+
responses: jsonOk({
|
|
295
|
+
type: "object",
|
|
296
|
+
required: ["nodes"],
|
|
297
|
+
properties: {
|
|
298
|
+
nodes: {
|
|
299
|
+
type: "array",
|
|
300
|
+
items: ref(name)
|
|
301
|
+
},
|
|
302
|
+
cursor: {
|
|
303
|
+
type: "string",
|
|
304
|
+
nullable: true
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}, `Paginated list of ${name}`)
|
|
308
|
+
},
|
|
309
|
+
put: {
|
|
310
|
+
summary: `Create or update many ${name} objects`,
|
|
311
|
+
tags: [tag],
|
|
312
|
+
requestBody: jsonBody({
|
|
313
|
+
type: "object",
|
|
314
|
+
required: ["data"],
|
|
315
|
+
properties: {
|
|
316
|
+
data: {
|
|
317
|
+
type: "array",
|
|
318
|
+
items: ref(`${name}Put`)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
responses: jsonOk({
|
|
323
|
+
type: "array",
|
|
324
|
+
items: ref(name)
|
|
325
|
+
}, `Updated ${name} list`)
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
paths[`${base}/${handle}/agg`] = {
|
|
329
|
+
post: {
|
|
330
|
+
summary: `Aggregate ${name} data`,
|
|
331
|
+
tags: [tag],
|
|
332
|
+
requestBody: jsonBody(getAggRequestSchema),
|
|
333
|
+
responses: jsonOk({
|
|
334
|
+
type: "object",
|
|
335
|
+
additionalProperties: {
|
|
336
|
+
type: "number",
|
|
337
|
+
nullable: true
|
|
338
|
+
}
|
|
339
|
+
}, `${name} aggregation result`)
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
for (const key in schema) {
|
|
344
|
+
const entry = schema[key];
|
|
345
|
+
if (entry.decorators?.entity || schemas[key]) continue;
|
|
346
|
+
if (entry.enumValues) {
|
|
347
|
+
schemas[key] = {
|
|
348
|
+
type: "string",
|
|
349
|
+
enum: Object.keys(entry.enumValues),
|
|
350
|
+
...(entry.comment ? {
|
|
351
|
+
description: entry.comment
|
|
352
|
+
} : {})
|
|
353
|
+
};
|
|
354
|
+
} else if (entry.fields) {
|
|
355
|
+
schemas[key] = buildObjectSchema(entry, schema);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
openapi: "3.0.3",
|
|
360
|
+
info: {
|
|
361
|
+
title: openApiOptions?.title ?? "Rads DB API",
|
|
362
|
+
version: openApiOptions?.version ?? "1.0.0",
|
|
363
|
+
...(openApiOptions?.description ? {
|
|
364
|
+
description: openApiOptions.description
|
|
365
|
+
} : {})
|
|
366
|
+
},
|
|
367
|
+
...(openApiOptions?.servers ? {
|
|
368
|
+
servers: openApiOptions.servers
|
|
369
|
+
} : {}),
|
|
370
|
+
paths,
|
|
371
|
+
components: {
|
|
372
|
+
schemas
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GetRestRoutesOptions } from '../types';
|
|
2
|
+
export interface OpenApiSpecOptions {
|
|
3
|
+
title?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
servers?: {
|
|
7
|
+
url: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
export declare function getOpenApiSpec(options: GetRestRoutesOptions, openApiOptions?: OpenApiSpecOptions): object;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
function primitiveToOas(type) {
|
|
2
|
+
switch (type) {
|
|
3
|
+
case "string":
|
|
4
|
+
return { type: "string" };
|
|
5
|
+
case "number":
|
|
6
|
+
return { type: "number" };
|
|
7
|
+
case "boolean":
|
|
8
|
+
return { type: "boolean" };
|
|
9
|
+
case "Record<string, string>":
|
|
10
|
+
return { type: "object", additionalProperties: { type: "string" } };
|
|
11
|
+
case "Record<string, any>":
|
|
12
|
+
return { type: "object", additionalProperties: true };
|
|
13
|
+
default:
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function relationToOas(field, schema) {
|
|
18
|
+
const properties = { id: { type: "string" } };
|
|
19
|
+
for (const fname of field.relationDenormFields ?? []) {
|
|
20
|
+
const relField = schema[field.type]?.fields?.[fname];
|
|
21
|
+
properties[fname] = relField ? primitiveToOas(relField.type) : {};
|
|
22
|
+
}
|
|
23
|
+
return { type: "object", required: ["id"], properties };
|
|
24
|
+
}
|
|
25
|
+
function fieldToOas(field, schema) {
|
|
26
|
+
const entry = schema[field.type];
|
|
27
|
+
let itemSchema;
|
|
28
|
+
if (field.isRelation) {
|
|
29
|
+
itemSchema = relationToOas(field, schema);
|
|
30
|
+
} else if (entry) {
|
|
31
|
+
itemSchema = entry.enumValues ? { type: "string", enum: Object.keys(entry.enumValues) } : { $ref: `#/components/schemas/${field.type}` };
|
|
32
|
+
} else {
|
|
33
|
+
itemSchema = primitiveToOas(field.type);
|
|
34
|
+
}
|
|
35
|
+
return field.isArray ? { type: "array", items: itemSchema } : itemSchema;
|
|
36
|
+
}
|
|
37
|
+
function buildObjectSchema(entity, schema, allOptional = false) {
|
|
38
|
+
const properties = {};
|
|
39
|
+
const required = [];
|
|
40
|
+
for (const [name, field] of Object.entries(entity.fields ?? {})) {
|
|
41
|
+
if (field.isInverseRelation) continue;
|
|
42
|
+
properties[name] = {
|
|
43
|
+
...fieldToOas(field, schema),
|
|
44
|
+
...field.comment ? { description: field.comment } : {}
|
|
45
|
+
};
|
|
46
|
+
if (!allOptional && field.isRequired) required.push(name);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
type: "object",
|
|
50
|
+
...entity.comment ? { description: entity.comment } : {},
|
|
51
|
+
properties,
|
|
52
|
+
...required.length > 0 ? { required } : {}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const whereSchema = { type: "object", additionalProperties: true };
|
|
56
|
+
const includeSchema = { type: "object", additionalProperties: true };
|
|
57
|
+
const paginationProps = {
|
|
58
|
+
cursor: { type: "string" },
|
|
59
|
+
maxItemCount: { type: "integer" },
|
|
60
|
+
orderBy: { type: "string", description: 'Single order-by expression, e.g. "name_asc"' },
|
|
61
|
+
orderByArray: { type: "array", items: { type: "string" }, description: "Multiple order-by expressions" }
|
|
62
|
+
};
|
|
63
|
+
const getRequestSchema = {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: { where: whereSchema, include: includeSchema }
|
|
66
|
+
};
|
|
67
|
+
const getManyRequestSchema = {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: { where: whereSchema, include: includeSchema, ...paginationProps }
|
|
70
|
+
};
|
|
71
|
+
const getAggRequestSchema = {
|
|
72
|
+
type: "object",
|
|
73
|
+
required: ["agg"],
|
|
74
|
+
properties: {
|
|
75
|
+
where: whereSchema,
|
|
76
|
+
agg: {
|
|
77
|
+
type: "array",
|
|
78
|
+
items: { type: "string" },
|
|
79
|
+
description: 'Aggregation operations: "_count", "field_min", "field_max", "field_sum"'
|
|
80
|
+
},
|
|
81
|
+
...paginationProps
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function jsonBody(s) {
|
|
85
|
+
return { required: true, content: { "application/json": { schema: s } } };
|
|
86
|
+
}
|
|
87
|
+
function jsonOk(s, description = "Success") {
|
|
88
|
+
return { "200": { description, content: { "application/json": { schema: s } } } };
|
|
89
|
+
}
|
|
90
|
+
export function getOpenApiSpec(options, openApiOptions) {
|
|
91
|
+
const { db, prefix = "/api/" } = options;
|
|
92
|
+
const schema = db._schema;
|
|
93
|
+
const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
94
|
+
const paths = {};
|
|
95
|
+
const schemas = {};
|
|
96
|
+
paths[`${base}/me`] = {
|
|
97
|
+
get: {
|
|
98
|
+
summary: "Get current user",
|
|
99
|
+
tags: ["Auth"],
|
|
100
|
+
responses: jsonOk({
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: { userId: { type: "string", nullable: true } }
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
paths[`${base}/uploadFile`] = {
|
|
107
|
+
post: {
|
|
108
|
+
summary: "Upload a file",
|
|
109
|
+
tags: ["Files"],
|
|
110
|
+
requestBody: jsonBody({
|
|
111
|
+
type: "object",
|
|
112
|
+
required: ["blobDataUrl"],
|
|
113
|
+
properties: {
|
|
114
|
+
blobDataUrl: { type: "string", description: "Base64 data URL: data:<mime>;base64,<data>" },
|
|
115
|
+
filename: { type: "string" },
|
|
116
|
+
containerName: { type: "string" }
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
responses: jsonOk({
|
|
120
|
+
type: "object",
|
|
121
|
+
required: ["url"],
|
|
122
|
+
properties: { url: { type: "string" } }
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
paths[`${base}/radsTunnel`] = {
|
|
127
|
+
post: {
|
|
128
|
+
summary: "Direct database method access",
|
|
129
|
+
tags: ["Tunnel"],
|
|
130
|
+
requestBody: jsonBody({
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
method: { type: "string", description: "Method to call (getMany, put, _schema, uploadFile, \u2026)" },
|
|
134
|
+
entity: { type: "string", description: "Entity class name, e.g. TcUser" },
|
|
135
|
+
args: { description: "Arguments forwarded to the method" }
|
|
136
|
+
}
|
|
137
|
+
}),
|
|
138
|
+
responses: jsonOk({})
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
for (const key in schema) {
|
|
142
|
+
const entity = schema[key];
|
|
143
|
+
if (!entity.decorators?.entity) continue;
|
|
144
|
+
const { name, handle, handlePlural } = entity;
|
|
145
|
+
const tag = name;
|
|
146
|
+
schemas[name] = buildObjectSchema(entity, schema);
|
|
147
|
+
schemas[`${name}Put`] = buildObjectSchema(entity, schema, true);
|
|
148
|
+
schemas[`${name}Put`].required = ["id"];
|
|
149
|
+
const ref = (n) => ({ $ref: `#/components/schemas/${n}` });
|
|
150
|
+
paths[`${base}/${handle}`] = {
|
|
151
|
+
post: {
|
|
152
|
+
summary: `Get ${name}`,
|
|
153
|
+
tags: [tag],
|
|
154
|
+
requestBody: jsonBody(getRequestSchema),
|
|
155
|
+
responses: jsonOk(ref(name), `${name} object`)
|
|
156
|
+
},
|
|
157
|
+
put: {
|
|
158
|
+
summary: `Create or update ${name}`,
|
|
159
|
+
tags: [tag],
|
|
160
|
+
requestBody: jsonBody({
|
|
161
|
+
type: "object",
|
|
162
|
+
required: ["data"],
|
|
163
|
+
properties: { data: ref(`${name}Put`) }
|
|
164
|
+
}),
|
|
165
|
+
responses: jsonOk(ref(name), `Updated ${name}`)
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
paths[`${base}/${handlePlural}`] = {
|
|
169
|
+
post: {
|
|
170
|
+
summary: `Get many ${name} objects`,
|
|
171
|
+
tags: [tag],
|
|
172
|
+
requestBody: jsonBody(getManyRequestSchema),
|
|
173
|
+
responses: jsonOk(
|
|
174
|
+
{
|
|
175
|
+
type: "object",
|
|
176
|
+
required: ["nodes"],
|
|
177
|
+
properties: {
|
|
178
|
+
nodes: { type: "array", items: ref(name) },
|
|
179
|
+
cursor: { type: "string", nullable: true }
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
`Paginated list of ${name}`
|
|
183
|
+
)
|
|
184
|
+
},
|
|
185
|
+
put: {
|
|
186
|
+
summary: `Create or update many ${name} objects`,
|
|
187
|
+
tags: [tag],
|
|
188
|
+
requestBody: jsonBody({
|
|
189
|
+
type: "object",
|
|
190
|
+
required: ["data"],
|
|
191
|
+
properties: { data: { type: "array", items: ref(`${name}Put`) } }
|
|
192
|
+
}),
|
|
193
|
+
responses: jsonOk({ type: "array", items: ref(name) }, `Updated ${name} list`)
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
paths[`${base}/${handle}/agg`] = {
|
|
197
|
+
post: {
|
|
198
|
+
summary: `Aggregate ${name} data`,
|
|
199
|
+
tags: [tag],
|
|
200
|
+
requestBody: jsonBody(getAggRequestSchema),
|
|
201
|
+
responses: jsonOk(
|
|
202
|
+
{ type: "object", additionalProperties: { type: "number", nullable: true } },
|
|
203
|
+
`${name} aggregation result`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
for (const key in schema) {
|
|
209
|
+
const entry = schema[key];
|
|
210
|
+
if (entry.decorators?.entity || schemas[key]) continue;
|
|
211
|
+
if (entry.enumValues) {
|
|
212
|
+
schemas[key] = {
|
|
213
|
+
type: "string",
|
|
214
|
+
enum: Object.keys(entry.enumValues),
|
|
215
|
+
...entry.comment ? { description: entry.comment } : {}
|
|
216
|
+
};
|
|
217
|
+
} else if (entry.fields) {
|
|
218
|
+
schemas[key] = buildObjectSchema(entry, schema);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
openapi: "3.0.3",
|
|
223
|
+
info: {
|
|
224
|
+
title: openApiOptions?.title ?? "Rads DB API",
|
|
225
|
+
version: openApiOptions?.version ?? "1.0.0",
|
|
226
|
+
...openApiOptions?.description ? { description: openApiOptions.description } : {}
|
|
227
|
+
},
|
|
228
|
+
...openApiOptions?.servers ? { servers: openApiOptions.servers } : {},
|
|
229
|
+
paths,
|
|
230
|
+
components: { schemas }
|
|
231
|
+
};
|
|
232
|
+
}
|