Skip to content

Commit 68687d1

Browse files
Merge pull request #554 from topcoder-platform/gigs-change-aggs
Taas-API change for Gigs Listing change
2 parents fb4a89e + 5cdb9f8 commit 68687d1

File tree

7 files changed

+229
-13
lines changed

7 files changed

+229
-13
lines changed

.circleci/config.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ workflows:
6868
branches:
6969
only:
7070
- dev
71-
- feature/shapeup4-cqrs-update
7271

7372
# Production builds are exectuted only on tagged commits to the
7473
# master branch.

docs/swagger.yaml

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ paths:
102102
schema:
103103
type: string
104104
default: id
105-
enum: ["id", "createdAt", "startDate", "rateType", "status"]
105+
enum: ["id", "createdAt", "updatedAt", "startDate", "rateType", "status"]
106106
description: The sort by column.
107107
- in: query
108108
name: sortOrder
@@ -118,6 +118,24 @@ paths:
118118
schema:
119119
type: integer
120120
description: The project id.
121+
- in: query
122+
name: jobLocation
123+
required: false
124+
schema:
125+
type: string
126+
description: The location of the jobs.
127+
- in: query
128+
name: minSalary
129+
required: false
130+
schema:
131+
type: integer
132+
description: The minimum Salary.
133+
- in: query
134+
name: maxSalary
135+
required: false
136+
schema:
137+
type: integer
138+
description: The maximum Salary.
121139
- in: query
122140
name: isApplicationPageActive
123141
required: false
@@ -4116,6 +4134,12 @@ components:
41164134
description: "The user who updated the job last time.(Will get the user info from the token)"
41174135
JobSearchBody:
41184136
properties:
4137+
bodySkills:
4138+
type: array
4139+
items:
4140+
type: string
4141+
format: uuid
4142+
description: "The array of skill ids"
41194143
jobIds:
41204144
type: array
41214145
items:
@@ -4197,6 +4221,20 @@ components:
41974221
type: string
41984222
format: uuid
41994223
description: "The role id."
4224+
showInHotList:
4225+
type: boolean
4226+
default: false
4227+
featured:
4228+
type: boolean
4229+
default: false
4230+
hotListExcerpt:
4231+
type: string
4232+
example: "This is very hot job"
4233+
description: "The further instruction to show for the hot job"
4234+
jobTag:
4235+
type: string
4236+
enum: ["", "new", "dollor", "hot"]
4237+
description: "The tag of a job"
42004238
isApplicationPageActive:
42014239
type: boolean
42024240
default: false
@@ -4739,6 +4777,20 @@ components:
47394777
type: string
47404778
format: uuid
47414779
description: "The role id."
4780+
showInHotList:
4781+
type: boolean
4782+
default: false
4783+
featured:
4784+
type: boolean
4785+
default: false
4786+
hotListExcerpt:
4787+
type: string
4788+
example: "This is very hot job"
4789+
description: "The further instruction to show for the hot job"
4790+
jobTag:
4791+
type: string
4792+
enum: ["", "new", "dollor", "hot"]
4793+
description: "The tag of a job"
47424794
isApplicationPageActive:
47434795
type: boolean
47444796
default: false
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const config = require('config')
2+
3+
/*
4+
* Add show_in_hot_list, featured, hot_list_excerpt and job_tag to the Job model.
5+
type: Sequelize.BOOLEAN,
6+
defaultValue: false,
7+
allowNull: false
8+
*/
9+
10+
module.exports = {
11+
up: async (queryInterface, Sequelize) => {
12+
const transaction = await queryInterface.sequelize.transaction()
13+
try {
14+
await queryInterface.addColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'show_in_hot_list',
15+
{ type: Sequelize.BOOLEAN, allowNull: true, defaultValue: false },
16+
{ transaction })
17+
await queryInterface.addColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'featured',
18+
{ type: Sequelize.BOOLEAN, allowNull: true, defaultValue: false },
19+
{ transaction })
20+
await queryInterface.addColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'hot_list_excerpt',
21+
{ type: Sequelize.STRING(255), allowNull: true, defaultValue: '' },
22+
{ transaction })
23+
await queryInterface.addColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'job_tag',
24+
{ type: Sequelize.STRING(30), allowNull: true, defaultValue: '' },
25+
{ transaction })
26+
await transaction.commit()
27+
} catch (err) {
28+
await transaction.rollback()
29+
throw err
30+
}
31+
},
32+
down: async (queryInterface, Sequelize) => {
33+
const transaction = await queryInterface.sequelize.transaction()
34+
try {
35+
await queryInterface.removeColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'show_in_hot_list', { transaction })
36+
await queryInterface.removeColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'featured', { transaction })
37+
await queryInterface.removeColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'hot_list_excerpt', { transaction })
38+
await queryInterface.removeColumn({ tableName: 'jobs', schema: config.DB_SCHEMA_NAME }, 'job_tag', { transaction })
39+
await transaction.commit()
40+
} catch (err) {
41+
await transaction.rollback()
42+
throw err
43+
}
44+
}
45+
}

src/bootstrap.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Joi.page = () => Joi.number().integer().min(1).default(1)
1313
Joi.perPage = () => Joi.number().integer().min(1).default(20)
1414
Joi.rateType = () => Joi.string().valid('hourly', 'daily', 'weekly', 'monthly', 'annual')
1515
Joi.jobStatus = () => Joi.string().valid('sourcing', 'in-review', 'assigned', 'closed', 'cancelled')
16+
Joi.jobTag = () => Joi.string().valid('new', 'dollor', 'hot').allow('')
1617
Joi.resourceBookingStatus = () => Joi.string().valid('placed', 'closed', 'cancelled')
1718
Joi.workload = () => Joi.string().valid('full-time', 'fractional')
1819
Joi.jobCandidateStatus = () => Joi.string().valid('open', 'placed', 'selected', 'client rejected - screening', 'client rejected - interview', 'rejected - other', 'cancelled', 'interview', 'topcoder-rejected', 'applied', 'rejected-pre-screen', 'skills-test', 'skills-test', 'phone-screen', 'job-closed', 'offered', 'withdrawn', 'withdrawn-prescreen')

src/controllers/JobController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async function deleteJob (req, res) {
5858
* @param res the response
5959
*/
6060
async function searchJobs (req, res) {
61-
const query = { ...req.query, jobIds: _.get(req, 'body.jobIds', []) }
61+
const query = { ...req.query, jobIds: _.get(req, 'body.jobIds', []), bodySkills: _.get(req, 'body.bodySkills', []) }
6262
const result = await service.searchJobs(req.authUser, query)
6363
helper.setResHeaders(req, res, result)
6464
res.send(result.result)

src/models/Job.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,30 @@ module.exports = (sequelize) => {
140140
type: Sequelize.UUID
141141
})
142142
},
143+
showInHotList: {
144+
field: 'show_in_hot_list',
145+
type: Sequelize.BOOLEAN,
146+
allowNull: true,
147+
defaultValue: false
148+
},
149+
featured: {
150+
field: 'featured',
151+
type: Sequelize.BOOLEAN,
152+
allowNull: true,
153+
defaultValue: false
154+
},
155+
hotListExcerpt: {
156+
field: 'hot_list_excerpt',
157+
type: Sequelize.STRING(255),
158+
allowNull: true,
159+
defaultValue: ''
160+
},
161+
jobTag: {
162+
field: 'job_tag',
163+
type: Sequelize.STRING(30),
164+
allowNull: true,
165+
defaultValue: ''
166+
},
143167
createdBy: {
144168
field: 'created_by',
145169
type: Sequelize.UUID,

src/services/JobService.js

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,11 @@ createJob.schema = Joi.object()
231231
jobLocation: Joi.stringAllowEmpty().allow(null),
232232
jobTimezone: Joi.stringAllowEmpty().allow(null),
233233
currency: Joi.stringAllowEmpty().allow(null),
234-
roleIds: Joi.array().items(Joi.string().uuid().required())
234+
roleIds: Joi.array().items(Joi.string().uuid().required()),
235+
showInHotList: Joi.boolean().default(false),
236+
featured: Joi.boolean().default(false),
237+
hotListExcerpt: Joi.stringAllowEmpty().default(''),
238+
jobTag: Joi.jobTag().default('')
235239
})
236240
.required(),
237241
onTeamCreating: Joi.boolean().default(false)
@@ -327,7 +331,11 @@ partiallyUpdateJob.schema = Joi.object()
327331
jobLocation: Joi.stringAllowEmpty().allow(null),
328332
jobTimezone: Joi.stringAllowEmpty().allow(null),
329333
currency: Joi.stringAllowEmpty().allow(null),
330-
roleIds: Joi.array().items(Joi.string().uuid().required()).allow(null)
334+
roleIds: Joi.array().items(Joi.string().uuid().required()).allow(null),
335+
showInHotList: Joi.boolean().default(false),
336+
featured: Joi.boolean().default(false),
337+
hotListExcerpt: Joi.stringAllowEmpty().default('').allow(null),
338+
jobTag: Joi.jobTag().default('').allow(null)
331339
})
332340
.required()
333341
})
@@ -367,7 +375,11 @@ fullyUpdateJob.schema = Joi.object().keys({
367375
jobLocation: Joi.stringAllowEmpty().allow(null),
368376
jobTimezone: Joi.stringAllowEmpty().allow(null),
369377
currency: Joi.stringAllowEmpty().allow(null),
370-
roleIds: Joi.array().items(Joi.string().uuid().required()).default(null)
378+
roleIds: Joi.array().items(Joi.string().uuid().required()).default(null),
379+
showInHotList: Joi.boolean().default(false),
380+
featured: Joi.boolean().default(false),
381+
hotListExcerpt: Joi.stringAllowEmpty().default('').allow(null),
382+
jobTag: Joi.jobTag().default('').allow(null)
371383
}).required()
372384
}).required()
373385

@@ -444,7 +456,8 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
444456
query: {
445457
bool: {
446458
must: [],
447-
filter: []
459+
filter: [],
460+
should: []
448461
}
449462
},
450463
from: (page - 1) * perPage,
@@ -465,7 +478,9 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
465478
'rateType',
466479
'workload',
467480
'title',
468-
'status'
481+
'status',
482+
'minSalary',
483+
'maxSalary'
469484
]), (value, key) => {
470485
let must
471486
if (key === 'description' || key === 'title') {
@@ -482,6 +497,15 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
482497
[`${key}s`]: [value]
483498
}
484499
}
500+
} else if (key === 'minSalary' || key === 'maxSalary') {
501+
const salaryOp = key === 'minSalary' ? 'gte' : 'lte'
502+
must = {
503+
range: {
504+
[key]: {
505+
[salaryOp]: value
506+
}
507+
}
508+
}
485509
} else {
486510
must = {
487511
term: {
@@ -493,6 +517,27 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
493517
}
494518
esQuery.body.query.bool.must.push(must)
495519
})
520+
// If criteria contains jobLocation, filter jobLocation with this value
521+
if (criteria.jobLocation) {
522+
// filter out null value
523+
esQuery.body.query.bool.should.push({
524+
bool: {
525+
must: [
526+
{
527+
exists: {
528+
field: 'jobLocation'
529+
}
530+
}
531+
]
532+
}
533+
})
534+
// filter the jobLocation
535+
esQuery.body.query.bool.should.push({
536+
term: {
537+
jobLocation: criteria.jobLocation
538+
}
539+
})
540+
}
496541
// If criteria contains projectIds, filter projectId with this value
497542
if (criteria.projectIds) {
498543
esQuery.body.query.bool.filter.push({
@@ -509,6 +554,14 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
509554
}
510555
})
511556
}
557+
// if critera contains bodySkills, filter skills with this value
558+
if (criteria.bodySkills && criteria.bodySkills.length > 0) {
559+
esQuery.body.query.bool.filter.push({
560+
terms: {
561+
skills: criteria.bodySkills
562+
}
563+
})
564+
}
512565
logger.debug({ component: 'JobService', context: 'searchJobs', message: `Query: ${JSON.stringify(esQuery)}` })
513566

514567
const { body } = await esClient.search(esQuery)
@@ -555,9 +608,37 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
555608
[Op.like]: `%${criteria.title}%`
556609
}
557610
}
558-
if (criteria.skill) {
559-
filter.skills = {
560-
[Op.contains]: [criteria.skill]
611+
if (criteria.jobLocation) {
612+
filter.jobLocation = {
613+
[Op.like]: `%${criteria.jobLocation}%`
614+
}
615+
}
616+
if (criteria.skill || (criteria.bodySkills && criteria.bodySkills.length > 0)) {
617+
const skill = criteria.skill
618+
const bodySkills = criteria.bodySkills
619+
if (skill && bodySkills && bodySkills.length > 0) {
620+
filter.skills = {
621+
[Op.and]: [
622+
{
623+
[Op.contains]: [criteria.skill]
624+
},
625+
{
626+
[Op.or]: _.map(bodySkills, (item) => {
627+
return { [Op.contains]: [item] }
628+
})
629+
}
630+
]
631+
}
632+
} else if (skill) {
633+
filter.skills = {
634+
[Op.contains]: [criteria.skill]
635+
}
636+
} else if (bodySkills && bodySkills > 0) {
637+
filter.skills = {
638+
[Op.or]: _.map(bodySkills, (item) => {
639+
return { [Op.contains]: [item] }
640+
})
641+
}
561642
}
562643
}
563644
if (criteria.role) {
@@ -568,6 +649,16 @@ async function searchJobs (currentUser, criteria, options = { returnAll: false }
568649
if (criteria.jobIds && criteria.jobIds.length > 0) {
569650
filter[Op.and].push({ id: criteria.jobIds })
570651
}
652+
if (criteria.minSalary !== undefined) {
653+
filter.minSalary = {
654+
[Op.gte]: criteria.minSalary
655+
}
656+
}
657+
if (criteria.maxSalary !== undefined) {
658+
filter.maxSalary = {
659+
[Op.lte]: criteria.maxSalary
660+
}
661+
}
571662
const jobs = await Job.findAll({
572663
where: filter,
573664
offset: ((page - 1) * perPage),
@@ -594,7 +685,7 @@ searchJobs.schema = Joi.object().keys({
594685
criteria: Joi.object().keys({
595686
page: Joi.number().integer(),
596687
perPage: Joi.number().integer(),
597-
sortBy: Joi.string().valid('id', 'createdAt', 'startDate', 'rateType', 'status'),
688+
sortBy: Joi.string().valid('id', 'createdAt', 'updatedAt', 'startDate', 'rateType', 'status'),
598689
sortOrder: Joi.string().valid('desc', 'asc'),
599690
projectId: Joi.number().integer(),
600691
externalId: Joi.string(),
@@ -609,7 +700,11 @@ searchJobs.schema = Joi.object().keys({
609700
workload: Joi.workload(),
610701
status: Joi.jobStatus(),
611702
projectIds: Joi.array().items(Joi.number().integer()).single(),
612-
jobIds: Joi.array().items(Joi.string().uuid())
703+
jobIds: Joi.array().items(Joi.string().uuid()),
704+
bodySkills: Joi.array().items(Joi.string().uuid()),
705+
minSalary: Joi.number().integer(),
706+
maxSalary: Joi.number().integer(),
707+
jobLocation: Joi.string()
613708
}).required(),
614709
options: Joi.object()
615710
}).required()

0 commit comments

Comments
 (0)