From a4cf0c031c59046fb709c77f98c0c70cdf86954b Mon Sep 17 00:00:00 2001 From: matleppa Date: Fri, 5 Jan 2018 17:18:28 +0200 Subject: [PATCH 1/7] External documentation type changed --- apinf_packages/rest_apis/server/catalog.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apinf_packages/rest_apis/server/catalog.js b/apinf_packages/rest_apis/server/catalog.js index a2062cbffb..5b84f56f6c 100644 --- a/apinf_packages/rest_apis/server/catalog.js +++ b/apinf_packages/rest_apis/server/catalog.js @@ -255,9 +255,12 @@ CatalogV1.swagger = { example: 'link-address-to-specification', }, externalDocumentation: { - type: 'string', + type: 'array', description: 'A URL to an external site page with API documentation', - example: 'url-to-external-site', + items: { + type: 'string', + example: 'url-to-external-site', + }, }, }, }, From c9cc5a4336014fa5722fa107993a115772d75eb9 Mon Sep 17 00:00:00 2001 From: matleppa Date: Fri, 5 Jan 2018 17:36:46 +0200 Subject: [PATCH 2/7] Improved documentation of POST /apis and PUT /apis --- .../rest_apis/lib/descriptions/apis_texts.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/apinf_packages/rest_apis/lib/descriptions/apis_texts.js b/apinf_packages/rest_apis/lib/descriptions/apis_texts.js index 07e91b62a3..608ee08c43 100644 --- a/apinf_packages/rest_apis/lib/descriptions/apis_texts.js +++ b/apinf_packages/rest_apis/lib/descriptions/apis_texts.js @@ -47,11 +47,13 @@ const descriptionApis = { Parameters - * mandatory: name and url - * length of description must not exceed 1000 characters - * value of lifecycleStatus must be one of example list - * allowed values for parameter isPublic are "true" and "false" - * if isPublic is set false, only admin or manager can see the API + * mandatory: *name* and *url* + * length of *description* must not exceed 1000 characters + * value of *lifecycleStatus* must be one of example list + * allowed values for parameter *isPublic* are "true" and "false" + * if isPublic is set false, only admin or manager can see the API + * *documentationUrl* contains a http(s) link to OpenAPI (or Swagger) documentationUrl + * *externalDocument* contains a http(s) link for other types of documentation `, // -------------------------------------------- put: ` @@ -61,10 +63,13 @@ const descriptionApis = { On success, returns the updated API object. Parameters - * length of description must not exceed 1000 characters - * value of lifecycleStatus must be one of example list - * allowed values for parameter isPublic are "true" and "false" - * if isPublic is set false, only admin or manager can see the API + * length of *description* must not exceed 1000 characters + * value of *lifecycleStatus* must be one of example list + * allowed values for parameter *isPublic* are "true" and "false" + * if isPublic is set false, only admin or manager can see the API + * *documentationUrl* contains a http(s) link to OpenAPI (or Swagger) documentationUrl + * *externalDocument* contains a http(s) link for other types of documentation + `, // -------------------------------------------- delete: ` From f34a2b8b9854d4b5ec27ce8f1c9bb3c6ad29179a Mon Sep 17 00:00:00 2001 From: matleppa Date: Fri, 5 Jan 2018 17:40:23 +0200 Subject: [PATCH 3/7] POST /apis and PUT /apis external documentation - added simpleSchema - changed documentation fields to validate with regex - changed apiDocs updates to update external documentation array - added checking of operation integrity and rollback if needed --- apinf_packages/apis/server/api.js | 291 ++++++++++++++++++++++++------ 1 file changed, 231 insertions(+), 60 deletions(-) diff --git a/apinf_packages/apis/server/api.js b/apinf_packages/apis/server/api.js index e3c3f78f30..52ddcb11db 100644 --- a/apinf_packages/apis/server/api.js +++ b/apinf_packages/apis/server/api.js @@ -8,6 +8,7 @@ import { Meteor } from 'meteor/meteor'; // Meteor contributed packages imports import { Roles } from 'meteor/alanning:roles'; +import { SimpleSchema } from 'meteor/aldeed:simple-schema'; // Collection imports import Apis from '/apinf_packages/apis/collection'; @@ -443,13 +444,14 @@ CatalogV1.addCollection(Apis, { const documentationUrl = bodyParams.documentationUrl; const externalDocumentation = bodyParams.externalDocumentation; + // Regex for http(s) protocol + const regex = SimpleSchema.RegEx.Url; // Documentation URL must have URL format if (documentationUrl) { - isValid = ApiDocs.simpleSchema().namedContext() - .validateOne({ remoteFileUrl: documentationUrl }, 'remoteFileUrl'); - - if (!isValid) { + // Check link validity + const regexUrl = regex.test(documentationUrl); + if (!regexUrl) { // Error message const message = 'Parameter "documentationUrl" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -458,10 +460,10 @@ CatalogV1.addCollection(Apis, { // Link to an external site must have URL format if (externalDocumentation) { - isValid = ApiDocs.simpleSchema().namedContext() - .validateOne({ otherUrl: externalDocumentation }, 'otherUrl'); + // Check link validity + const regexUrl = regex.test(externalDocumentation); - if (!isValid) { + if (!regexUrl) { // Error message const message = 'Parameter "externalDocumentation" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -474,20 +476,28 @@ CatalogV1.addCollection(Apis, { // Insert API data into collection const apiId = Apis.insert(apiData); - // Did insert fail + // If insert failed, stop and send response if (!apiId) { - return errorMessagePayload(500, 'Inserting API into database failed.'); + return errorMessagePayload(500, 'Insert API card into database failed.'); } - - // Make sure a user provides some Documentation data & API is created + // Add also documentation, if links are given if (documentationUrl || externalDocumentation) { - ApiDocs.insert({ - apiId, - type: 'url', - otherUrl: externalDocumentation, - remoteFileUrl: documentationUrl, - }); + const result = ApiDocs.update( + { apiId }, + { $set: { + type: 'url', + remoteFileUrl: bodyParams.documentationUrl, + }, + $push: { otherUrl: bodyParams.externalDocumentation } }, + { upsert: true }, + ); + // Integrity: If insertion of document link failed, remove also API card + if (result === 0) { + // Remove newly created API document + Meteor.call('removeApi', apiId); + return errorMessagePayload(500, 'Insert documentation failed. API card not created.'); + } } // Prepare data to response, extend it with Documentation URLs @@ -559,6 +569,9 @@ CatalogV1.addCollection(Apis, { ], }, action () { + // Used in case of rollback + let previousDocumentationUrl; + // Get data from body parameters const bodyParams = this.bodyParams; // Get ID of API @@ -617,57 +630,73 @@ CatalogV1.addCollection(Apis, { return errorMessagePayload(400, 'Parameter isPublic has erroneous value.'); } } - + // Check if link for openAPI documentation was given const documentationUrl = bodyParams.documentationUrl; + // Cehck if link for external documentation was given const externalDocumentation = bodyParams.externalDocumentation; - // Documentation URL must have URL format - if (documentationUrl) { - const isValid = ApiDocs.simpleSchema().namedContext() - .validateOne({ remoteFileUrl: documentationUrl }, 'remoteFileUrl'); - - if (!isValid) { - // Error message - const message = 'Parameter "documentationUrl" must be a valid URL with http(s).'; - return errorMessagePayload(400, message); - } - } - - // Link to an external site must have URL format - if (externalDocumentation) { - const isValid = ApiDocs.simpleSchema().namedContext() - .validateOne({ otherUrl: externalDocumentation }, 'otherUrl'); - - if (!isValid) { - // Error message - const message = 'Parameter "externalDocumentation" must be a valid URL with http(s).'; - return errorMessagePayload(400, message); - } - } - - // Save new or updated documentation links if (documentationUrl || externalDocumentation) { // Try to fetch existing documentation const apiDoc = ApiDocs.findOne({ apiId }); + console.log('apiDoc=', apiDoc); + // Regex for http(s) protocol + const regex = SimpleSchema.RegEx.Url; + + // Check link to Documentation URL + if (documentationUrl) { + // Check link validity + const regexUrl = regex.test(documentationUrl); + // If value is https(s) + if (!regexUrl) { + // Error message + const message = 'Parameter "documentationUrl" must be a valid URL with http(s).'; + return errorMessagePayload(400, message); + } + } - if (apiDoc) { - // Update existing documentation - ApiDocs.update( - { apiId }, - { $set: { - type: 'url', - remoteFileUrl: bodyParams.documentationUrl, - otherUrl: bodyParams.externalDocumentation, - }, - }); - } else { - // Create a new apiDoc instance - ApiDocs.insert({ - apiId, + // Check link to an external documentation + if (externalDocumentation) { + // Check link validity + const regexUrl = regex.test(externalDocumentation); + // If value is https(s) + if (!regexUrl) { + // Error message + const message = 'Parameter "externalDocumentation" must be a valid URL with http(s).'; + return errorMessagePayload(400, message); + } + + // Check if new external documentation link can be added + if (apiDoc && apiDoc.otherUrl) { + // Can not add same link again + const isLinkAlreadyPresent = apiDoc.otherUrl.includes(externalDocumentation); + if (isLinkAlreadyPresent) { + const message = 'Same link to "externalDocumentation" already exists.'; + return errorMessagePayload(400, message, 'url', externalDocumentation); + } + // Max 8 external documentation links can be added + if (apiDoc.otherUrl.length > 7) { + const message = 'Maximum number of external documentation links (8) already given.'; + return errorMessagePayload(400, message); + } + } + // Prepare for rollback after possible failure + previousDocumentationUrl = apiDoc.remoteFileUrl; + } + + // Update Documentation + const result = ApiDocs.update( + { apiId }, + { $set: { type: 'url', - otherUrl: bodyParams.externalDocumentation, remoteFileUrl: bodyParams.documentationUrl, - }); + }, + $push: { otherUrl: bodyParams.externalDocumentation } }, + { upsert: true }, + ); + // If insertion of document link failed + if (result === 0) { + // Remove newly created API document + return errorMessagePayload(500, 'Update failed because Documentation update fail.'); } } @@ -676,7 +705,46 @@ CatalogV1.addCollection(Apis, { bodyParams.updated_by = userId; // Update API document - Apis.update(apiId, { $set: bodyParams }); + const result = Apis.update(apiId, { $set: bodyParams }); + // Check if API update failed + if (result !== 0) { + // try to rollback documentation update, if necessary + if (documentationUrl || externalDocumentation) { + if (documentationUrl) { + // Restore previous documentation link, if it exist + if (previousDocumentationUrl) { + ApiDocs.update( + { apiId }, + { $set: { + type: 'url', + remoteFileUrl: previousDocumentationUrl, + }, + }, + ); + } else { + // No previous link, just make it empty + ApiDocs.update( + { apiId }, + { $unset: { + remoteFileUrl: '', + }, + }, + ); + } + } + // Rollback external documentation by removing latest added link + if (externalDocumentation) { + ApiDocs.update( + { apiId }, + { $set: { + type: 'url', + }, + $pull: { otherUrl: externalDocumentation } }, + ); + } + } + return errorMessagePayload(500, 'Update failed because API update fail.'); + } // Prepare data to response, extend it with Documentation URLs const responseData = Object.assign( @@ -767,3 +835,106 @@ CatalogV1.addCollection(Apis, { }, }, }); + + +// // Request /rest/v1/apis/:id/documents/ +// CatalogV1.addRoute('apis/:id/documents', { +// // Remove a manager (:managerId) from given Organization (:id) +// delete: { +// authRequired: true, +// swagger: { +// tags: [ +// CatalogV1.swagger.tags.organization, +// ], +// summary: 'Delete identified Manager from Organization Manager list.', +// description: descriptionOrganizations.deleteIdManagersManagerid, +// parameters: [ +// CatalogV1.swagger.params.organizationId, +// CatalogV1.swagger.params.managerId, +// ], +// responses: { +// 204: { +// description: 'Organization Manager removed successfully.', +// schema: { +// type: 'object', +// properties: { +// data: { +// $ref: '#/definitions/organizationManagerResponse', +// }, +// }, +// }, +// }, +// 400: { +// description: 'Bad Request. Erroneous or missing parameter.', +// }, +// 401: { +// description: 'Authentication is required', +// }, +// 403: { +// description: 'User does not have permission', +// }, +// 404: { +// description: 'Organization is not found', +// }, +// }, +// security: [ +// { +// userSecurityToken: [], +// userId: [], +// }, +// ], +// }, +// action () { +// // Get ID of Organization +// const organizationId = this.urlParams.id; +// // Note! It can not be checked here, if this parameter is not provided, +// // because in that case the parameters are shifted and endpoint is not found at all. +// +// // Get Organization document +// const organization = Organizations.findOne(organizationId); +// // Organization must exist +// if (!organization) { +// return errorMessagePayload(404, 'Organization with specified ID is not found'); +// } +// +// // Get ID of requesting User +// const requestorId = this.userId; +// +// const userCanManage = Meteor.call('userCanManageOrganization', requestorId, organization); +// // Requestor must have permission for action +// if (!userCanManage) { +// return errorMessagePayload(403, 'You do not have permission for edit this Organization'); +// } +// +// // Get ID of Manager to be removed +// const removeManagerId = this.urlParams.managerId; +// // Manager ID must be given +// if (!removeManagerId) { +// return errorMessagePayload(400, 'Missing parameter: Manager ID not provided.'); +// } +// +// // Admin/Manager is not allowed to remove self +// if (removeManagerId === requestorId) { +// return errorMessagePayload(403, 'User can not remove self.'); +// } +// +// // Only existing Manager can be removed from Organization manager list +// const isManager = organization.managerIds.includes(removeManagerId); +// +// if (!isManager) { +// return errorMessagePayload(404, 'Manager not found in Organization.'); +// } +// +// // Remove user from organization manager list +// Meteor.call('removeOrganizationManager', organizationId, removeManagerId); +// +// return { +// statusCode: 204, +// body: { +// status: 'success', +// message: 'Manager removed successfully.', +// }, +// }; +// }, +// }, +// }); From 08d67e131e21f717440066891deee0c6c440c3b7 Mon Sep 17 00:00:00 2001 From: matleppa Date: Mon, 8 Jan 2018 16:37:04 +0200 Subject: [PATCH 4/7] Added removal of API document links - new endpoint DELETE /apis/id/documents - some comment enhancements --- apinf_packages/apis/server/api.js | 257 +++++++++++++++++------------- 1 file changed, 149 insertions(+), 108 deletions(-) diff --git a/apinf_packages/apis/server/api.js b/apinf_packages/apis/server/api.js index 52ddcb11db..5a311654ac 100644 --- a/apinf_packages/apis/server/api.js +++ b/apinf_packages/apis/server/api.js @@ -638,7 +638,6 @@ CatalogV1.addCollection(Apis, { if (documentationUrl || externalDocumentation) { // Try to fetch existing documentation const apiDoc = ApiDocs.findOne({ apiId }); - console.log('apiDoc=', apiDoc); // Regex for http(s) protocol const regex = SimpleSchema.RegEx.Url; @@ -679,8 +678,10 @@ CatalogV1.addCollection(Apis, { return errorMessagePayload(400, message); } } - // Prepare for rollback after possible failure - previousDocumentationUrl = apiDoc.remoteFileUrl; + // Prepare for rollback of openAPI documentation after possible failure + if (apiDoc && apiDoc.remoteFileUrl) { + previousDocumentationUrl = apiDoc.remoteFileUrl; + } } // Update Documentation @@ -695,7 +696,6 @@ CatalogV1.addCollection(Apis, { ); // If insertion of document link failed if (result === 0) { - // Remove newly created API document return errorMessagePayload(500, 'Update failed because Documentation update fail.'); } } @@ -707,11 +707,11 @@ CatalogV1.addCollection(Apis, { // Update API document const result = Apis.update(apiId, { $set: bodyParams }); // Check if API update failed - if (result !== 0) { - // try to rollback documentation update, if necessary + if (result === 0) { + // Try to rollback documentation update, if necessary if (documentationUrl || externalDocumentation) { if (documentationUrl) { - // Restore previous documentation link, if it exist + // Restore previous openAPI documentation link, if it exists if (previousDocumentationUrl) { ApiDocs.update( { apiId }, @@ -837,104 +837,145 @@ CatalogV1.addCollection(Apis, { }); -// // Request /rest/v1/apis/:id/documents/ -// CatalogV1.addRoute('apis/:id/documents', { -// // Remove a manager (:managerId) from given Organization (:id) -// delete: { -// authRequired: true, -// swagger: { -// tags: [ -// CatalogV1.swagger.tags.organization, -// ], -// summary: 'Delete identified Manager from Organization Manager list.', -// description: descriptionOrganizations.deleteIdManagersManagerid, -// parameters: [ -// CatalogV1.swagger.params.organizationId, -// CatalogV1.swagger.params.managerId, -// ], -// responses: { -// 204: { -// description: 'Organization Manager removed successfully.', -// schema: { -// type: 'object', -// properties: { -// data: { -// $ref: '#/definitions/organizationManagerResponse', -// }, -// }, -// }, -// }, -// 400: { -// description: 'Bad Request. Erroneous or missing parameter.', -// }, -// 401: { -// description: 'Authentication is required', -// }, -// 403: { -// description: 'User does not have permission', -// }, -// 404: { -// description: 'Organization is not found', -// }, -// }, -// security: [ -// { -// userSecurityToken: [], -// userId: [], -// }, -// ], -// }, -// action () { -// // Get ID of Organization -// const organizationId = this.urlParams.id; -// // Note! It can not be checked here, if this parameter is not provided, -// // because in that case the parameters are shifted and endpoint is not found at all. -// -// // Get Organization document -// const organization = Organizations.findOne(organizationId); -// // Organization must exist -// if (!organization) { -// return errorMessagePayload(404, 'Organization with specified ID is not found'); -// } -// -// // Get ID of requesting User -// const requestorId = this.userId; -// -// const userCanManage = Meteor.call('userCanManageOrganization', requestorId, organization); -// // Requestor must have permission for action -// if (!userCanManage) { -// return errorMessagePayload(403, 'You do not have permission for edit this Organization'); -// } -// -// // Get ID of Manager to be removed -// const removeManagerId = this.urlParams.managerId; -// // Manager ID must be given -// if (!removeManagerId) { -// return errorMessagePayload(400, 'Missing parameter: Manager ID not provided.'); -// } -// -// // Admin/Manager is not allowed to remove self -// if (removeManagerId === requestorId) { -// return errorMessagePayload(403, 'User can not remove self.'); -// } -// -// // Only existing Manager can be removed from Organization manager list -// const isManager = organization.managerIds.includes(removeManagerId); -// -// if (!isManager) { -// return errorMessagePayload(404, 'Manager not found in Organization.'); -// } -// -// // Remove user from organization manager list -// Meteor.call('removeOrganizationManager', organizationId, removeManagerId); -// -// return { -// statusCode: 204, -// body: { -// status: 'success', -// message: 'Manager removed successfully.', -// }, -// }; -// }, -// }, -// }); +// Request /rest/v1/apis/:id/documents/ +CatalogV1.addRoute('apis/:id/documents', { + // Remove documentation from given API (:id) either completely or partially + delete: { + authRequired: true, + swagger: { + tags: [ + CatalogV1.swagger.tags.api, + ], + summary: 'Delete identified documentation or all documentation.', + description: descriptionApis.deleteDocumentation, + parameters: [ + CatalogV1.swagger.params.apiId, + CatalogV1.swagger.params.url, + ], + responses: { + 200: { + description: 'API documentation updated successfully', + schema: { + type: 'object', + properties: { + status: { + type: 'string', + example: 'Success', + }, + data: { + $ref: '#/definitions/apiResponse', + }, + }, + }, + }, + 400: { + description: 'Bad Request. Erroneous or missing parameter.', + }, + 401: { + description: 'Authentication is required', + }, + 403: { + description: 'User does not have permission', + }, + 404: { + description: 'API is not found', + }, + }, + security: [ + { + userSecurityToken: [], + userId: [], + }, + ], + }, + action () { + // Get ID of API + const apiId = this.urlParams.id; + // Get User ID + const userId = this.userId; + // Get API document + const api = Apis.findOne(apiId); + + // API must exist + if (!api) { + // API doesn't exist + return errorMessagePayload(404, 'API with specified ID is not found.'); + } + + // User must be able to manage API + if (!api.currentUserCanManage(userId)) { + return errorMessagePayload(403, 'User does not have permission to this API.'); + } + + // Check if documentation exists + const apiDoc = ApiDocs.findOne({ apiId }); + + if (!apiDoc) { + return errorMessagePayload(404, 'No documentation exists for this API.'); + } + + // Check if link for openAPI documentation was given + const documentUrl = this.queryParams.url; + + // Remove identified documentation link + if (documentUrl) { + // Check if it is openAPI documentation or external documentation link + if (documentUrl === apiDoc.remoteFileUrl) { + // Matching link found as openAPI documentatin, try to remove + const removeResult = ApiDocs.update( + { apiId }, + { $unset: { + remoteFileUrl: '', + }, + }, + ); + // If removal of openAPI document link failed + if (removeResult === 0) { + const message = 'OpenAPI Documentation link removal failure.'; + return errorMessagePayload(500, message, 'url', documentUrl); + } + } else if (apiDoc.otherUrl.includes(documentUrl)) { + // Matching link found as external documentatin, try to remove + const removeResult = ApiDocs.update( + { apiId }, + { $set: { + type: 'url', + }, + $pull: { otherUrl: documentUrl } }, + ); + // If removal of external document link failed + if (removeResult === 0) { + const message = 'External Documentation link removal failure.'; + return errorMessagePayload(500, message, 'url', documentUrl); + } + } else { + // No matching link found + const message = 'Documentation link match not found.'; + return errorMessagePayload(404, message, 'url', documentUrl); + } + } else { + // Remove all documentation, because no link identified + Meteor.call('removeApiDoc', apiId); + } + + // Prepare data to response, extend it with Documentation URLs + const responseData = Object.assign( + // Get updated value of API + Apis.findOne(apiId), + // Get updated values of Documentation urls + { + externalDocumentation: api.otherUrl(), + documentationUrl: api.documentationUrl(), + }); + + // OK response with API data + return { + statusCode: 200, + body: { + status: 'success', + data: responseData, + }, + }; + }, + }, +}); From 30e62b1611c48b90e8e77f4befd66e52fa8b77c4 Mon Sep 17 00:00:00 2001 From: matleppa Date: Mon, 8 Jan 2018 16:37:55 +0200 Subject: [PATCH 5/7] Added description for removal of API documentation --- .../rest_apis/lib/descriptions/apis_texts.js | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/apinf_packages/rest_apis/lib/descriptions/apis_texts.js b/apinf_packages/rest_apis/lib/descriptions/apis_texts.js index 608ee08c43..f898ab871c 100644 --- a/apinf_packages/rest_apis/lib/descriptions/apis_texts.js +++ b/apinf_packages/rest_apis/lib/descriptions/apis_texts.js @@ -75,13 +75,41 @@ const descriptionApis = { delete: ` ### Deletes an API ### - Admin user or API manager can delete an identified API from the Catalog, + Admin user or API manager can delete an identified API from the Catalog. Example call: DELETE /apis/ Result: deletes the API identified with and responds with HTTP code 204. + + If match is not found, the operation is considered as failed. + `, + // -------------------------------------------- + deleteDocumentation: ` + ### Removes documentation of an API ### + + Admin user or API manager can remove whole documentation or part of it of an identified API. + On success returns API object. + + Example calls: + + DELETE /apis//documents + + Result: Removes all documentation of the API identified with + and responds with HTTP code 200. + + + DELETE /apis//documents?url=http://link-to-documentation + + Result: Removes (if finds the match of) the mentioned documentation link of the API identified + with and responds with HTTP code 200. + + Order of finding the match for the link + - openAPI documentation (documentationUrl) + - external documentation links + + If match is not found, the operation is considered as failed. `, }; From f8fcff8278e57d861728edc55e30b932142d82eb Mon Sep 17 00:00:00 2001 From: matleppa Date: Mon, 8 Jan 2018 16:39:29 +0200 Subject: [PATCH 6/7] Added new parameter for documentation url --- apinf_packages/rest_apis/server/catalog.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apinf_packages/rest_apis/server/catalog.js b/apinf_packages/rest_apis/server/catalog.js index 5b84f56f6c..376a6e375b 100644 --- a/apinf_packages/rest_apis/server/catalog.js +++ b/apinf_packages/rest_apis/server/catalog.js @@ -121,6 +121,13 @@ CatalogV1.swagger = { format: 'int32', minimum: 0, }, + url: { + name: 'url', + in: 'query', + description: 'Documentation link to be removed.', + required: false, + type: 'string', + }, }, definitions: { // The schema defining the type used for the body parameter in POST or PUT method From 0fc2e2c3bf78163c8cf8072f62ac32e4bdb7ee9e Mon Sep 17 00:00:00 2001 From: matleppa Date: Tue, 9 Jan 2018 13:40:11 +0200 Subject: [PATCH 7/7] Corrections after review - use regex test in decisions - replaced complicated ApiDocs.update with simple ApiDocs.insert in POST /apis endpoint - additions anf fixes in comments - removed unnecessary setting of field type when removing external documentation link - removed unnecessary duplicate Api read when returning API object data --- apinf_packages/apis/server/api.js | 44 +++++++++++-------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/apinf_packages/apis/server/api.js b/apinf_packages/apis/server/api.js index 5a311654ac..209ddca43a 100644 --- a/apinf_packages/apis/server/api.js +++ b/apinf_packages/apis/server/api.js @@ -450,8 +450,7 @@ CatalogV1.addCollection(Apis, { // Documentation URL must have URL format if (documentationUrl) { // Check link validity - const regexUrl = regex.test(documentationUrl); - if (!regexUrl) { + if (!regex.test(documentationUrl)) { // Error message const message = 'Parameter "documentationUrl" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -461,9 +460,7 @@ CatalogV1.addCollection(Apis, { // Link to an external site must have URL format if (externalDocumentation) { // Check link validity - const regexUrl = regex.test(externalDocumentation); - - if (!regexUrl) { + if (!regex.test(externalDocumentation)) { // Error message const message = 'Parameter "externalDocumentation" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -483,15 +480,12 @@ CatalogV1.addCollection(Apis, { // Add also documentation, if links are given if (documentationUrl || externalDocumentation) { - const result = ApiDocs.update( - { apiId }, - { $set: { - type: 'url', - remoteFileUrl: bodyParams.documentationUrl, - }, - $push: { otherUrl: bodyParams.externalDocumentation } }, - { upsert: true }, - ); + const result = ApiDocs.insert({ + apiId, + type: 'url', + remoteFileUrl: documentationUrl, + otherUrl: [externalDocumentation], + }); // Integrity: If insertion of document link failed, remove also API card if (result === 0) { // Remove newly created API document @@ -644,9 +638,7 @@ CatalogV1.addCollection(Apis, { // Check link to Documentation URL if (documentationUrl) { // Check link validity - const regexUrl = regex.test(documentationUrl); - // If value is https(s) - if (!regexUrl) { + if (!regex.test(documentationUrl)) { // Error message const message = 'Parameter "documentationUrl" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -656,9 +648,7 @@ CatalogV1.addCollection(Apis, { // Check link to an external documentation if (externalDocumentation) { // Check link validity - const regexUrl = regex.test(externalDocumentation); - // If value is https(s) - if (!regexUrl) { + if (!regex.test(externalDocumentation)) { // Error message const message = 'Parameter "externalDocumentation" must be a valid URL with http(s).'; return errorMessagePayload(400, message); @@ -684,7 +674,7 @@ CatalogV1.addCollection(Apis, { } } - // Update Documentation + // Update Documentation (or create a new one) const result = ApiDocs.update( { apiId }, { $set: { @@ -692,9 +682,10 @@ CatalogV1.addCollection(Apis, { remoteFileUrl: bodyParams.documentationUrl, }, $push: { otherUrl: bodyParams.externalDocumentation } }, + // If apiDocs document did not exist, create a new one { upsert: true }, ); - // If insertion of document link failed + // If update/insert of document link(s) failed if (result === 0) { return errorMessagePayload(500, 'Update failed because Documentation update fail.'); } @@ -938,10 +929,7 @@ CatalogV1.addRoute('apis/:id/documents', { // Matching link found as external documentatin, try to remove const removeResult = ApiDocs.update( { apiId }, - { $set: { - type: 'url', - }, - $pull: { otherUrl: documentUrl } }, + { $pull: { otherUrl: documentUrl } }, ); // If removal of external document link failed if (removeResult === 0) { @@ -960,8 +948,8 @@ CatalogV1.addRoute('apis/:id/documents', { // Prepare data to response, extend it with Documentation URLs const responseData = Object.assign( - // Get updated value of API - Apis.findOne(apiId), + // API has not changed, use already fetched value + api, // Get updated values of Documentation urls { externalDocumentation: api.otherUrl(),