Skip to content

Commit

Permalink
feat: add openapi routes ignore
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-tymoshenko committed Mar 12, 2023
1 parent 0501b14 commit 394bd18
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 247 deletions.
19 changes: 18 additions & 1 deletion docs/reference/sql-openapi/ignore.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,24 @@ To ignore individual fields:
app.register(require('@platformatic/sql-openapi'), {
ignore: {
categories: {
name: true
fields: {
name: true
}
}
}
})
```

To ignore individual routes:

```javascript
app.register(require('@platformatic/sql-openapi'), {
ignore: {
categories: {
routes: {
GET: ['/categories/'],
POST: ['/categories/:id']
}
}
}
})
Expand Down
22 changes: 21 additions & 1 deletion packages/db/lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,27 @@ const core = {
type: 'object',
// TODO add support for column-level ignore
additionalProperties: {
type: 'boolean'
oneOf: [{
type: 'boolean'
}, {
type: 'object',
properties: {
routes: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' }
}
},
fields: {
type: 'object',
additionalProperties: {
type: 'boolean'
}
},
additionalProperties: false
}
}]
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion packages/sql-openapi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ async function setupOpenAPI (app, opts) {
})

for (const entity of Object.values(app.platformatic.entities)) {
const entitySchema = mapSQLEntityToJSONSchema(entity, ignore[entity.pluralName])
const ignoredFields = ignore[entity.pluralName]?.fields
const entitySchema = mapSQLEntityToJSONSchema(entity, ignoredFields)
// TODO remove reverseRelationships from the entity
/* istanbul ignore next */
entity.reverseRelationships = entity.reverseRelationships || []
Expand Down
150 changes: 82 additions & 68 deletions packages/sql-openapi/lib/entity-to-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,61 +41,65 @@ const getEntityLinksForEntity = (app, entity) => {

async function entityPlugin (app, opts) {
const entity = opts.entity
const ignore = opts.ignore
const ignoredFields = opts.ignore.fields || []
const ignoredRoutes = opts.ignore.routes || {}

const entitySchema = {
$ref: entity.name + '#'
}
const primaryKey = entity.primaryKeys.values().next().value
const primaryKeyParams = getPrimaryKeyParams(entity, ignore)
const primaryKeyParams = getPrimaryKeyParams(entity, ignoredFields)
const primaryKeyCamelcase = camelcase(primaryKey)
const entityLinks = getEntityLinksForEntity(app, entity)

const { whereArgs, orderByArgs } = generateArgs(entity, ignore)
const { whereArgs, orderByArgs } = generateArgs(entity, ignoredFields)

app.addHook('preValidation', async (req) => {
if (typeof req.query.fields === 'string') {
req.query.fields = req.query.fields.split(',')
}
})

const fields = getFieldsForEntity(entity, ignore)
const fields = getFieldsForEntity(entity, ignoredFields)

rootEntityRoutes(app, entity, whereArgs, orderByArgs, entityLinks, entitySchema, fields)
rootEntityRoutes(app, entity, whereArgs, orderByArgs, entityLinks, entitySchema, fields, ignoredRoutes)

app.get(`/:${primaryKeyCamelcase}`, {
schema: {
operationId: `get${entity.name}By${capitalize(primaryKeyCamelcase)}`,
params: primaryKeyParams,
querystring: {
type: 'object',
properties: {
fields
const entityByPrimaryKeyUrl = `/:${primaryKeyCamelcase}`
if (!ignoredRoutes.GET?.includes(app.prefix + entityByPrimaryKeyUrl)) {
app.get(entityByPrimaryKeyUrl, {
schema: {
operationId: `get${entity.name}By${capitalize(primaryKeyCamelcase)}`,
params: primaryKeyParams,
querystring: {
type: 'object',
properties: {
fields
}
},
response: {
200: entitySchema
}
},
response: {
200: entitySchema
links: {
200: entityLinks
}
},
links: {
200: entityLinks
}
}, async function (request, reply) {
const ctx = { app: this, reply }
const res = await entity.find({
ctx,
where: {
[primaryKeyCamelcase]: {
eq: request.params[primaryKeyCamelcase]
}
},
fields: request.query.fields
}, async function (request, reply) {
const ctx = { app: this, reply }
const res = await entity.find({
ctx,
where: {
[primaryKeyCamelcase]: {
eq: request.params[primaryKeyCamelcase]
}
},
fields: request.query.fields
})
if (res.length === 0) {
return reply.callNotFound()
}
return res[0]
})
if (res.length === 0) {
return reply.callNotFound()
}
return res[0]
})
}

const mapRoutePathNamesReverseRelations = new Map()
let idxRoutePathNamesReverseRelations = 1
Expand All @@ -122,15 +126,18 @@ async function entityPlugin (app, opts) {
mapRoutePathNamesReverseRelations.set(routePathName, true)
}

const entityUrl = `/:${camelcase(primaryKey)}/${routePathName}`
if (ignoredRoutes.GET?.includes(app.prefix + entityUrl)) continue

try {
app.get(`/:${camelcase(primaryKey)}/${routePathName}`, {
schema: {
operationId,
params: getPrimaryKeyParams(entity, ignore),
params: getPrimaryKeyParams(entity, ignoredFields),
querystring: {
type: 'object',
properties: {
fields: getFieldsForEntity(targetEntity, ignore)
fields: getFieldsForEntity(targetEntity, ignoredFields)
}
},
response: {
Expand Down Expand Up @@ -204,6 +211,9 @@ async function entityPlugin (app, opts) {
mapRoutePathNamesRelations.set(targetRelation, true)
}

const entityUrl = `/:${camelcase(primaryKey)}/${targetRelation}`
if (ignoredRoutes.GET?.includes(app.prefix + entityUrl)) continue

const targetEntitySchema = {
$ref: targetEntity.name + '#'
}
Expand All @@ -212,14 +222,14 @@ async function entityPlugin (app, opts) {
const operationId = `get${capitalize(targetEntity.singularName)}For${capitalize(entity.singularName)}`
// We need to get the relation name from the PK column:
try {
app.get(`/:${camelcase(primaryKey)}/${targetRelation}`, {
app.get(entityUrl, {
schema: {
operationId,
params: getPrimaryKeyParams(entity, ignore),
params: getPrimaryKeyParams(entity, ignoredFields),
querystring: {
type: 'object',
properties: {
fields: getFieldsForEntity(targetEntity, ignore)
fields: getFieldsForEntity(targetEntity, ignoredFields)
}
},
response: {
Expand Down Expand Up @@ -269,8 +279,10 @@ async function entityPlugin (app, opts) {
}

for (const method of ['POST', 'PUT']) {
if (ignoredRoutes[method]?.includes(app.prefix + entityByPrimaryKeyUrl)) continue

app.route({
url: `/:${primaryKeyCamelcase}`,
url: entityByPrimaryKeyUrl,
method,
schema: {
body: entitySchema,
Expand Down Expand Up @@ -310,43 +322,45 @@ async function entityPlugin (app, opts) {
})
}

app.delete(`/:${primaryKeyCamelcase}`, {
schema: {
params: primaryKeyParams,
querystring: {
type: 'object',
properties: {
fields
if (!ignoredRoutes.DELETE?.includes(app.prefix + entityByPrimaryKeyUrl)) {
app.delete(entityByPrimaryKeyUrl, {
schema: {
params: primaryKeyParams,
querystring: {
type: 'object',
properties: {
fields
}
},
response: {
200: entitySchema
}
},
response: {
200: entitySchema
}
}
}, async function (request, reply) {
const ctx = { app: this, reply }
const res = await entity.delete({
ctx,
where: {
[primaryKeyCamelcase]: {
eq: request.params[primaryKeyCamelcase]
}
},
fields: request.query.fields
}, async function (request, reply) {
const ctx = { app: this, reply }
const res = await entity.delete({
ctx,
where: {
[primaryKeyCamelcase]: {
eq: request.params[primaryKeyCamelcase]
}
},
fields: request.query.fields
})
if (res.length === 0) {
return reply.callNotFound()
}
return res[0]
})
if (res.length === 0) {
return reply.callNotFound()
}
return res[0]
})
}
}

function getPrimaryKeyParams (entity, ignore) {
function getPrimaryKeyParams (entity, ignoredFields) {
const primaryKey = entity.primaryKeys.values().next().value
const fields = entity.fields
const field = fields[primaryKey]
const properties = {
[field.camelcase]: { type: mapSQLTypeToOpenAPIType(field.sqlType, ignore) }
[field.camelcase]: { type: mapSQLTypeToOpenAPIType(field.sqlType, ignoredFields) }
}
const required = [field.camelcase]

Expand Down
Loading

0 comments on commit 394bd18

Please sign in to comment.