mirror of
https://github.com/strapi/strapi.git
synced 2025-12-24 05:34:33 +00:00
Merge pull request #12929 from strapi/fix/documentation-component-schemas
Add component schemas
This commit is contained in:
commit
8eec9c2d71
41
packages/plugins/documentation/__mocks__/strapi.js
Normal file
41
packages/plugins/documentation/__mocks__/strapi.js
Normal file
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const strapi = {
|
||||
plugins: {
|
||||
'users-permissions': {
|
||||
contentTypes: {
|
||||
role: {
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
'content-api': {
|
||||
routes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
api: {
|
||||
restaurant: {
|
||||
contentTypes: {
|
||||
restaurant: {
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
restaurant: { routes: [] },
|
||||
},
|
||||
},
|
||||
},
|
||||
contentType: () => ({ info: {}, attributes: { test: { type: 'string' } } }),
|
||||
};
|
||||
|
||||
module.exports = strapi;
|
||||
@ -0,0 +1,266 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const buildComponentSchema = require('../server/services/helpers/build-component-schema');
|
||||
const strapi = require('../__mocks__/strapi');
|
||||
|
||||
describe('Build Component Schema', () => {
|
||||
beforeEach(() => {
|
||||
// Reset the mocked strapi instance
|
||||
global.strapi = _.cloneDeep(strapi);
|
||||
});
|
||||
|
||||
it('builds the Response schema', () => {
|
||||
const apiMocks = [
|
||||
{
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['role'],
|
||||
},
|
||||
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
||||
];
|
||||
|
||||
let schemas = {};
|
||||
for (const mock of apiMocks) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
...buildComponentSchema(mock),
|
||||
};
|
||||
}
|
||||
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const [pluginResponseName, apiResponseName] = Object.keys(schemas);
|
||||
const [pluginResponseValue, apiResponseValue] = Object.values(schemas);
|
||||
|
||||
const expectedShape = {
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
attributes: { type: 'object', properties: { test: { type: 'string' } } },
|
||||
},
|
||||
},
|
||||
meta: { type: 'object' },
|
||||
},
|
||||
};
|
||||
|
||||
expect(schemaNames.length).toBe(2);
|
||||
expect(pluginResponseName).toBe('UsersPermissionsRoleResponse');
|
||||
expect(apiResponseName).toBe('RestaurantResponse');
|
||||
expect(pluginResponseValue).toStrictEqual(expectedShape);
|
||||
expect(apiResponseValue).toStrictEqual(expectedShape);
|
||||
});
|
||||
|
||||
it('builds the ResponseList schema', () => {
|
||||
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
||||
{ method: 'GET', path: '/test', handler: 'test.find' },
|
||||
];
|
||||
global.strapi.api.restaurant.routes.restaurant.routes = [
|
||||
{ method: 'GET', path: '/test', handler: 'test.find' },
|
||||
];
|
||||
|
||||
const apiMocks = [
|
||||
{
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['role'],
|
||||
},
|
||||
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
||||
];
|
||||
|
||||
let schemas = {};
|
||||
for (const mock of apiMocks) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
...buildComponentSchema(mock),
|
||||
};
|
||||
}
|
||||
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const pluginListResponseValue = schemas['UsersPermissionsRoleListResponse'];
|
||||
const apiListResponseValue = schemas['RestaurantListResponse'];
|
||||
|
||||
const expectedShape = {
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
attributes: { type: 'object', properties: { test: { type: 'string' } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pagination: {
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
pageSize: { type: 'integer', minimum: 25 },
|
||||
pageCount: { type: 'integer', maximum: 1 },
|
||||
total: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(schemaNames.length).toBe(4);
|
||||
expect(schemaNames.includes('UsersPermissionsRoleListResponse')).toBe(true);
|
||||
expect(schemaNames.includes('RestaurantListResponse')).toBe(true);
|
||||
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
||||
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
||||
});
|
||||
|
||||
it('builds the Request schema', () => {
|
||||
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
||||
{ method: 'POST', path: '/test', handler: 'test.create' },
|
||||
];
|
||||
global.strapi.api.restaurant.routes.restaurant.routes = [
|
||||
{ method: 'POST', path: '/test', handler: 'test.create' },
|
||||
];
|
||||
|
||||
const apiMocks = [
|
||||
{
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['role'],
|
||||
},
|
||||
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
||||
];
|
||||
|
||||
let schemas = {};
|
||||
for (const mock of apiMocks) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
...buildComponentSchema(mock),
|
||||
};
|
||||
}
|
||||
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const pluginListResponseValue = schemas['UsersPermissionsRoleRequest'];
|
||||
const apiListResponseValue = schemas['RestaurantRequest'];
|
||||
|
||||
const expectedShape = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: { test: { type: 'string' } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(schemaNames.length).toBe(4);
|
||||
expect(schemaNames.includes('UsersPermissionsRoleRequest')).toBe(true);
|
||||
expect(schemaNames.includes('RestaurantRequest')).toBe(true);
|
||||
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
||||
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
||||
});
|
||||
|
||||
it('builds the LocalizationResponse schema', () => {
|
||||
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
||||
{ method: 'GET', path: '/localizations', handler: 'test' },
|
||||
];
|
||||
global.strapi.api.restaurant.routes.restaurant.routes = [
|
||||
{ method: 'GET', path: '/localizations', handler: 'test' },
|
||||
];
|
||||
|
||||
const apiMocks = [
|
||||
{
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['role'],
|
||||
},
|
||||
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
||||
];
|
||||
|
||||
let schemas = {};
|
||||
for (const mock of apiMocks) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
...buildComponentSchema(mock),
|
||||
};
|
||||
}
|
||||
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const pluginListResponseValue = schemas['UsersPermissionsRoleLocalizationResponse'];
|
||||
const apiListResponseValue = schemas['RestaurantLocalizationResponse'];
|
||||
|
||||
const expectedShape = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
test: { type: 'string' },
|
||||
},
|
||||
};
|
||||
|
||||
expect(schemaNames.length).toBe(4);
|
||||
expect(schemaNames.includes('UsersPermissionsRoleLocalizationResponse')).toBe(true);
|
||||
expect(schemaNames.includes('RestaurantLocalizationResponse')).toBe(true);
|
||||
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
||||
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
||||
});
|
||||
|
||||
it('builds the LocalizationRequest schema', () => {
|
||||
global.strapi.plugins['users-permissions'].routes['content-api'].routes = [
|
||||
{ method: 'POST', path: '/localizations', handler: 'test' },
|
||||
];
|
||||
global.strapi.api.restaurant.routes.restaurant.routes = [
|
||||
{ method: 'POST', path: '/localizations', handler: 'test' },
|
||||
];
|
||||
|
||||
const apiMocks = [
|
||||
{
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['role'],
|
||||
},
|
||||
{ name: 'restaurant', getter: 'api', ctNames: ['restaurant'] },
|
||||
];
|
||||
|
||||
let schemas = {};
|
||||
for (const mock of apiMocks) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
...buildComponentSchema(mock),
|
||||
};
|
||||
}
|
||||
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const pluginListResponseValue = schemas['UsersPermissionsRoleLocalizationRequest'];
|
||||
const apiListResponseValue = schemas['RestaurantLocalizationRequest'];
|
||||
|
||||
const expectedShape = {
|
||||
type: 'object',
|
||||
properties: { test: { type: 'string' } },
|
||||
};
|
||||
|
||||
expect(schemaNames.length).toBe(8);
|
||||
expect(schemaNames.includes('UsersPermissionsRoleLocalizationRequest')).toBe(true);
|
||||
expect(schemaNames.includes('RestaurantLocalizationRequest')).toBe(true);
|
||||
expect(pluginListResponseValue).toStrictEqual(expectedShape);
|
||||
expect(apiListResponseValue).toStrictEqual(expectedShape);
|
||||
});
|
||||
|
||||
it('creates the correct name given multiple content types', () => {
|
||||
const apiMock = {
|
||||
name: 'users-permissions',
|
||||
getter: 'plugin',
|
||||
ctNames: ['permission', 'role', 'user'],
|
||||
};
|
||||
|
||||
const schemas = buildComponentSchema(apiMock);
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const [permission, role, user] = schemaNames;
|
||||
|
||||
expect(schemaNames.length).toBe(3);
|
||||
expect(permission).toBe('UsersPermissionsPermissionResponse');
|
||||
expect(role).toBe('UsersPermissionsRoleResponse');
|
||||
expect(user).toBe('UsersPermissionsUserResponse');
|
||||
});
|
||||
});
|
||||
@ -21,7 +21,7 @@ module.exports = {
|
||||
path: '/documentation',
|
||||
showGeneratedFiles: true,
|
||||
generateDefaultResponse: true,
|
||||
plugins: ['email', 'upload'],
|
||||
plugins: ['email', 'upload', 'users-permissions'],
|
||||
},
|
||||
servers: [],
|
||||
externalDocs: {
|
||||
@ -41,5 +41,34 @@ module.exports = {
|
||||
bearerFormat: 'JWT',
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
Error: {
|
||||
type: 'object',
|
||||
required: ['error'],
|
||||
properties: {
|
||||
data: {
|
||||
nullable: true,
|
||||
oneOf: [{ type: 'object' }, { type: 'array' }],
|
||||
},
|
||||
error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'integer',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
details: {
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const defaultDocumentationConfig = require('./default-config');
|
||||
const defaultPluginConfig = require('./default-plugin-config');
|
||||
|
||||
module.exports = {
|
||||
default: defaultDocumentationConfig,
|
||||
default: defaultPluginConfig,
|
||||
};
|
||||
|
||||
@ -5,8 +5,8 @@ const fs = require('fs-extra');
|
||||
const _ = require('lodash');
|
||||
const { getAbsoluteServerUrl } = require('@strapi/utils');
|
||||
|
||||
const { builApiEndpointPath } = require('../utils/builders');
|
||||
const defaultConfig = require('../config/default-config');
|
||||
const defaultPluginConfig = require('../config/default-plugin-config');
|
||||
const { builApiEndpointPath, buildComponentSchema } = require('./helpers');
|
||||
|
||||
module.exports = ({ strapi }) => {
|
||||
const config = strapi.config.get('plugin.documentation');
|
||||
@ -107,7 +107,7 @@ module.exports = ({ strapi }) => {
|
||||
return [...apisToDocument, ...pluginsToDocument];
|
||||
},
|
||||
|
||||
async getCustomSettings() {
|
||||
async getCustomConfig() {
|
||||
const customConfigPath = this.getCustomDocumentationPath();
|
||||
const pathExists = await fs.pathExists(customConfigPath);
|
||||
if (pathExists) {
|
||||
@ -122,23 +122,31 @@ module.exports = ({ strapi }) => {
|
||||
*/
|
||||
async generateFullDoc(version = this.getDocumentationVersion()) {
|
||||
let paths = {};
|
||||
|
||||
let schemas = {};
|
||||
const apis = this.getPluginAndApiInfo();
|
||||
for (const api of apis) {
|
||||
const apiName = api.name;
|
||||
const apiDirPath = path.join(this.getApiDocumentationPath(api), version);
|
||||
|
||||
const apiDocPath = path.join(apiDirPath, `${apiName}.json`);
|
||||
const apiPathsObject = builApiEndpointPath(api);
|
||||
|
||||
if (!apiPathsObject) {
|
||||
const apiPath = builApiEndpointPath(api);
|
||||
|
||||
if (!apiPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fs.ensureFile(apiDocPath);
|
||||
await fs.writeJson(apiDocPath, apiPathsObject, { spaces: 2 });
|
||||
await fs.writeJson(apiDocPath, apiPath, { spaces: 2 });
|
||||
|
||||
paths = { ...paths, ...apiPathsObject.paths };
|
||||
const componentSchema = buildComponentSchema(api);
|
||||
|
||||
schemas = {
|
||||
...schemas,
|
||||
...componentSchema,
|
||||
};
|
||||
|
||||
paths = { ...paths, ...apiPath };
|
||||
}
|
||||
|
||||
const fullDocJsonPath = path.join(
|
||||
@ -147,27 +155,26 @@ module.exports = ({ strapi }) => {
|
||||
'full_documentation.json'
|
||||
);
|
||||
|
||||
const defaultSettings = _.cloneDeep(defaultConfig);
|
||||
const defaultConfig = _.cloneDeep(defaultPluginConfig);
|
||||
|
||||
const serverUrl = getAbsoluteServerUrl(strapi.config);
|
||||
const apiPath = strapi.config.get('api.rest.prefix');
|
||||
|
||||
_.set(defaultSettings, 'servers', [
|
||||
_.set(defaultConfig, 'servers', [
|
||||
{
|
||||
url: `${serverUrl}${apiPath}`,
|
||||
description: 'Development server',
|
||||
},
|
||||
]);
|
||||
_.set(defaultConfig, ['info', 'x-generation-date'], new Date().toISOString());
|
||||
_.set(defaultConfig, ['info', 'version'], version);
|
||||
_.merge(defaultConfig.components, { schemas });
|
||||
|
||||
_.set(defaultSettings, ['info', 'x-generation-date'], new Date().toISOString());
|
||||
_.set(defaultSettings, ['info', 'version'], version);
|
||||
|
||||
const customSettings = await this.getCustomSettings();
|
||||
|
||||
const settings = _.merge(defaultSettings, customSettings);
|
||||
const customConfig = await this.getCustomConfig();
|
||||
const config = _.merge(defaultConfig, customConfig);
|
||||
|
||||
await fs.ensureFile(fullDocJsonPath);
|
||||
await fs.writeJson(fullDocJsonPath, { ...settings, paths }, { spaces: 2 });
|
||||
await fs.writeJson(fullDocJsonPath, { ...config, paths }, { spaces: 2 });
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,185 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const pathToRegexp = require('path-to-regexp');
|
||||
|
||||
const pascalCase = require('./utils/pascal-case');
|
||||
const queryParams = require('./utils/query-params');
|
||||
const loopContentTypeNames = require('./utils/loop-content-type-names');
|
||||
const getApiResponses = require('./utils/get-api-responses');
|
||||
const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
|
||||
|
||||
/**
|
||||
* @description Parses a route with ':variable'
|
||||
*
|
||||
* @param {string} routePath - The route's path property
|
||||
* @returns {string}
|
||||
*/
|
||||
const parsePathWithVariables = (routePath) => {
|
||||
return pathToRegexp
|
||||
.parse(routePath)
|
||||
.map((token) => {
|
||||
if (_.isObject(token)) {
|
||||
return token.prefix + '{' + token.name + '}';
|
||||
}
|
||||
|
||||
return token;
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Builds the required object for a path parameter
|
||||
*
|
||||
* @param {string} routePath - The route's path property
|
||||
*
|
||||
* @returns {object } Swagger path params object
|
||||
*/
|
||||
const getPathParams = (routePath) => {
|
||||
return pathToRegexp
|
||||
.parse(routePath)
|
||||
.filter((token) => _.isObject(token))
|
||||
.map((param) => {
|
||||
return {
|
||||
name: param.name,
|
||||
in: 'path',
|
||||
description: '',
|
||||
deprecated: false,
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} prefix - The prefix found on the routes object
|
||||
* @param {string} route - The current route
|
||||
* @property {string} route.path - The current route's path
|
||||
* @property {object} route.config - The current route's config object
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
const getPathWithPrefix = (prefix, route) => {
|
||||
// When the prefix is set on the routes and
|
||||
// the current route is not trying to remove it
|
||||
if (prefix && !_.has(route.config, 'prefix')) {
|
||||
// Add the prefix to the path
|
||||
return prefix.concat(route.path);
|
||||
}
|
||||
|
||||
// Otherwise just return path
|
||||
return route.path;
|
||||
};
|
||||
/**
|
||||
* @description Gets all paths based on routes
|
||||
*
|
||||
* @param {object} apiInfo
|
||||
* @property {object} apiInfo.routeInfo - The api routes object
|
||||
* @property {string} apiInfo.uniqueName - Content type name | Api name + Content type name
|
||||
* @property {object} apiInfo.contentTypeInfo - The info object found on content type schemas
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
const getPaths = ({ routeInfo, uniqueName, contentTypeInfo }) => {
|
||||
// Get the routes for the current content type
|
||||
const contentTypeRoutes = routeInfo.routes.filter((route) => {
|
||||
return (
|
||||
route.path.includes(contentTypeInfo.pluralName) ||
|
||||
route.path.includes(contentTypeInfo.singularName)
|
||||
);
|
||||
});
|
||||
|
||||
const paths = contentTypeRoutes.reduce((acc, route) => {
|
||||
// TODO: Find a more reliable way to determine list of entities vs a single entity
|
||||
const isListOfEntities = hasFindMethod(route.handler);
|
||||
const isLocalizationPath = isLocalizedPath(route.path);
|
||||
const methodVerb = route.method.toLowerCase();
|
||||
const hasPathParams = route.path.includes('/:');
|
||||
const pathWithPrefix = getPathWithPrefix(routeInfo.prefix, route);
|
||||
const routePath = hasPathParams ? parsePathWithVariables(pathWithPrefix) : pathWithPrefix;
|
||||
const { responses } = getApiResponses({
|
||||
uniqueName,
|
||||
route,
|
||||
isListOfEntities,
|
||||
isLocalizationPath,
|
||||
});
|
||||
|
||||
const swaggerConfig = {
|
||||
responses,
|
||||
tags: [_.upperFirst(uniqueName)],
|
||||
parameters: [],
|
||||
operationId: `${methodVerb}${routePath}`,
|
||||
};
|
||||
|
||||
if (isListOfEntities) {
|
||||
swaggerConfig.parameters.push(...queryParams);
|
||||
}
|
||||
|
||||
if (hasPathParams) {
|
||||
const pathParams = getPathParams(route.path);
|
||||
swaggerConfig.parameters.push(...pathParams);
|
||||
}
|
||||
|
||||
if (['post', 'put'].includes(methodVerb)) {
|
||||
const refName = isLocalizationPath ? 'LocalizationRequest' : 'Request';
|
||||
const requestBody = {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${pascalCase(uniqueName)}${refName}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
swaggerConfig.requestBody = requestBody;
|
||||
}
|
||||
|
||||
_.set(acc, `${routePath}.${methodVerb}`, swaggerConfig);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return paths;
|
||||
};
|
||||
|
||||
/**
|
||||
* @decription Gets all open api paths object for a given content type
|
||||
*
|
||||
* @param {object} apiInfo
|
||||
*
|
||||
* @returns {object} Open API paths
|
||||
*/
|
||||
const getAllPathsForContentType = (apiInfo) => {
|
||||
let paths = {};
|
||||
|
||||
const pathsObject = getPaths(apiInfo);
|
||||
|
||||
paths = {
|
||||
...paths,
|
||||
...pathsObject,
|
||||
};
|
||||
|
||||
return paths;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description - Builds the Swagger paths object for each api
|
||||
*
|
||||
* @param {object} api - Information about the current api
|
||||
* @property {string} api.name - The name of the api
|
||||
* @property {string} api.getter - The getter for the api (api | plugin)
|
||||
* @property {array} api.ctNames - The name of all contentTypes found on the api
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
const buildApiEndpointPath = (api) => {
|
||||
// A reusable loop for building paths and component schemas
|
||||
// Uses the api param to build a new set of params for each content type
|
||||
// Passes these new params to the function provided
|
||||
return loopContentTypeNames(api, getAllPathsForContentType);
|
||||
};
|
||||
|
||||
module.exports = buildApiEndpointPath;
|
||||
@ -0,0 +1,153 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
|
||||
const cleanSchemaAttributes = require('./utils/clean-schema-attributes');
|
||||
const loopContentTypeNames = require('./utils/loop-content-type-names');
|
||||
const pascalCase = require('./utils/pascal-case');
|
||||
const { hasFindMethod, isLocalizedPath } = require('./utils/routes');
|
||||
|
||||
/**
|
||||
* @decription Get all open api schema objects for a given content type
|
||||
*
|
||||
* @param {object} apiInfo
|
||||
* @property {string} apiInfo.uniqueName - Api name | Api name + Content type name
|
||||
* @property {object} apiInfo.attributes - Attributes on content type
|
||||
* @property {object} apiInfo.routeInfo - The routes for the api
|
||||
*
|
||||
* @returns {object} Open API schemas
|
||||
*/
|
||||
const getAllSchemasForContentType = ({ routeInfo, attributes, uniqueName }) => {
|
||||
// Store response and request schemas in an object
|
||||
let schemas = {};
|
||||
// Get all the route methods
|
||||
const routeMethods = routeInfo.routes.map((route) => route.method);
|
||||
// Check for localized paths
|
||||
const hasLocalizationPath = routeInfo.routes.filter((route) =>
|
||||
isLocalizedPath(route.path)
|
||||
).length;
|
||||
// When the route methods contain any post or put requests
|
||||
if (routeMethods.includes('POST') || routeMethods.includes('PUT')) {
|
||||
const attributesToOmit = [
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'publishedAt',
|
||||
'publishedBy',
|
||||
'updatedBy',
|
||||
'createdBy',
|
||||
'localizations',
|
||||
];
|
||||
const attributesForRequest = _.omit(attributes, attributesToOmit);
|
||||
|
||||
const requiredAttributes = Object.entries(attributesForRequest)
|
||||
.filter(([, attribute]) => attribute.required)
|
||||
.map(([attributeName, attribute]) => {
|
||||
return { [attributeName]: attribute };
|
||||
});
|
||||
|
||||
const requestAttributes =
|
||||
routeMethods.includes('POST') && requiredAttributes.length
|
||||
? Object.assign({}, ...requiredAttributes)
|
||||
: attributesForRequest;
|
||||
|
||||
if (hasLocalizationPath) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}LocalizationRequest`]: {
|
||||
type: 'object',
|
||||
properties: cleanSchemaAttributes(requestAttributes, { isRequest: true }),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Build the request schema
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}Request`]: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: cleanSchemaAttributes(requestAttributes, { isRequest: true }),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (hasLocalizationPath) {
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}LocalizationResponse`]: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
...cleanSchemaAttributes(attributes),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Check for routes that need to return a list
|
||||
const hasListOfEntities = routeInfo.routes.filter((route) => hasFindMethod(route.handler)).length;
|
||||
if (hasListOfEntities) {
|
||||
// Build the list response schema
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}ListResponse`]: {
|
||||
properties: {
|
||||
data: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
attributes: { type: 'object', properties: cleanSchemaAttributes(attributes) },
|
||||
},
|
||||
},
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pagination: {
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
pageSize: { type: 'integer', minimum: 25 },
|
||||
pageCount: { type: 'integer', maximum: 1 },
|
||||
total: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Build the response schema
|
||||
schemas = {
|
||||
...schemas,
|
||||
[`${pascalCase(uniqueName)}Response`]: {
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
attributes: { type: 'object', properties: cleanSchemaAttributes(attributes) },
|
||||
},
|
||||
},
|
||||
meta: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return schemas;
|
||||
};
|
||||
|
||||
const buildComponentSchema = (api) => {
|
||||
// A reusable loop for building paths and component schemas
|
||||
// Uses the api param to build a new set of params for each content type
|
||||
// Passes these new params to the function provided
|
||||
return loopContentTypeNames(api, getAllSchemasForContentType);
|
||||
};
|
||||
|
||||
module.exports = buildComponentSchema;
|
||||
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const builApiEndpointPath = require('./build-api-endpoint-path');
|
||||
const buildComponentSchema = require('./build-component-schema');
|
||||
|
||||
module.exports = {
|
||||
builApiEndpointPath,
|
||||
buildComponentSchema,
|
||||
};
|
||||
@ -4,13 +4,12 @@ const _ = require('lodash');
|
||||
const getSchemaData = require('./get-schema-data');
|
||||
|
||||
/**
|
||||
* @description - Converts types found on attributes to OpenAPI specific data types
|
||||
* @description - Converts types found on attributes to OpenAPI acceptable data types
|
||||
*
|
||||
* @param {object} attributes - The attributes found on a contentType
|
||||
* @param {{ typeMap: Map, isRequest: boolean }} opts
|
||||
* @returns Attributes using OpenAPI acceptable data types
|
||||
*/
|
||||
|
||||
const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = false } = {}) => {
|
||||
const attributesCopy = _.cloneDeep(attributes);
|
||||
|
||||
@ -114,7 +113,7 @@ const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = fa
|
||||
break;
|
||||
}
|
||||
case 'dynamiczone': {
|
||||
const components = attribute.components.map(component => {
|
||||
const components = attribute.components.map((component) => {
|
||||
const componentAttributes = strapi.components[component].attributes;
|
||||
return {
|
||||
type: 'object',
|
||||
@ -170,6 +169,14 @@ const cleanSchemaAttributes = (attributes, { typeMap = new Map(), isRequest = fa
|
||||
break;
|
||||
}
|
||||
|
||||
if (prop === 'localizations') {
|
||||
attributesCopy[prop] = {
|
||||
type: 'array',
|
||||
items: { type: 'object', properties: {} },
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (!attribute.target || typeMap.has(attribute.target)) {
|
||||
attributesCopy[prop] = {
|
||||
type: 'object',
|
||||
@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
|
||||
const pascalCase = require('./pascal-case');
|
||||
|
||||
/**
|
||||
* @description - Builds the Swagger response object for a given api
|
||||
*
|
||||
* @param {object} name - Name of the api or plugin
|
||||
* @param {object} route - The current route
|
||||
* @param {boolean} isListOfEntities - Checks for a list of entitities
|
||||
*
|
||||
* @returns The Swagger responses
|
||||
*/
|
||||
const getApiResponse = ({
|
||||
uniqueName,
|
||||
route,
|
||||
isListOfEntities = false,
|
||||
isLocalizationPath = false,
|
||||
}) => {
|
||||
const getSchema = () => {
|
||||
if (route.method === 'DELETE') {
|
||||
return {
|
||||
type: 'integer',
|
||||
format: 'int64',
|
||||
};
|
||||
}
|
||||
|
||||
if (isLocalizationPath) {
|
||||
return { $ref: `#/components/schemas/${pascalCase(uniqueName)}LocalizationResponse` };
|
||||
}
|
||||
|
||||
if (isListOfEntities) {
|
||||
return { $ref: `#/components/schemas/${pascalCase(uniqueName)}ListResponse` };
|
||||
}
|
||||
|
||||
return { $ref: `#/components/schemas/${pascalCase(uniqueName)}Response` };
|
||||
};
|
||||
|
||||
const schema = getSchema();
|
||||
|
||||
return {
|
||||
responses: {
|
||||
200: {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
400: {
|
||||
description: 'Bad Request',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
401: {
|
||||
description: 'Unauthorized',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
403: {
|
||||
description: 'Forbidden',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: 'Not Found',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
500: {
|
||||
description: 'Internal Server Error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
$ref: '#/components/schemas/Error',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = getApiResponse;
|
||||
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @description A reusable loop for building api endpoint paths and component schemas
|
||||
*
|
||||
* @param {object} api - Api information to pass to the callback
|
||||
* @param {function} callback - Logic to execute for the given api
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
const loopContentTypeNames = (api, callback) => {
|
||||
let result = {};
|
||||
for (const contentTypeName of api.ctNames) {
|
||||
// Get the attributes found on the api's contentType
|
||||
const uid = `${api.getter}::${api.name}.${contentTypeName}`;
|
||||
const { attributes, info: contentTypeInfo } = strapi.contentType(uid);
|
||||
|
||||
// Get the routes for the current api
|
||||
const routeInfo =
|
||||
api.getter === 'plugin'
|
||||
? strapi.plugin(api.name).routes['content-api']
|
||||
: strapi.api[api.name].routes[contentTypeName];
|
||||
|
||||
// Continue to next iteration if routeInfo is undefined
|
||||
if (!routeInfo) continue;
|
||||
|
||||
// Uppercase the first letter of the api name
|
||||
const apiName = _.upperFirst(api.name);
|
||||
|
||||
// Create a unique name if the api name and contentType name don't match
|
||||
const uniqueName =
|
||||
api.name === contentTypeName ? apiName : `${apiName} - ${_.upperFirst(contentTypeName)}`;
|
||||
|
||||
const apiInfo = {
|
||||
...api,
|
||||
routeInfo,
|
||||
attributes,
|
||||
uniqueName,
|
||||
contentTypeInfo,
|
||||
};
|
||||
|
||||
result = {
|
||||
...result,
|
||||
...callback(apiInfo),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = loopContentTypeNames;
|
||||
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const pascalCase = (string) => {
|
||||
return _.upperFirst(_.camelCase(string));
|
||||
};
|
||||
|
||||
module.exports = pascalCase;
|
||||
@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
const hasFindMethod = (handler) => handler.split('.').pop() === 'find';
|
||||
|
||||
const isLocalizedPath = (routePath) => routePath.includes('localizations');
|
||||
|
||||
module.exports = {
|
||||
isLocalizedPath,
|
||||
hasFindMethod,
|
||||
};
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"Error": {
|
||||
"required": ["code", "message"],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "_limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Maximum number of results possible",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_sort",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Sort according to a specific field.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_start",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Skip a specific number of entries (especially useful for pagination)",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "=",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get entries that matches exactly your input",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_ne",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that are not equals to something",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_lt",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get record that are lower than a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_lte",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that are lower than or equal to a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_gt",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that are greater than a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_gte",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that are greater than or equal a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_contains",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that contains a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_containss",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that contains (case sensitive) a value",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_in",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that matches any value in the array of values",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "_nin",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "Get records that doesn't match any value in the array of values",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"deprecated": false
|
||||
}
|
||||
]
|
||||
@ -1,11 +0,0 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Foo": {
|
||||
"properties": {
|
||||
"bar": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const pathToRegexp = require('path-to-regexp');
|
||||
|
||||
const queryParams = require('../query-params');
|
||||
const buildApiRequests = require('./build-api-requests');
|
||||
const buildApiResponses = require('./build-api-responses');
|
||||
|
||||
/**
|
||||
* @description Parses a route with ':variable'
|
||||
*
|
||||
* @param {string} routePath - The route's path property
|
||||
* @returns {string}
|
||||
*/
|
||||
const parsePathWithVariables = routePath => {
|
||||
return pathToRegexp
|
||||
.parse(routePath)
|
||||
.map(token => {
|
||||
if (_.isObject(token)) {
|
||||
return token.prefix + '{' + token.name + '}';
|
||||
}
|
||||
|
||||
return token;
|
||||
})
|
||||
.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Builds the required object for a path parameter
|
||||
*
|
||||
* @param {string} routePath - The route's path property
|
||||
*
|
||||
* @returns {object } Swagger path params object
|
||||
*/
|
||||
const getPathParams = routePath => {
|
||||
return pathToRegexp
|
||||
.parse(routePath)
|
||||
.filter(token => _.isObject(token))
|
||||
.map(param => {
|
||||
return {
|
||||
name: param.name,
|
||||
in: 'path',
|
||||
description: '',
|
||||
deprecated: false,
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} prefix - The route prefix
|
||||
* @param {string} path - The route path
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
const getPathWithPrefix = (prefix, path) => {
|
||||
if (path.includes('localizations')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (path.endsWith('/')) {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
return prefix.concat(path);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} api - Information about the api
|
||||
* @param {object} api.routeInfo - The routes for a given api or plugin
|
||||
* @param {string} api.routeInfo.prefix - The prefix for all routes
|
||||
* @param {array} api.routeInfo.routes - The routes for the current api
|
||||
* @param {object} api.attributes - The attributes for a given api or plugin
|
||||
* @param {string} api.tag - A descriptor for OpenAPI
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
const getPaths = ({ routeInfo, attributes, tag }) => {
|
||||
const paths = routeInfo.routes.reduce((acc, route) => {
|
||||
// TODO: Find a more reliable way to determine list of entities vs a single entity
|
||||
const isListOfEntities = route.handler.split('.').pop() === 'find';
|
||||
const methodVerb = route.method.toLowerCase();
|
||||
|
||||
const hasPathParams = route.path.includes('/:');
|
||||
const pathWithPrefix = routeInfo.prefix
|
||||
? getPathWithPrefix(routeInfo.prefix, route.path)
|
||||
: route.path;
|
||||
const routePath = hasPathParams ? parsePathWithVariables(pathWithPrefix) : pathWithPrefix;
|
||||
|
||||
const { responses } = buildApiResponses(attributes, route, isListOfEntities);
|
||||
|
||||
const swaggerConfig = {
|
||||
responses,
|
||||
tags: [_.upperFirst(tag)],
|
||||
parameters: [],
|
||||
operationId: `${methodVerb}${routePath}`,
|
||||
};
|
||||
|
||||
if (isListOfEntities) {
|
||||
swaggerConfig.parameters.push(...queryParams);
|
||||
}
|
||||
|
||||
if (hasPathParams) {
|
||||
const pathParams = getPathParams(route.path);
|
||||
swaggerConfig.parameters.push(...pathParams);
|
||||
}
|
||||
|
||||
if (['post', 'put'].includes(methodVerb)) {
|
||||
const { requestBody } = buildApiRequests(attributes, route);
|
||||
|
||||
swaggerConfig.requestBody = requestBody;
|
||||
}
|
||||
|
||||
_.set(acc, `${routePath}.${methodVerb}`, swaggerConfig);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return { paths };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description - Builds the Swagger paths object for each api
|
||||
*
|
||||
* @param {object} api - Information about the current api
|
||||
* @property {string} api.name - The name of the api
|
||||
* @property {string} api.getter - The getter for the api (api | plugin)
|
||||
* @property {array} api.ctNames - The name of all contentTypes found on the api
|
||||
*
|
||||
* @returns {object}
|
||||
*/
|
||||
module.exports = api => {
|
||||
if (!api.ctNames.length && api.getter === 'plugin') {
|
||||
// Set arbitrary attributes
|
||||
const attributes = { foo: { type: 'string' } };
|
||||
const routeInfo = strapi.plugin(api.name).routes['admin'];
|
||||
|
||||
const apiInfo = {
|
||||
routeInfo,
|
||||
attributes,
|
||||
tag: api.name,
|
||||
};
|
||||
|
||||
return getPaths(apiInfo);
|
||||
}
|
||||
|
||||
// An api could have multiple contentTypes
|
||||
let paths = {};
|
||||
for (const contentTypeName of api.ctNames) {
|
||||
// Get the attributes found on the api's contentType
|
||||
const uid = `${api.getter}::${api.name}.${contentTypeName}`;
|
||||
const ct = strapi.contentType(uid);
|
||||
const attributes = ct.attributes;
|
||||
|
||||
// Get the routes for the current api
|
||||
const routeInfo =
|
||||
api.getter === 'plugin'
|
||||
? strapi.plugin(api.name).routes['content-api']
|
||||
: strapi.api[api.name].routes[contentTypeName];
|
||||
|
||||
// Parse an identifier for OpenAPI tag if the api name and contentType name don't match
|
||||
const tag = api.name === contentTypeName ? api.name : `${api.name} - ${contentTypeName}`;
|
||||
const apiInfo = {
|
||||
routeInfo,
|
||||
attributes,
|
||||
tag,
|
||||
};
|
||||
|
||||
paths = {
|
||||
...paths,
|
||||
...getPaths(apiInfo).paths,
|
||||
};
|
||||
}
|
||||
|
||||
return { paths };
|
||||
};
|
||||
@ -1,41 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const cleanSchemaAttributes = require('../clean-schema-attributes');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} attributes - The attributes found on a contentType
|
||||
* @param {object} route - The current route
|
||||
*
|
||||
* @returns The Swagger requestBody
|
||||
*/
|
||||
module.exports = (attributes, route) => {
|
||||
const requiredAttributes = Object.entries(attributes)
|
||||
.filter(([, attribute]) => attribute.required)
|
||||
.map(([attributeName, attribute]) => {
|
||||
return { [attributeName]: attribute };
|
||||
});
|
||||
|
||||
const requestAttributes =
|
||||
route.method === 'POST' && requiredAttributes.length
|
||||
? Object.assign({}, ...requiredAttributes)
|
||||
: attributes;
|
||||
|
||||
return {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
properties: {
|
||||
data: {
|
||||
type: 'object',
|
||||
properties: cleanSchemaAttributes(requestAttributes, { isRequest: true }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1,109 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const getSchemaData = require('../get-schema-data');
|
||||
const cleanSchemaAttributes = require('../clean-schema-attributes');
|
||||
const errorResponse = require('../error-response');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {boolean} isSingleEntity - Checks for a single entity
|
||||
* @returns {object} The correctly formatted meta object
|
||||
*/
|
||||
const getMeta = isListOfEntities => {
|
||||
if (isListOfEntities) {
|
||||
return {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pagination: {
|
||||
properties: {
|
||||
page: { type: 'integer' },
|
||||
pageSize: { type: 'integer', minimum: 25 },
|
||||
pageCount: { type: 'integer', maximum: 1 },
|
||||
total: { type: 'integer' },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'object' };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description - Builds the Swagger response object for a given api
|
||||
*
|
||||
* @param {object} attributes - The attributes found on a contentType
|
||||
* @param {object} route - The current route
|
||||
* @param {boolean} isListOfEntities - Checks for a list of entitities
|
||||
*
|
||||
* @returns The Swagger responses
|
||||
*/
|
||||
module.exports = (attributes, route, isListOfEntities = false) => {
|
||||
let schema;
|
||||
if (route.method === 'DELETE') {
|
||||
schema = {
|
||||
type: 'integer',
|
||||
format: 'int64',
|
||||
};
|
||||
} else {
|
||||
schema = {
|
||||
properties: {
|
||||
data: getSchemaData(isListOfEntities, cleanSchemaAttributes(attributes)),
|
||||
meta: getMeta(isListOfEntities),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'OK',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
'400': {
|
||||
description: 'Bad Request',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: errorResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
'401': {
|
||||
description: 'Unauthorized',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: errorResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
'403': {
|
||||
description: 'Forbidden',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: errorResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
'404': {
|
||||
description: 'Not Found',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: errorResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
'500': {
|
||||
description: 'Internal Server Error',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: errorResponse,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const buildApiResponses = require('./build-api-responses');
|
||||
const buildApiRequests = require('./build-api-requests');
|
||||
const builApiEndpointPath = require('./build-api-endpoint-path');
|
||||
|
||||
module.exports = {
|
||||
buildApiResponses,
|
||||
buildApiRequests,
|
||||
builApiEndpointPath,
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
type: 'object',
|
||||
required: ['error'],
|
||||
properties: {
|
||||
error: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
status: {
|
||||
type: 'integer',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user