@platformatic/sql-openapi 3.4.1 → 3.5.0
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/README.md +1 -1
- package/eslint.config.js +2 -2
- package/index.d.ts +3 -0
- package/index.js +33 -34
- package/lib/cursor.js +94 -0
- package/lib/entity-to-routes.js +252 -202
- package/lib/errors.js +25 -8
- package/lib/many-to-many.js +99 -81
- package/lib/shared.js +220 -135
- package/lib/utils.js +2 -8
- package/package.json +21 -18
package/lib/entity-to-routes.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { findNearestString } from '@platformatic/foundation'
|
|
2
|
+
import { mapSQLTypeToOpenAPIType } from '@platformatic/sql-json-schema-mapper'
|
|
3
|
+
import camelcase from 'camelcase'
|
|
4
|
+
import {
|
|
5
|
+
UnableToCreateTheRouteForThePKColRelationshipError,
|
|
6
|
+
UnableToCreateTheRouteForTheReverseRelationshipError
|
|
7
|
+
} from './errors.js'
|
|
8
|
+
import { capitalize, generateArgs, getFieldsForEntity, rootEntityRoutes } from './shared.js'
|
|
9
|
+
|
|
10
|
+
function getEntityLinksForEntity (app, entity) {
|
|
10
11
|
const entityLinks = {}
|
|
11
12
|
for (const relation of entity.relations) {
|
|
12
13
|
const ownField = camelcase(relation.column_name)
|
|
@@ -17,8 +18,8 @@ const getEntityLinksForEntity = (app, entity) => {
|
|
|
17
18
|
entityLinks[getEntityById] = {
|
|
18
19
|
operationId: `get${relatedEntity.name}By${relatedEntityPrimaryKeyCamelcaseCapitalized}`,
|
|
19
20
|
parameters: {
|
|
20
|
-
[relatedEntityPrimaryKeyCamelcase]: `$response.body#/${ownField}
|
|
21
|
-
}
|
|
21
|
+
[relatedEntityPrimaryKeyCamelcase]: `$response.body#/${ownField}`
|
|
22
|
+
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -34,35 +35,34 @@ const getEntityLinksForEntity = (app, entity) => {
|
|
|
34
35
|
entityLinks[getEntities] = {
|
|
35
36
|
operationId: `get${capitalize(relatedEntity.pluralName)}`,
|
|
36
37
|
parameters: {
|
|
37
|
-
[`where.${theirField}.eq`]: `$response.body#/${ownField}
|
|
38
|
-
}
|
|
38
|
+
[`where.${theirField}.eq`]: `$response.body#/${ownField}`
|
|
39
|
+
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
return entityLinks
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
async function entityPlugin (app, opts) {
|
|
45
|
+
export async function entityPlugin (app, opts) {
|
|
45
46
|
const entity = opts.entity
|
|
46
47
|
const ignore = opts.ignore
|
|
47
48
|
const ignoreRoutes = opts.ignoreRoutes
|
|
48
49
|
|
|
49
50
|
const entitySchema = {
|
|
50
|
-
$ref: entity.name + '#'
|
|
51
|
+
$ref: entity.name + '#'
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const entitySchemaInput = {
|
|
54
|
-
$ref: entity.name + 'Input#'
|
|
55
|
+
$ref: entity.name + 'Input#'
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
const entityFieldsNames = Object.values(entity.fields)
|
|
58
|
-
.map(field => field.camelcase)
|
|
58
|
+
const entityFieldsNames = Object.values(entity.fields).map(field => field.camelcase)
|
|
59
59
|
|
|
60
60
|
for (const ignoredField of Object.keys(ignore)) {
|
|
61
61
|
if (!entityFieldsNames.includes(ignoredField)) {
|
|
62
62
|
const nearestField = findNearestString(entityFieldsNames, ignoredField)
|
|
63
63
|
app.log.warn(
|
|
64
64
|
`Ignored openapi field "${ignoredField}" not found in entity "${entity.singularName}".` +
|
|
65
|
-
|
|
65
|
+
` Did you mean "${nearestField}"?`
|
|
66
66
|
)
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -74,7 +74,7 @@ async function entityPlugin (app, opts) {
|
|
|
74
74
|
|
|
75
75
|
const { whereArgs, orderByArgs } = generateArgs(entity, ignore)
|
|
76
76
|
|
|
77
|
-
app.addHook('preValidation', async
|
|
77
|
+
app.addHook('preValidation', async req => {
|
|
78
78
|
if (typeof req.query.fields === 'string') {
|
|
79
79
|
req.query.fields = req.query.fields.split(',')
|
|
80
80
|
}
|
|
@@ -82,7 +82,17 @@ async function entityPlugin (app, opts) {
|
|
|
82
82
|
|
|
83
83
|
const fields = getFieldsForEntity(entity, ignore)
|
|
84
84
|
|
|
85
|
-
rootEntityRoutes(
|
|
85
|
+
rootEntityRoutes(
|
|
86
|
+
app,
|
|
87
|
+
entity,
|
|
88
|
+
whereArgs,
|
|
89
|
+
orderByArgs,
|
|
90
|
+
entityLinks,
|
|
91
|
+
entitySchema,
|
|
92
|
+
fields,
|
|
93
|
+
entitySchemaInput,
|
|
94
|
+
ignoreRoutes
|
|
95
|
+
)
|
|
86
96
|
|
|
87
97
|
const openapiPath = `${app.prefix}/{${primaryKeyCamelcase}}`
|
|
88
98
|
const ignoredGETRoute = ignoreRoutes.find(ignoreRoute => {
|
|
@@ -90,42 +100,46 @@ async function entityPlugin (app, opts) {
|
|
|
90
100
|
})
|
|
91
101
|
|
|
92
102
|
if (!ignoredGETRoute) {
|
|
93
|
-
app.get(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
app.get(
|
|
104
|
+
`/:${primaryKeyCamelcase}`,
|
|
105
|
+
{
|
|
106
|
+
schema: {
|
|
107
|
+
operationId: `get${entity.name}By${capitalize(primaryKeyCamelcase)}`,
|
|
108
|
+
summary: `Get ${entity.name} by ${primaryKeyCamelcase}.`,
|
|
109
|
+
description: `Fetch ${entity.name} using its ${primaryKeyCamelcase} from the database.`,
|
|
110
|
+
params: primaryKeyParams,
|
|
111
|
+
tags: [entity.table],
|
|
112
|
+
querystring: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
properties: {
|
|
115
|
+
fields
|
|
116
|
+
}
|
|
104
117
|
},
|
|
118
|
+
response: {
|
|
119
|
+
200: entitySchema
|
|
120
|
+
}
|
|
105
121
|
},
|
|
106
|
-
|
|
107
|
-
200:
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
links: {
|
|
111
|
-
200: entityLinks,
|
|
122
|
+
links: {
|
|
123
|
+
200: entityLinks
|
|
124
|
+
}
|
|
112
125
|
},
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
async function (request, reply) {
|
|
127
|
+
const ctx = { app: this, reply }
|
|
128
|
+
const res = await entity.find({
|
|
129
|
+
ctx,
|
|
130
|
+
where: {
|
|
131
|
+
[primaryKeyCamelcase]: {
|
|
132
|
+
eq: request.params[primaryKeyCamelcase]
|
|
133
|
+
}
|
|
120
134
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
fields: request.query.fields
|
|
136
|
+
})
|
|
137
|
+
if (res.length === 0) {
|
|
138
|
+
return reply.callNotFound()
|
|
139
|
+
}
|
|
140
|
+
return res[0]
|
|
126
141
|
}
|
|
127
|
-
|
|
128
|
-
})
|
|
142
|
+
)
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
const mapRoutePathNamesReverseRelations = new Map()
|
|
@@ -136,15 +150,16 @@ async function entityPlugin (app, opts) {
|
|
|
136
150
|
const targetEntity = app.platformatic.entities[targetEntityName]
|
|
137
151
|
const targetForeignKeyCamelcase = camelcase(reverseRelationship.relation.column_name)
|
|
138
152
|
const targetEntitySchema = {
|
|
139
|
-
$ref: targetEntity.name + '#'
|
|
153
|
+
$ref: targetEntity.name + '#'
|
|
140
154
|
}
|
|
141
155
|
const entityLinks = getEntityLinksForEntity(app, targetEntity)
|
|
142
156
|
// e.g. getQuotesForMovie
|
|
143
157
|
const operationId = `get${capitalize(targetEntity.pluralName)}For${capitalize(entity.singularName)}`
|
|
144
158
|
|
|
145
|
-
let routePathName =
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
let routePathName =
|
|
160
|
+
targetEntity.relations.length > 1
|
|
161
|
+
? camelcase([reverseRelationship.sourceEntity, targetForeignKeyCamelcase])
|
|
162
|
+
: targetEntity.pluralName
|
|
148
163
|
|
|
149
164
|
if (mapRoutePathNamesReverseRelations.get(routePathName)) {
|
|
150
165
|
idxRoutePathNamesReverseRelations++
|
|
@@ -160,69 +175,96 @@ async function entityPlugin (app, opts) {
|
|
|
160
175
|
|
|
161
176
|
if (!ignoredReversedGETRoute) {
|
|
162
177
|
try {
|
|
163
|
-
app.get(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
app.get(
|
|
179
|
+
`/:${camelcase(primaryKey)}/${routePathName}`,
|
|
180
|
+
{
|
|
181
|
+
schema: {
|
|
182
|
+
operationId,
|
|
183
|
+
summary: `Get ${targetEntity.pluralName} for ${entity.singularName}.`,
|
|
184
|
+
description: `Fetch all the ${targetEntity.pluralName} for ${entity.singularName} from the database.`,
|
|
185
|
+
params: getPrimaryKeyParams(entity, ignore),
|
|
186
|
+
tags: [entity.table],
|
|
187
|
+
querystring: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
limit: {
|
|
191
|
+
type: 'integer',
|
|
192
|
+
description:
|
|
193
|
+
'Limit will be applied by default if not passed. If the provided value exceeds the maximum allowed value a validation error will be thrown'
|
|
194
|
+
},
|
|
195
|
+
offset: { type: 'integer' },
|
|
196
|
+
fields: getFieldsForEntity(targetEntity, ignore),
|
|
197
|
+
totalCount: { type: 'boolean', default: false }
|
|
198
|
+
}
|
|
180
199
|
},
|
|
200
|
+
response: {
|
|
201
|
+
200: {
|
|
202
|
+
type: 'array',
|
|
203
|
+
items: targetEntitySchema
|
|
204
|
+
}
|
|
205
|
+
}
|
|
181
206
|
},
|
|
207
|
+
links: {
|
|
208
|
+
200: entityLinks
|
|
209
|
+
}
|
|
182
210
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// get the related entities
|
|
206
|
-
const res = await targetEntity.find({
|
|
207
|
-
ctx,
|
|
208
|
-
where: {
|
|
211
|
+
async function (request, reply) {
|
|
212
|
+
const { limit, offset, fields } = request.query
|
|
213
|
+
const ctx = { app: this, reply }
|
|
214
|
+
// IF we want to have HTTP/404 in case the entity does not exist
|
|
215
|
+
// we need to do 2 queries. One to check if the entity exists. the other to get the related entities
|
|
216
|
+
// Improvement: this could be also done with a single query with a join,
|
|
217
|
+
|
|
218
|
+
// check that the entity exists
|
|
219
|
+
const resEntity = await entity.count({
|
|
220
|
+
ctx,
|
|
221
|
+
where: {
|
|
222
|
+
[primaryKeyCamelcase]: {
|
|
223
|
+
eq: request.params[primaryKeyCamelcase]
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
if (resEntity === 0) {
|
|
228
|
+
return reply.callNotFound()
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const where = {
|
|
209
232
|
[targetForeignKeyCamelcase]: {
|
|
210
|
-
eq: request.params[primaryKeyCamelcase]
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
eq: request.params[primaryKeyCamelcase]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// get the related entities
|
|
238
|
+
const res = await targetEntity.find({
|
|
239
|
+
ctx,
|
|
240
|
+
where,
|
|
241
|
+
fields,
|
|
242
|
+
limit,
|
|
243
|
+
offset
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
// X-Total-Count header
|
|
247
|
+
if (request.query.totalCount) {
|
|
248
|
+
let totalCount
|
|
249
|
+
if (((offset ?? 0) === 0 || res.length > 0) && limit !== undefined && res.length < limit) {
|
|
250
|
+
totalCount = (offset ?? 0) + res.length
|
|
251
|
+
} else {
|
|
252
|
+
totalCount = await targetEntity.count({ where, ctx })
|
|
253
|
+
}
|
|
254
|
+
reply.header('X-Total-Count', totalCount)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (res.length === 0) {
|
|
258
|
+
// This is a query on a FK, so
|
|
259
|
+
return []
|
|
260
|
+
}
|
|
261
|
+
return res
|
|
219
262
|
}
|
|
220
|
-
|
|
221
|
-
})
|
|
263
|
+
)
|
|
222
264
|
} catch (error) /* istanbul ignore next */ {
|
|
223
265
|
app.log.error(error)
|
|
224
266
|
app.log.info({ routePathName, targetEntityName, targetEntitySchema, operationId })
|
|
225
|
-
throw new
|
|
267
|
+
throw new UnableToCreateTheRouteForTheReverseRelationshipError()
|
|
226
268
|
}
|
|
227
269
|
}
|
|
228
270
|
}
|
|
@@ -246,7 +288,7 @@ async function entityPlugin (app, opts) {
|
|
|
246
288
|
}
|
|
247
289
|
|
|
248
290
|
const targetEntitySchema = {
|
|
249
|
-
$ref: targetEntity.name + '#'
|
|
291
|
+
$ref: targetEntity.name + '#'
|
|
250
292
|
}
|
|
251
293
|
const entityLinks = getEntityLinksForEntity(app, targetEntity)
|
|
252
294
|
// e.g. getMovieForQuote
|
|
@@ -260,62 +302,68 @@ async function entityPlugin (app, opts) {
|
|
|
260
302
|
|
|
261
303
|
if (!ignoredReversedGETRoute) {
|
|
262
304
|
try {
|
|
263
|
-
app.get(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
305
|
+
app.get(
|
|
306
|
+
`/:${camelcase(primaryKey)}/${targetRelation}`,
|
|
307
|
+
{
|
|
308
|
+
schema: {
|
|
309
|
+
operationId,
|
|
310
|
+
summary: `Get ${targetEntity.singularName} for ${entity.singularName}.`,
|
|
311
|
+
description: `Fetch the ${targetEntity.singularName} for ${entity.singularName} from the database.`,
|
|
312
|
+
params: getPrimaryKeyParams(entity, ignore),
|
|
313
|
+
tags: [entity.table],
|
|
314
|
+
querystring: {
|
|
315
|
+
type: 'object',
|
|
316
|
+
properties: {
|
|
317
|
+
fields: getFieldsForEntity(targetEntity, ignore)
|
|
318
|
+
}
|
|
274
319
|
},
|
|
320
|
+
response: {
|
|
321
|
+
200: targetEntitySchema
|
|
322
|
+
}
|
|
275
323
|
},
|
|
276
|
-
|
|
277
|
-
200:
|
|
278
|
-
}
|
|
279
|
-
},
|
|
280
|
-
links: {
|
|
281
|
-
200: entityLinks,
|
|
324
|
+
links: {
|
|
325
|
+
200: entityLinks
|
|
326
|
+
}
|
|
282
327
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
328
|
+
async function (request, reply) {
|
|
329
|
+
const ctx = { app: this, reply }
|
|
330
|
+
// check that the entity exists
|
|
331
|
+
const resEntity = (
|
|
332
|
+
await entity.find({
|
|
333
|
+
ctx,
|
|
334
|
+
where: {
|
|
335
|
+
[primaryKeyCamelcase]: {
|
|
336
|
+
eq: request.params[primaryKeyCamelcase]
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
)[0]
|
|
341
|
+
|
|
342
|
+
if (!resEntity) {
|
|
343
|
+
return reply.callNotFound()
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// get the related entity
|
|
347
|
+
const res = await targetEntity.find({
|
|
348
|
+
ctx,
|
|
349
|
+
where: {
|
|
350
|
+
[targetForeignKeyCamelcase]: {
|
|
351
|
+
eq: resEntity[targetColumnCamelcase]
|
|
352
|
+
}
|
|
291
353
|
},
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!resEntity) {
|
|
296
|
-
return reply.callNotFound()
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// get the related entity
|
|
300
|
-
const res = await targetEntity.find({
|
|
301
|
-
ctx,
|
|
302
|
-
where: {
|
|
303
|
-
[targetForeignKeyCamelcase]: {
|
|
304
|
-
eq: resEntity[targetColumnCamelcase],
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
fields: request.query.fields,
|
|
308
|
-
})
|
|
354
|
+
fields: request.query.fields
|
|
355
|
+
})
|
|
309
356
|
|
|
310
|
-
|
|
311
|
-
|
|
357
|
+
if (res.length === 0) {
|
|
358
|
+
return reply.callNotFound()
|
|
359
|
+
}
|
|
360
|
+
return res[0]
|
|
312
361
|
}
|
|
313
|
-
|
|
314
|
-
})
|
|
362
|
+
)
|
|
315
363
|
} catch (error) /* istanbul ignore next */ {
|
|
316
364
|
app.log.error(error)
|
|
317
365
|
app.log.info({ primaryKey, targetRelation, targetEntitySchema, targetEntityName, targetEntity, operationId })
|
|
318
|
-
throw new
|
|
366
|
+
throw new UnableToCreateTheRouteForThePKColRelationshipError()
|
|
319
367
|
}
|
|
320
368
|
}
|
|
321
369
|
}
|
|
@@ -337,15 +385,15 @@ async function entityPlugin (app, opts) {
|
|
|
337
385
|
querystring: {
|
|
338
386
|
type: 'object',
|
|
339
387
|
properties: {
|
|
340
|
-
fields
|
|
341
|
-
}
|
|
388
|
+
fields
|
|
389
|
+
}
|
|
342
390
|
},
|
|
343
391
|
response: {
|
|
344
|
-
200: entitySchema
|
|
345
|
-
}
|
|
392
|
+
200: entitySchema
|
|
393
|
+
}
|
|
346
394
|
},
|
|
347
395
|
links: {
|
|
348
|
-
200: entityLinks
|
|
396
|
+
200: entityLinks
|
|
349
397
|
},
|
|
350
398
|
async handler (request, reply) {
|
|
351
399
|
const id = request.params[primaryKeyCamelcase]
|
|
@@ -354,18 +402,18 @@ async function entityPlugin (app, opts) {
|
|
|
354
402
|
ctx,
|
|
355
403
|
input: {
|
|
356
404
|
...request.body,
|
|
357
|
-
[primaryKeyCamelcase]: id
|
|
405
|
+
[primaryKeyCamelcase]: id
|
|
358
406
|
},
|
|
359
407
|
where: {
|
|
360
408
|
[primaryKeyCamelcase]: {
|
|
361
|
-
eq: id
|
|
362
|
-
}
|
|
409
|
+
eq: id
|
|
410
|
+
}
|
|
363
411
|
},
|
|
364
|
-
fields: request.query.fields
|
|
412
|
+
fields: request.query.fields
|
|
365
413
|
})
|
|
366
414
|
reply.header('location', `${app.prefix}/${res[primaryKeyCamelcase]}`)
|
|
367
415
|
return res
|
|
368
|
-
}
|
|
416
|
+
}
|
|
369
417
|
})
|
|
370
418
|
}
|
|
371
419
|
|
|
@@ -373,39 +421,43 @@ async function entityPlugin (app, opts) {
|
|
|
373
421
|
return ignoreRoute.path === openapiPath && ignoreRoute.method === 'DELETE'
|
|
374
422
|
})
|
|
375
423
|
if (!ignoredDELETERoute) {
|
|
376
|
-
app.delete(
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
424
|
+
app.delete(
|
|
425
|
+
`/:${primaryKeyCamelcase}`,
|
|
426
|
+
{
|
|
427
|
+
schema: {
|
|
428
|
+
operationId: 'delete' + capitalize(entity.pluralName),
|
|
429
|
+
summary: `Delete ${entity.pluralName}.`,
|
|
430
|
+
description: `Delete one or more ${entity.pluralName} from the Database.`,
|
|
431
|
+
params: primaryKeyParams,
|
|
432
|
+
tags: [entity.table],
|
|
433
|
+
querystring: {
|
|
434
|
+
type: 'object',
|
|
435
|
+
properties: {
|
|
436
|
+
fields
|
|
437
|
+
}
|
|
387
438
|
},
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
439
|
+
response: {
|
|
440
|
+
200: entitySchema
|
|
441
|
+
}
|
|
442
|
+
}
|
|
392
443
|
},
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
444
|
+
async function (request, reply) {
|
|
445
|
+
const ctx = { app: this, reply }
|
|
446
|
+
const res = await entity.delete({
|
|
447
|
+
ctx,
|
|
448
|
+
where: {
|
|
449
|
+
[primaryKeyCamelcase]: {
|
|
450
|
+
eq: request.params[primaryKeyCamelcase]
|
|
451
|
+
}
|
|
400
452
|
},
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
453
|
+
fields: request.query.fields
|
|
454
|
+
})
|
|
455
|
+
if (res.length === 0) {
|
|
456
|
+
return reply.callNotFound()
|
|
457
|
+
}
|
|
458
|
+
return res[0]
|
|
406
459
|
}
|
|
407
|
-
|
|
408
|
-
})
|
|
460
|
+
)
|
|
409
461
|
}
|
|
410
462
|
}
|
|
411
463
|
|
|
@@ -414,13 +466,11 @@ function getPrimaryKeyParams (entity, ignore) {
|
|
|
414
466
|
const fields = entity.fields
|
|
415
467
|
const field = fields[primaryKey]
|
|
416
468
|
const properties = {
|
|
417
|
-
[field.camelcase]: { type: mapSQLTypeToOpenAPIType(field.sqlType, ignore) }
|
|
469
|
+
[field.camelcase]: { type: mapSQLTypeToOpenAPIType(field.sqlType, ignore) }
|
|
418
470
|
}
|
|
419
471
|
|
|
420
472
|
return {
|
|
421
473
|
type: 'object',
|
|
422
|
-
properties
|
|
474
|
+
properties
|
|
423
475
|
}
|
|
424
476
|
}
|
|
425
|
-
|
|
426
|
-
module.exports = entityPlugin
|
package/lib/errors.js
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
import createError from '@fastify/error'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
export const ERROR_PREFIX = 'PLT_SQL_OPENAPI'
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
5
|
+
export const UnableToCreateTheRouteForTheReverseRelationshipError = createError(
|
|
6
|
+
`${ERROR_PREFIX}_UNABLE_CREATE_ROUTE_FOR_REVERSE_RELATIONSHIP`,
|
|
7
|
+
'Unable to create the route for the reverse relationship'
|
|
8
|
+
)
|
|
9
|
+
export const UnableToCreateTheRouteForThePKColRelationshipError = createError(
|
|
10
|
+
`${ERROR_PREFIX}_UNABLE_CREATE_ROUTE_FOR_PK_COL_RELATIONSHIP`,
|
|
11
|
+
'Unable to create the route for the PK col relationship'
|
|
12
|
+
)
|
|
13
|
+
export const UnableToParseCursorStrError = createError(
|
|
14
|
+
`${ERROR_PREFIX}_UNABLE_TO_PARSE_CURSOR_STR`,
|
|
15
|
+
'Unable to parse cursor string. Make sure to provide valid encoding of cursor object. Error: %s',
|
|
16
|
+
400
|
|
17
|
+
)
|
|
18
|
+
export const CursorValidationError = createError(
|
|
19
|
+
`${ERROR_PREFIX}_CURSOR_VALIDATION_ERROR`,
|
|
20
|
+
'Cursor validation error. %s',
|
|
21
|
+
400
|
|
22
|
+
)
|
|
23
|
+
export const PrimaryKeyNotIncludedInOrderByInCursorPaginationError = createError(
|
|
24
|
+
`${ERROR_PREFIX}_PRIMARY_KEY_NOT_INCLUDED_IN_ORDER_BY_IN_CURSOR_PAGINATION`,
|
|
25
|
+
'At least one primary key must be included in orderBy clause in case of cursor pagination',
|
|
26
|
+
400
|
|
27
|
+
)
|