From ad1bdb88b9aeabaa2f9d82fb97efb74ba46cacfa Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 18 Aug 2022 09:27:30 +1000 Subject: [PATCH 1/5] Allow for admin deletion of challenge tracks and types https://github.com/topcoder-platform/challenge-api/issues/519 --- app-constants.js | 4 ++ docs/swagger.yaml | 80 +++++++++++++++++++++ src/controllers/ChallengeTrackController.js | 13 +++- src/controllers/ChallengeTypeController.js | 13 +++- src/routes.js | 14 ++++ src/services/ChallengeTrackService.js | 22 +++++- src/services/ChallengeTypeService.js | 22 +++++- test/e2e/challenge.type.api.test.js | 42 +++++++++++ test/unit/ChallengeTypeService.test.js | 37 ++++++++++ 9 files changed, 243 insertions(+), 4 deletions(-) diff --git a/app-constants.js b/app-constants.js index 2a415635..cf5b2544 100644 --- a/app-constants.js +++ b/app-constants.js @@ -64,6 +64,10 @@ const Topics = { ChallengeDeleted: 'challenge.notification.delete', ChallengeTypeCreated: 'test.new.bus.events', // 'challenge.action.type.created', ChallengeTypeUpdated: 'test.new.bus.events', // 'challenge.action.type.updated', + ChallengeTypeDeleted: 'test.new.bus.events', // 'challenge.action.type.deleted', + ChallengeTrackCreated: 'test.new.bus.events', // 'challenge.action.track.created', + ChallengeTrackUpdated: 'test.new.bus.events', // 'challenge.action.track.updated', + ChallengeTrackDeleted: 'test.new.bus.events', // 'challenge.action.track.deleted', ChallengePhaseCreated: 'test.new.bus.events', // 'challenge.action.phase.created', ChallengePhaseUpdated: 'test.new.bus.events', // 'challenge.action.phase.updated', ChallengePhaseDeleted: 'test.new.bus.events', // 'challenge.action.phase.deleted', diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ec5fbba6..6ea776fb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -908,6 +908,46 @@ paths: description: Internal Server error schema: $ref: "#/definitions/ErrorModel" + delete: + tags: + - ChallengeTypes + description: Delete the challenge type with specified id. + security: + - bearer: [] + produces: + - application/json + parameters: + - name: challengeTypeId + in: path + required: true + type: string + format: UUID + description: The id of challengeType to be deleted + responses: + "200": + description: Deleted - The request was successful and the resource is returned. + schema: + $ref: "#/definitions/ChallengeType" + "400": + description: Bad request. Request parameters were invalid. + schema: + $ref: "#/definitions/ErrorModel" + "401": + description: Unauthorized. Fail to authenticate the requester. + schema: + $ref: "#/definitions/ErrorModel" + "403": + description: Forbidden. The requester does not have the correct permission to delete the challenge type. + schema: + $ref: "#/definitions/ErrorModel" + "404": + description: Not Found. Challenge type not found + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/ErrorModel" /challenge-tracks: get: tags: @@ -1143,6 +1183,46 @@ paths: description: Internal Server Error schema: $ref: "#/definitions/ErrorModel" + delete: + tags: + - ChallengeTracks + description: Delete the challenge track with specified id. + security: + - bearer: [] + produces: + - application/json + parameters: + - name: challengeTrackId + in: path + required: true + type: string + format: UUID + description: The id of challengeTrack to be deleted. + responses: + "200": + description: Deleted - The request was successful and the resource is returned. + schema: + $ref: "#/definitions/ChallengeTrack" + "400": + description: Bad request. Request parameters were invalid. + schema: + $ref: "#/definitions/ErrorModel" + "401": + description: Unauthorized. Fail to authenticate the requester. + schema: + $ref: "#/definitions/ErrorModel" + "403": + description: Forbidden. The requester does not have the correct permission to delete the challenge track. + schema: + $ref: "#/definitions/ErrorModel" + "404": + description: Not Found. Challenge track not found + schema: + $ref: "#/definitions/ErrorModel" + "500": + description: Internal Server Error + schema: + $ref: "#/definitions/ErrorModel" /challenge-phases: get: tags: diff --git a/src/controllers/ChallengeTrackController.js b/src/controllers/ChallengeTrackController.js index 14f97bc1..6e795362 100644 --- a/src/controllers/ChallengeTrackController.js +++ b/src/controllers/ChallengeTrackController.js @@ -56,10 +56,21 @@ async function partiallyUpdateChallengeTrack (req, res) { res.send(result) } +/** + * Delete challenge track + * @param {Object} req the request + * @param {Object} res the response + */ +async function deleteChallengeTrack (req, res) { + const result = await service.deleteChallengeTrack(req.params.challengeTrackId) + res.send(result) +} + module.exports = { searchChallengeTracks, createChallengeTrack, getChallengeTrack, fullyUpdateChallengeTrack, - partiallyUpdateChallengeTrack + partiallyUpdateChallengeTrack, + deleteChallengeTrack } diff --git a/src/controllers/ChallengeTypeController.js b/src/controllers/ChallengeTypeController.js index d0da490d..85d529f2 100644 --- a/src/controllers/ChallengeTypeController.js +++ b/src/controllers/ChallengeTypeController.js @@ -56,10 +56,21 @@ async function partiallyUpdateChallengeType (req, res) { res.send(result) } +/** + * Delete challenge type + * @param {Object} req the request + * @param {Object} res the response + */ +async function deleteChallengeType (req, res) { + const result = await service.deleteChallengeType(req.params.challengeTypeId) + res.send(result) +} + module.exports = { searchChallengeTypes, createChallengeType, getChallengeType, fullyUpdateChallengeType, - partiallyUpdateChallengeType + partiallyUpdateChallengeType, + deleteChallengeType } diff --git a/src/routes.js b/src/routes.js index b02f79fe..5a2b98cd 100644 --- a/src/routes.js +++ b/src/routes.js @@ -113,6 +113,13 @@ module.exports = { auth: 'jwt', access: [constants.UserRoles.Admin, constants.UserRoles.Copilot, constants.UserRoles.Manager], scopes: [UPDATE, ALL] + }, + delete: { + controller: 'ChallengeTypeController', + method: 'deleteChallengeType', + auth: 'jwt', + access: [constants.UserRoles.Admin, constants.UserRoles.Copilot, constants.UserRoles.Manager], + scopes: [DELETE, ALL] } }, '/challenge-tracks': { @@ -146,6 +153,13 @@ module.exports = { auth: 'jwt', access: [constants.UserRoles.Admin, constants.UserRoles.Copilot, constants.UserRoles.Manager], scopes: [UPDATE, ALL] + }, + delete: { + controller: 'ChallengeTrackController', + method: 'deleteChallengeTrack', + auth: 'jwt', + access: [constants.UserRoles.Admin, constants.UserRoles.Copilot, constants.UserRoles.Manager], + scopes: [DELETE, ALL] } }, '/challenge-timelines': { diff --git a/src/services/ChallengeTrackService.js b/src/services/ChallengeTrackService.js index a68402cd..467d0192 100644 --- a/src/services/ChallengeTrackService.js +++ b/src/services/ChallengeTrackService.js @@ -172,12 +172,32 @@ partiallyUpdateChallengeTrack.schema = { }).required() } +/** + * Delete challenge track. + * @param {String} id the challenge track id + * @return {Object} the deleted challenge track + */ +async function deleteChallengeTrack (id) { + const span = await logger.startSpan('ChallengeTrackService.deleteChallengeTrack') + const record = await helper.getById('ChallengeTrack', id) + await record.delete() + // post bus event + await helper.postBusEvent(constants.Topics.ChallengeTrackDeleted, record) + await logger.endSpan(span) + return record +} + +deleteChallengeTrack.schema = { + id: Joi.id() +} + module.exports = { searchChallengeTracks, createChallengeTrack, getChallengeTrack, fullyUpdateChallengeTrack, - partiallyUpdateChallengeTrack + partiallyUpdateChallengeTrack, + deleteChallengeTrack } // logger.buildService(module.exports) diff --git a/src/services/ChallengeTypeService.js b/src/services/ChallengeTypeService.js index 2fe53e3d..7faae32f 100644 --- a/src/services/ChallengeTypeService.js +++ b/src/services/ChallengeTypeService.js @@ -150,12 +150,32 @@ partiallyUpdateChallengeType.schema = { }).required() } +/** + * Delete challenge type. + * @param {String} id the challenge type id + * @returns {Object} the deleted challenge type + */ +async function deleteChallengeType (id) { + const span = await logger.startSpan('ChallengeTypeService.deleteChallengeType') + const ret = await helper.getById('ChallengeType', id) + await ret.delete() + // post bus event + await helper.postBusEvent(constants.Topics.ChallengeTypeDeleted, ret) + await logger.endSpan(span) + return ret +} + +deleteChallengeType.schema = { + id: Joi.id() +} + module.exports = { searchChallengeTypes, createChallengeType, getChallengeType, fullyUpdateChallengeType, - partiallyUpdateChallengeType + partiallyUpdateChallengeType, + deleteChallengeType } // logger.buildService(module.exports) diff --git a/test/e2e/challenge.type.api.test.js b/test/e2e/challenge.type.api.test.js index f290e6de..6fa07697 100644 --- a/test/e2e/challenge.type.api.test.js +++ b/test/e2e/challenge.type.api.test.js @@ -665,4 +665,46 @@ describe('challenge type API E2E tests', () => { should.equal(response.body.message, '"legacyId" must be a number') }) }) + + describe('remove challenge type API tests', () => { + it('remove challenge type - forbidden', async () => { + const response = await chai.request(app) + .delete(`${basePath}/${id}`) + .set('Authorization', `Bearer ${config.USER_TOKEN}`) + should.equal(response.status, 403) + should.equal(response.body.message, 'You are not allowed to perform this action!') + }) + + it('remove challenge type successfully', async () => { + const response = await chai.request(app) + .delete(`${basePath}/${id}`) + .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) + should.equal(response.status, 200) + should.equal(response.body.id, id) + }) + + it('remove challenge type - not found 1', async () => { + const response = await chai.request(app) + .delete(`${basePath}/${id}`) + .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) + should.equal(response.status, 404) + should.equal(response.body.message, `ChallengeType with id: ${id} doesn't exist`) + }) + + it('remove challenge type - not found 2', async () => { + const response = await chai.request(app) + .delete(`${basePath}/${notFoundId}`) + .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) + should.equal(response.status, 404) + should.equal(response.body.message, `ChallengeType with id: ${notFoundId} doesn't exist`) + }) + + it('remove challenge type - invalid id', async () => { + const response = await chai.request(app) + .delete(`${basePath}/invalid`) + .set('Authorization', `Bearer ${config.ADMIN_TOKEN}`) + should.equal(response.status, 400) + should.equal(response.body.message, '"challengeTypeId" must be a valid GUID') + }) + }) }) diff --git a/test/unit/ChallengeTypeService.test.js b/test/unit/ChallengeTypeService.test.js index ea3944b3..cdc613e9 100644 --- a/test/unit/ChallengeTypeService.test.js +++ b/test/unit/ChallengeTypeService.test.js @@ -630,4 +630,41 @@ describe('challenge type service unit tests', () => { throw new Error('should not reach here') }) }) + + describe('remove challenge type tests', () => { + it('remove challenge type successfully', async () => { + const result = await service.deleteChallengeType(id2) + should.equal(result.id, id2) + }) + + it('remove challenge type - not found 1', async () => { + try { + await service.deleteChallengeType(notFoundId) + } catch (e) { + should.equal(e.message, `ChallengeType with id: ${notFoundId} doesn't exist`) + return + } + throw new Error('should not reach here') + }) + + it('remove challenge type - not found 2', async () => { + try { + await service.deleteChallengeType(id2) + } catch (e) { + should.equal(e.message, `ChallengeType with id: ${id2} doesn't exist`) + return + } + throw new Error('should not reach here') + }) + + it('remove challenge type - invalid id', async () => { + try { + await service.deleteChallengeType('invalid') + } catch (e) { + should.equal(e.message.indexOf('"challengeType" must be a valid GUID') >= 0, true) + return + } + throw new Error('should not reach here') + }) + }) }) From 220e614aff93ff69a326005949c35246d737c69f Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Fri, 10 Feb 2023 11:03:45 +0600 Subject: [PATCH 2/5] chore: remove unusable tracing code --- src/services/ChallengeTrackService.js | 2 -- src/services/ChallengeTypeService.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/services/ChallengeTrackService.js b/src/services/ChallengeTrackService.js index 467d0192..3e9905e6 100644 --- a/src/services/ChallengeTrackService.js +++ b/src/services/ChallengeTrackService.js @@ -178,12 +178,10 @@ partiallyUpdateChallengeTrack.schema = { * @return {Object} the deleted challenge track */ async function deleteChallengeTrack (id) { - const span = await logger.startSpan('ChallengeTrackService.deleteChallengeTrack') const record = await helper.getById('ChallengeTrack', id) await record.delete() // post bus event await helper.postBusEvent(constants.Topics.ChallengeTrackDeleted, record) - await logger.endSpan(span) return record } diff --git a/src/services/ChallengeTypeService.js b/src/services/ChallengeTypeService.js index 7faae32f..71072694 100644 --- a/src/services/ChallengeTypeService.js +++ b/src/services/ChallengeTypeService.js @@ -156,12 +156,10 @@ partiallyUpdateChallengeType.schema = { * @returns {Object} the deleted challenge type */ async function deleteChallengeType (id) { - const span = await logger.startSpan('ChallengeTypeService.deleteChallengeType') const ret = await helper.getById('ChallengeType', id) await ret.delete() // post bus event await helper.postBusEvent(constants.Topics.ChallengeTypeDeleted, ret) - await logger.endSpan(span) return ret } From bf3fce54c7323875a1d18236ec84d28ab2ba0a3c Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 13 Oct 2022 11:11:38 +1100 Subject: [PATCH 3/5] Filters in the competitive programming section are not giving correct results https://github.com/topcoder-platform/challenge-api/issues/525 --- src/services/ChallengeService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 841a0396..a0cbd32a 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -165,7 +165,7 @@ async function searchChallenges (currentUser, criteria) { if (criteria.types) { for (const t of criteria.types) { const typeSearchRes = await ChallengeTypeService.searchChallengeTypes({ abbreviation: t }) - if (typeSearchRes.total > 0) { + if (typeSearchRes.total > 0 || criteria.types.length === 1) { includedTypeIds.push(_.get(typeSearchRes, 'result[0].id')) } } From c7894f4912b304f4a18f60369319284e5b3b7a74 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Sun, 30 Oct 2022 17:17:46 +1100 Subject: [PATCH 4/5] Test for Add new sort types --- app-constants.js | 4 ++++ docs/swagger.yaml | 5 ++++- src/init-es.js | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app-constants.js b/app-constants.js index cf5b2544..7cad4648 100644 --- a/app-constants.js +++ b/app-constants.js @@ -45,6 +45,10 @@ const validChallengeParams = { StartDate: 'startDate', ProjectId: 'projectId', Name: 'name', + Type: 'type', + NumOfSubmissions: 'numOfSubmissions', + NumOfRegistrants: 'numOfRegistrants', + Status: 'status', TypeId: 'typeId', Prizes: 'overview.totalPrizes' } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 6ea776fb..e261a2ed 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -360,6 +360,9 @@ paths: projectId, name, typeId, + numOfRegistrants, + numOfSubmissions, + status, overview.totalPrizes, ] - name: sortOrder @@ -3007,7 +3010,7 @@ definitions: type: boolean selfServiceCopilot: type: string - cancelReason: + cancelReason: type: string billing: type: object diff --git a/src/init-es.js b/src/init-es.js index 918fe9da..1b5f8a40 100644 --- a/src/init-es.js +++ b/src/init-es.js @@ -41,6 +41,24 @@ const initES = async () => { }, normalizer: 'custom_sort_normalizer' }, + status: { + type: 'keyword', + fields: { + text: { + type: 'text' + } + }, + normalizer: 'custom_sort_normalizer' + }, + type: { + type: 'keyword', + fields: { + text: { + type: 'text' + } + }, + normalizer: 'custom_sort_normalizer' + }, prizeSets: { properties: { type: { type: 'text' }, From d6ff83c5fda64a2f3ad3422ff8cb9349ac1a3aec Mon Sep 17 00:00:00 2001 From: Rakib Ansary Date: Fri, 10 Feb 2023 11:04:51 +0600 Subject: [PATCH 5/5] ci: deploy to dev --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5584ef2c..4eb7fa3c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,8 +71,7 @@ workflows: filters: branches: only: - - develop - - feature/PLAT-2032 + - dev # Production builds are exectuted only on tagged commits to the # master branch.