diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 555a35e..fde2c64 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,15 +1,24 @@ swagger: '2.0' info: title: V5 Challenge Resource API - description: | + description: > ## Security constraints - GET/POST/DELETE `/Resources` endpoints can only be called from admins, via M2M, or users for whom there is at least one existing `Resource` where both `role.fullAccess` and `role.isActive` are `true`. Special Case: User can create/delte selfObtainable resource for its own. - `Resource Roles` POST and PUT endpoints can only be called from admins or via M2M. - GET `/Resources/{memberId}/challenges` endpoints can be called from any authenticated user or via M2M. - Resource role phase dependencies APIs can only be called from admins or M2M with valid scopes. - version: "1.0.0" + GET/POST/DELETE `/Resources` endpoints can only be called from admins, via + M2M, or users for whom there is at least one existing `Resource` where both + `role.fullAccess` and `role.isActive` are `true`. Special Case: User can + create/delte selfObtainable resource for its own. + + `Resource Roles` POST and PUT endpoints can only be called from admins or + via M2M. + + GET `/Resources/{memberId}/challenges` endpoints can be called from any + authenticated user or via M2M. + + Resource role phase dependencies APIs can only be called from admins or M2M + with valid scopes. + version: 1.0.0 host: api.topcoder.com securityDefinitions: Bearer: @@ -24,7 +33,6 @@ produces: - application/json consumes: - application/json - paths: /health: get: @@ -32,7 +40,7 @@ paths: tags: - Health responses: - 200: + '200': description: success schema: type: object @@ -40,7 +48,7 @@ paths: checksRun: type: integer example: 1 - 503: + '503': $ref: '#/definitions/ServiceUnavailable' /resources: get: @@ -50,6 +58,8 @@ paths: security: - Bearer: [] parameters: + - $ref: '#/parameters/page' + - $ref: '#/parameters/perPage' - name: challengeId type: string description: The challenge id @@ -63,24 +73,26 @@ paths: in: query required: false responses: - 200: + '200': description: OK - the request was successful schema: type: array items: $ref: '#/definitions/Resource' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 500: + '500': $ref: '#/definitions/ServerError' post: - description: Create a new resource for a challenge. Verify that the challenge exists by calling the **/v5/challenges/{id}** endpoint using an M2M token. + description: >- + Create a new resource for a challenge. Verify that the challenge exists + by calling the **/v5/challenges/{id}** endpoint using an M2M token. tags: - Resources security: @@ -92,21 +104,21 @@ paths: schema: $ref: '#/definitions/ResourceRequestBody' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/Resource' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 409: + '409': $ref: '#/definitions/Conflict' - 500: + '500': $ref: '#/definitions/ServerError' delete: description: Delete a resource from a challenge @@ -121,21 +133,21 @@ paths: schema: $ref: '#/definitions/ResourceRequestBody' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/Resource' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 500: + '500': $ref: '#/definitions/ServerError' - /resources/{memberId}/challenges: + '/resources/{memberId}/challenges': get: description: List all challenges that given topcoder member has access to. tags: @@ -155,7 +167,7 @@ paths: required: false description: the resource role id filter responses: - 200: + '200': description: OK - the request was successful schema: type: array @@ -163,18 +175,20 @@ paths: type: string description: the challenge id format: UUID - example: '97ee7396-5946-4a2e-968a-b35c7b009753' - 400: + example: 97ee7396-5946-4a2e-968a-b35c7b009753 + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 500: + '500': $ref: '#/definitions/ServerError' /resource-roles: get: - description: Get all resource roles. If isActive parameter is provided, filter the results by isActive property. + description: >- + Get all resource roles. If isActive parameter is provided, filter the + results by isActive property. tags: - Resource Roles security: @@ -189,24 +203,23 @@ paths: in: query description: Filters the results based on the `name` property responses: - 200: + '200': description: OK - the request was successful schema: type: array items: $ref: '#/definitions/ResourceRole' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 500: + '500': $ref: '#/definitions/ServerError' post: - description: > + description: | Create a new resource role. - Only admins can call this endpoint. tags: - Resource Roles @@ -219,25 +232,24 @@ paths: schema: $ref: '#/definitions/ResourceRole' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/ResourceRole' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 409: + '409': $ref: '#/definitions/Conflict' - 500: + '500': $ref: '#/definitions/ServerError' - /resource-roles/{resourceRoleId}: + '/resource-roles/{resourceRoleId}': put: - description: > + description: | Update an existing resource role. - Only admins can call this endpoint. tags: - Resource Roles @@ -255,21 +267,21 @@ paths: schema: $ref: '#/definitions/ResourceRole' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/ResourceRole' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 409: + '409': $ref: '#/definitions/Conflict' - 500: + '500': $ref: '#/definitions/ServerError' /resource-roles/Phase-dependencies: get: @@ -297,21 +309,21 @@ paths: in: query required: false responses: - 200: + '200': description: OK - the request was successful schema: type: array items: $ref: '#/definitions/ResourceRolePhaseDependency' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 500: + '500': $ref: '#/definitions/ServerError' post: description: Create a new resource role phase dependency. @@ -326,25 +338,25 @@ paths: schema: $ref: '#/definitions/ResourceRolePhaseDependencyRequestBody' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/ResourceRolePhaseDependency' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 409: + '409': $ref: '#/definitions/Conflict' - 500: + '500': $ref: '#/definitions/ServerError' - /resource-roles/Phase-dependencies/{id}: + '/resource-roles/Phase-dependencies/{id}': put: - description: > + description: | Update an existing resource role phase dependency. tags: - Resource Role Phase Dependencies @@ -363,21 +375,21 @@ paths: schema: $ref: '#/definitions/ResourceRolePhaseDependencyRequestBody' responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/ResourceRolePhaseDependency' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 409: + '409': $ref: '#/definitions/Conflict' - 500: + '500': $ref: '#/definitions/ServerError' delete: description: Delete a resource role phase dependency @@ -393,20 +405,36 @@ paths: required: true description: the dependency id responses: - 200: + '200': description: OK - the request was successful schema: $ref: '#/definitions/ResourceRolePhaseDependency' - 400: + '400': $ref: '#/definitions/BadRequest' - 401: + '401': $ref: '#/definitions/Unauthorized' - 403: + '403': $ref: '#/definitions/Forbidden' - 404: + '404': $ref: '#/definitions/NotFound' - 500: + '500': $ref: '#/definitions/ServerError' +parameters: + page: + name: page + in: query + description: The page number. + required: false + type: integer + default: 1 + perPage: + name: perPage + in: query + description: The number of items to list per page. + required: false + type: integer + default: 20 + maximum: 100 definitions: Resource: type: object @@ -444,11 +472,11 @@ definitions: createdBy: type: string description: The user who created the entity. - example: 'Topcoder User' + example: Topcoder User updatedBy: type: string description: The user who updated the entity. - example: 'Topcoder User' + example: Topcoder User ResourceRole: type: object required: @@ -463,7 +491,7 @@ definitions: readOnly: true name: type: string - description: Unique resource role name, case in-sensitive + description: 'Unique resource role name, case in-sensitive' fullAccess: type: boolean isActive: @@ -491,8 +519,6 @@ definitions: description: the resource role id phaseState: type: boolean - - # Schema for request body ResourceRequestBody: type: object required: @@ -528,8 +554,6 @@ definitions: description: the resource role id phaseState: type: boolean - - # Schema for error body Unauthorized: type: object description: The unauthorized error entity. @@ -538,7 +562,6 @@ definitions: type: string description: The unauthorized error message. example: Unable to authenticate the user. - NotFound: type: object description: The not found error entity. @@ -547,7 +570,6 @@ definitions: type: string description: The not found error message. example: A resource with the name could not be found. - ServerError: type: object description: The server error entity. @@ -555,8 +577,10 @@ definitions: message: type: string description: The server error message. - example: Something went wrong while processing your request. We’re sorry for the trouble. We’ve been notified of the error and will correct it as soon as possible. Please try your request again in a moment. - + example: >- + Something went wrong while processing your request. We’re sorry for + the trouble. We’ve been notified of the error and will correct it as + soon as possible. Please try your request again in a moment. ServiceUnavailable: type: object description: The server is unavailable @@ -565,7 +589,6 @@ definitions: type: string description: The server error message. example: Something went wrong with the server. - BadRequest: type: object description: The bad request error entity. @@ -574,7 +597,6 @@ definitions: type: string description: The bad request error message. example: Invalid input. - Forbidden: type: object description: The permission error entity. @@ -583,7 +605,6 @@ definitions: type: string description: The forbidden error message. example: You are not allowed to access the request. - Conflict: type: object description: The conflict error entity. diff --git a/package-lock.json b/package-lock.json index c4bfcb8..5fd64a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4267,7 +4267,7 @@ "joi": "^13.4.0", "lodash": "^4.17.15", "superagent": "^3.8.3", - "tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.4" + "tc-core-library-js": "github:appirio-tech/tc-core-library-js#df0b36c51cf80918194cbff777214b3c0cf5a151" }, "dependencies": { "debug": { diff --git a/src/controllers/ResourceController.js b/src/controllers/ResourceController.js index fd290c3..6635a41 100644 --- a/src/controllers/ResourceController.js +++ b/src/controllers/ResourceController.js @@ -11,7 +11,7 @@ const helper = require('../common/helper') * @param {Object} res the response */ async function getResources (req, res) { - const result = await service.getResources(req.authUser, req.query.challengeId, req.query.roleId) + const result = await service.getResources(req.authUser, req.query.challengeId, req.query.roleId, req.query.page, req.query.perPage) helper.setResHeaders(req, res, result) res.send(result) } diff --git a/src/services/ResourceService.js b/src/services/ResourceService.js index f6d1574..f2c49f9 100644 --- a/src/services/ResourceService.js +++ b/src/services/ResourceService.js @@ -34,33 +34,72 @@ async function checkAccess (currentUser, resources) { } } -async function filterForSubmittersOnly (resources) { - const resourcesForSubmitters = _.filter(resources, { roleId: config.SUBMITTER_RESOURCE_ROLE_ID }) - return resourcesForSubmitters -} - /** * Get resources with given challenge id. * @param {Object} currentUser the current user * @param {String} challengeId the challenge id * @param {String} roleId the role id to filter on + * @param {Number} page The page number + * @param {Number} perPage The number of items to list per page * @returns {Array} the search result */ -async function getResources (currentUser, challengeId, roleId = '') { +async function getResources (currentUser, challengeId, roleId, page, perPage) { // Verify that the challenge exists await helper.getRequest(`${config.CHALLENGE_API_URL}/${challengeId}`) - let resources = await helper.query('Resource', { challengeId }) + const boolQuery = [] + const mustQuery = [] + page = page || 1 + perPage = perPage || 20 - // if the user filters on roleId, pull them from the stack - if (roleId) resources = _.filter(resources, { roleId }) + boolQuery.push({ match: { challengeId } }) if (!currentUser || (!currentUser.isMachine && !helper.hasAdminRole(currentUser))) { // await checkAccess(currentUser, resources) // if not admin, and not machine, only return submitters - resources = await filterForSubmittersOnly(resources) + boolQuery.push({ match: { roleId: config.SUBMITTER_RESOURCE_ROLE_ID } }) + } else if (roleId) { + boolQuery.push({ match: { roleId } }) } + mustQuery.push({ + bool: { + filter: boolQuery + } + }) + + const esQuery = { + index: config.get('ES.ES_INDEX'), + type: config.get('ES.ES_TYPE'), + size: perPage, + from: perPage * (page - 1), // Es Index starts from 0 + body: { + query: { + bool: { + must: mustQuery + } + } + } + } + + const esClient = await helper.getESClient() + let docs + try { + docs = await esClient.search(esQuery) + } catch (e) { + // Catch error when the ES is fresh and has no data + logger.info(`Query Error from ES ${JSON.stringify(e)}`) + + docs = { + hits: { + total: 0, + hits: [] + } + } + } + // Extract data from hits + const resources = _.map(docs.hits.hits, item => item._source) + const memberIds = _.uniq(_.map(resources, r => r.memberId)) let memberObjects = [] @@ -92,7 +131,9 @@ async function getResources (currentUser, challengeId, roleId = '') { getResources.schema = { currentUser: Joi.any(), challengeId: Joi.id(), - roleId: Joi.optionalId() + roleId: Joi.optionalId(), + page: Joi.page(), + perPage: Joi.perPage() } /**