diff --git a/docs/Topcoder-bookings-api.postman_collection.json b/docs/Topcoder-bookings-api.postman_collection.json index c0561ea1..6eb03c1f 100644 --- a/docs/Topcoder-bookings-api.postman_collection.json +++ b/docs/Topcoder-bookings-api.postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "cc3d894d-9cf4-4711-bd64-5515b4cf945c", + "_postman_id": "b1144d77-699c-47be-864e-fbe99e7f5c14", "name": "Topcoder-bookings-api", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -1625,7 +1625,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -1669,7 +1669,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -1713,7 +1713,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -1757,7 +1757,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -1801,7 +1801,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -1844,7 +1844,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2043,6 +2043,11 @@ "key": "status", "value": "shortlist", "disabled": true + }, + { + "key": "externalId", + "value": "300234321", + "disabled": true } ] } @@ -2103,6 +2108,11 @@ "key": "status", "value": "shortlist", "disabled": true + }, + { + "key": "externalId", + "value": "300234321", + "disabled": true } ] } @@ -2163,6 +2173,11 @@ "key": "status", "value": "shortlist", "disabled": true + }, + { + "key": "externalId", + "value": "300234321", + "disabled": true } ] } @@ -2221,6 +2236,11 @@ "key": "status", "value": "shortlist", "disabled": true + }, + { + "key": "externalId", + "value": "300234321", + "disabled": true } ] } @@ -2279,6 +2299,11 @@ "key": "status", "value": "shortlist", "disabled": true + }, + { + "key": "externalId", + "value": "300234321", + "disabled": true } ] } @@ -2298,7 +2323,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2331,7 +2356,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobIdCreatedByM2M}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2364,7 +2389,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2397,7 +2422,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2430,7 +2455,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2463,7 +2488,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"jobId\": \"{{jobId}}\",\r\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\r\n \"status\": \"selected\"\r\n}", + "raw": "{\n \"jobId\": \"{{jobId}}\",\n \"userId\": \"a55fe1bc-1754-45fa-9adc-cf3d6d7c377a\",\n \"status\": \"selected\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2496,7 +2521,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2529,7 +2554,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2562,7 +2587,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2595,7 +2620,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2628,7 +2653,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" @@ -2661,7 +2686,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"status\": \"shortlist\"\r\n}", + "raw": "{\n \"status\": \"shortlist\",\n \"externalId\": \"300234321\",\n \"resume\": \"http://example.com\"\n}", "options": { "raw": { "language": "json" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index be990521..85c2f429 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -584,6 +584,12 @@ paths: type: string enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] description: The job candidate status. + - in: query + name: externalId + required: false + schema: + type: string + description: The external id. responses: '200': description: OK @@ -1797,6 +1803,14 @@ components: type: string enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] description: "The job candidate status." + externalId: + type: string + example: "1212" + description: "The external id." + resume: + type: string + example: "http://example.com" + description: "The resume link" createdAt: type: string format: date-time @@ -1827,11 +1841,27 @@ components: format: uuid example: "a55fe1bc-1754-45fa-9adc-cf3d6d7c377a" description: "The user id." + externalId: + type: string + example: "1212" + description: "The external id." + resume: + type: string + example: "http://example.com" + description: "The resume link" JobCandidatePatchRequestBody: properties: status: type: string enum: ['open', 'selected', 'shortlist', 'rejected', 'cancelled'] + externalId: + type: string + example: "1212" + description: "The external id." + resume: + type: string + example: "http://example.com" + description: "The resume link" JobPatchRequestBody: properties: status: diff --git a/migrations/2021-01-07-job-candidate-add-external-id-and-resume-fields.js b/migrations/2021-01-07-job-candidate-add-external-id-and-resume-fields.js new file mode 100644 index 00000000..6ca47bba --- /dev/null +++ b/migrations/2021-01-07-job-candidate-add-external-id-and-resume-fields.js @@ -0,0 +1,18 @@ +/* + * Add externalId and resume fields to the JobCandidate model. + */ + +module.exports = { + up: queryInterface => { + return Promise.all([ + queryInterface.sequelize.query('ALTER TABLE bookings.job_candidates ADD external_id VARCHAR(255)'), + queryInterface.sequelize.query('ALTER TABLE bookings.job_candidates ADD resume VARCHAR(2048)') + ]) + }, + down: queryInterface => { + return Promise.all([ + queryInterface.sequelize.query('ALTER TABLE bookings.job_candidates DROP external_id'), + queryInterface.sequelize.query('ALTER TABLE bookings.job_candidates DROP resume') + ]) + } +} diff --git a/scripts/createIndex.js b/scripts/createIndex.js index f28e762b..4bba1957 100644 --- a/scripts/createIndex.js +++ b/scripts/createIndex.js @@ -43,6 +43,8 @@ async function createIndex () { jobId: { type: 'keyword' }, userId: { type: 'keyword' }, status: { type: 'keyword' }, + externalId: { type: 'keyword' }, + resume: { type: 'text' }, createdAt: { type: 'date' }, createdBy: { type: 'keyword' }, updatedAt: { type: 'date' }, diff --git a/src/models/JobCandidate.js b/src/models/JobCandidate.js index 285d6f36..20d0831c 100644 --- a/src/models/JobCandidate.js +++ b/src/models/JobCandidate.js @@ -56,6 +56,13 @@ module.exports = (sequelize) => { type: Sequelize.STRING, allowNull: false }, + externalId: { + field: 'external_id', + type: Sequelize.STRING + }, + resume: { + type: Sequelize.STRING + }, createdAt: { field: 'created_at', type: Sequelize.DATE, diff --git a/src/services/JobCandidateService.js b/src/services/JobCandidateService.js index 0aca7bc2..57e8a3d8 100644 --- a/src/services/JobCandidateService.js +++ b/src/services/JobCandidateService.js @@ -103,7 +103,9 @@ createJobCandidate.schema = Joi.object().keys({ currentUser: Joi.object().required(), jobCandidate: Joi.object().keys({ jobId: Joi.string().uuid().required(), - userId: Joi.string().uuid().required() + userId: Joi.string().uuid().required(), + externalId: Joi.string(), + resume: Joi.string().uri() }).required() }).required() @@ -148,7 +150,9 @@ partiallyUpdateJobCandidate.schema = Joi.object().keys({ currentUser: Joi.object().required(), id: Joi.string().uuid().required(), data: Joi.object().keys({ - status: Joi.jobCandidateStatus() + status: Joi.jobCandidateStatus(), + externalId: Joi.string(), + resume: Joi.string().uri() }).required() }).required() @@ -171,7 +175,9 @@ fullyUpdateJobCandidate.schema = Joi.object().keys({ data: Joi.object().keys({ jobId: Joi.string().uuid().required(), userId: Joi.string().uuid().required(), - status: Joi.jobCandidateStatus() + status: Joi.jobCandidateStatus(), + externalId: Joi.string(), + resume: Joi.string().uri() }).required() }).required() @@ -236,7 +242,7 @@ async function searchJobCandidates (currentUser, criteria) { } } - _.each(_.pick(criteria, ['jobId', 'userId', 'status']), (value, key) => { + _.each(_.pick(criteria, ['jobId', 'userId', 'status', 'externalId']), (value, key) => { esQuery.body.query.bool.must.push({ term: { [key]: { @@ -266,7 +272,7 @@ async function searchJobCandidates (currentUser, criteria) { const filter = { [Op.and]: [{ deletedAt: null }] } - _.each(_.pick(criteria, ['jobId', 'userId', 'status']), (value, key) => { + _.each(_.pick(criteria, ['jobId', 'userId', 'status', 'externalId']), (value, key) => { filter[Op.and].push({ [key]: value }) }) const jobCandidates = await JobCandidate.findAll({ @@ -296,7 +302,8 @@ searchJobCandidates.schema = Joi.object().keys({ sortOrder: Joi.string().valid('desc', 'asc'), jobId: Joi.string().uuid(), userId: Joi.string().uuid(), - status: Joi.jobCandidateStatus() + status: Joi.jobCandidateStatus(), + externalId: Joi.string() }).required() }).required()