Skip to content

Commit 7c91c31

Browse files
committed
Remove deprecated Kafka topics
1 parent a683a86 commit 7c91c31

7 files changed

Lines changed: 152 additions & 87 deletions

File tree

.github/workflows/trivy.yaml

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,44 @@
11
name: Trivy Scanner
22

3-
permissions:
4-
contents: read
5-
security-events: write
63
on:
74
push:
85
branches:
96
- main
7+
- master
108
- dev
9+
- develop
1110
pull_request:
11+
workflow_dispatch:
12+
13+
permissions:
14+
actions: read
15+
contents: read
16+
security-events: write
17+
1218
jobs:
1319
trivy-scan:
14-
name: Use Trivy
20+
name: Trivy SAST and SCA
1521
runs-on: ubuntu-24.04
1622
steps:
1723
- name: Checkout code
1824
uses: actions/checkout@v4
1925

2026
- name: Run Trivy scanner in repo mode
21-
uses: aquasecurity/trivy-action@0.35.0
27+
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
2228
with:
29+
version: "v0.73.0"
2330
scan-type: "fs"
31+
scan-ref: "."
2432
ignore-unfixed: true
2533
format: "sarif"
2634
output: "trivy-results.sarif"
2735
severity: "CRITICAL,HIGH,UNKNOWN"
36+
limit-severities-for-sarif: true
2837
scanners: vuln,secret,misconfig,license
2938
github-pat: ${{ secrets.GITHUB_TOKEN }}
3039

3140
- name: Upload Trivy scan results to GitHub Security tab
32-
uses: github/codeql-action/upload-sarif@v3
41+
if: always()
42+
uses: github/codeql-action/upload-sarif@v4
3343
with:
3444
sarif_file: "trivy-results.sarif"

ReadMe.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
- PostgreSQL
1010
- Docker and Docker Compose
1111

12-
The service does not consume Kafka messages. It continues to publish events through
13-
the Bus API wrapper, so the existing Bus API and Auth0 configuration remains unchanged.
12+
The service does not consume Kafka messages. It uses the Bus API wrapper only for
13+
email-change verification events; profile and trait mutations do not publish events.
14+
The existing Bus API and Auth0 configuration remains required for email verification.
1415

1516
## Install, Build, and Run
1617

app-constants.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,7 @@ const EVENT_ORIGINATOR = 'topcoder-member-api'
1111
const EVENT_MIME_TYPE = 'application/json'
1212

1313
const TOPICS = {
14-
MemberCreated: 'member.action.profile.create',
15-
MemberUpdated: 'member.action.profile.update',
16-
EmailChanged: 'member.action.email.profile.emailchange.verification',
17-
MemberTraitCreated: 'member.action.profile.trait.create',
18-
MemberTraitUpdated: 'member.action.profile.trait.update',
19-
MemberTraitDeleted: 'member.action.profile.trait.delete',
20-
MemberSkillsCreated: 'member.action.profile.skills.create',
21-
MemberSkillsUpdated: 'member.action.profile.skills.update'
14+
EmailChanged: 'member.action.email.profile.emailchange.verification'
2215
}
2316

2417
const MAMBO_GET_REWARDS_ALLOWED_FIELDS = [

src/services/MemberService.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,8 +905,6 @@ async function updateMember (currentUser, handle, query, data) {
905905

906906
// convert prisma data to response format
907907
prismaHelper.convertMember(result)
908-
// send data to event bus
909-
await helper.postBusEvent(constants.TOPICS.MemberUpdated, result)
910908
if (emailChanged) {
911909
// send email verification to old email
912910
await helper.postBusEvent(constants.TOPICS.EmailChanged, {
@@ -1078,7 +1076,6 @@ async function updateHandle (currentUser, handle, query, data) {
10781076
}
10791077

10801078
prismaHelper.convertMember(updatedMember)
1081-
await helper.postBusEvent(constants.TOPICS.MemberUpdated, updatedMember)
10821079
return cleanMember(currentUser, updatedMember, selectFields)
10831080
}
10841081

@@ -1142,7 +1139,6 @@ async function verifyEmail (currentUser, handle, query) {
11421139
data: _.omit(member, ['maxRating', 'phones'])
11431140
})
11441141
prismaHelper.convertMember(result)
1145-
await helper.postBusEvent(constants.TOPICS.MemberUpdated, result)
11461142
return { emailChangeCompleted, verifiedEmail }
11471143
}
11481144

@@ -1221,8 +1217,6 @@ async function uploadPhoto (currentUser, handle, files) {
12211217
}
12221218
})
12231219
prismaHelper.convertMember(result)
1224-
// post bus event
1225-
await helper.postBusEvent(constants.TOPICS.MemberUpdated, result)
12261220
return { photoURL }
12271221
}
12281222

@@ -1343,7 +1337,6 @@ async function deleteMember (currentUser, handle, data) {
13431337
}
13441338

13451339
prismaHelper.convertMember(updatedMember)
1346-
await helper.postBusEvent(constants.TOPICS.MemberUpdated, updatedMember)
13471340

13481341
return {
13491342
handle: deletedHandle,

src/services/MemberTraitService.ts

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const moment = require('moment')
99
const helper = require('../common/helper')
1010
const logger = require('../common/logger')
1111
const errors = require('../common/errors')
12-
const constants = require('../../app-constants')
1312
const prisma = require('../common/prisma').getClient()
1413
const prismaManager = require('../common/prisma')
1514
const skillsPrisma = prismaManager.getSkillsClient()
@@ -479,22 +478,6 @@ async function createTraits (currentUser, handle, data) {
479478
data: prismaData
480479
})
481480
}
482-
// send data to event bus
483-
for (let item of data) {
484-
const trait = { ...item }
485-
trait.userId = helper.bigIntToNumber(member.userId)
486-
trait.createdBy = Number(currentUser.userId || config.TC_WEBSERVICE_USERID)
487-
if (trait.traits) {
488-
trait.traits = { 'traitId': trait.traitId, 'data': trait.traits.data }
489-
} else {
490-
trait.traits = { 'traitId': trait.traitId, 'data': [] }
491-
}
492-
// convert date time
493-
trait.createdAt = new Date().getTime()
494-
// post bus event
495-
await helper.postBusEvent(constants.TOPICS.MemberTraitCreated, trait)
496-
}
497-
498481
// merge result
499482
existingTraits = _.concat(existingTraits, data)
500483

@@ -575,24 +558,6 @@ async function updateTraits (currentUser, handle, data) {
575558
await prisma.memberTraits.create({ data: createData })
576559
}
577560

578-
// post bus events: created for new traits, updated for existing ones
579-
const existingIds = new Set((existingTraits || []).map(t => t.traitId))
580-
for (let r of result) {
581-
if (!existingIds.has(r.traitId)) {
582-
const trait = { ...r }
583-
trait.userId = helper.bigIntToNumber(member.userId)
584-
trait.createdBy = Number(currentUser.userId || config.TC_WEBSERVICE_USERID)
585-
if (trait.traits) {
586-
trait.traits = { traitId: trait.traitId, data: trait.traits.data }
587-
} else {
588-
trait.traits = { traitId: trait.traitId, data: [] }
589-
}
590-
trait.createdAt = new Date().getTime()
591-
await helper.postBusEvent(constants.TOPICS.MemberTraitCreated, trait)
592-
} else {
593-
await helper.postBusEvent(constants.TOPICS.MemberTraitUpdated, r)
594-
}
595-
}
596561
return result
597562
}
598563

@@ -632,26 +597,10 @@ async function removeTraits (currentUser, handle, query) {
632597
})))
633598
})
634599
}
635-
// remove existingTraits data
636-
const memberProfileTraitIds = []
637-
_.forEach(existingTraits, t => {
638-
if (!traitIds || _.includes(traitIds, t.traitId)) {
639-
memberProfileTraitIds.push(t.traitId)
640-
}
641-
})
642-
600+
// remove deleted traits from the data used to recalculate skill-score deductions
643601
existingTraits = _.filter(existingTraits, t => !traitIds.includes(t.traitId))
644602

645603
await updateSkillScoreDeduction(currentUser, member, existingTraits)
646-
// post bus event
647-
if (memberProfileTraitIds.length > 0) {
648-
await helper.postBusEvent(constants.TOPICS.MemberTraitDeleted, {
649-
userId: helper.bigIntToNumber(member.userId),
650-
memberProfileTraitIds,
651-
updatedAt: new Date(),
652-
updatedBy: currentUser.userId || currentUser.sub
653-
})
654-
}
655604
}
656605

657606
removeTraits.schema = {

src/services/SearchService.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,33 +64,56 @@ const MEMBER_SELECT_EXCLUDED_FIELDS = ['addresses', 'maxRating', 'skills', 'stat
6464
const BULK_IDENTIFIER_MAX_LENGTH = 256
6565
const BULK_EMAIL_REGEX = /^[+_A-Za-z0-9-]+(\.[+_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\.[A-Za-z0-9]+)*(\.[A-Za-z]{2,}$)/
6666
const BULK_HANDLE_REGEX = /^[-A-Za-z0-9_.`{}[\]]+$/
67+
const MAX_USER_ID = BigInt('9223372036854775807')
68+
const USER_ID_QUERY_ITEM_SCHEMA = Joi.alternatives().try(
69+
Joi.string().pattern(/^\d+$/).custom((value, helpers) => (
70+
BigInt(value) <= MAX_USER_ID ? value : helpers.error('string.userIdRange')
71+
)).messages({
72+
'string.userIdRange': '{{#label}} must not exceed the maximum 64-bit user ID'
73+
}),
74+
Joi.number().integer().min(0).strict()
75+
)
6776

6877
/**
6978
* Accept arrays parsed by `qs` as well as the JSON-array query format documented
7079
* by this API. Joi 14 coerced JSON array strings automatically, while Joi 18
7180
* no longer coerces them.
81+
* @param {Object} [itemSchema] optional Joi schema used to validate and normalize items
7282
* @returns {Object} Joi schema for a query-string array
7383
*/
74-
function queryArraySchema () {
84+
function queryArraySchema (itemSchema = null) {
85+
const itemArraySchema = itemSchema && Joi.array().items(itemSchema)
86+
7587
return Joi.any().custom((value, helpers) => {
76-
if (_.isArray(value)) {
77-
return value
78-
}
88+
let parsed = value
7989

80-
if (_.isString(value)) {
90+
if (_.isArray(value)) {
91+
parsed = value
92+
} else if (_.isString(value)) {
8193
try {
82-
const parsed = JSON.parse(value)
83-
if (_.isArray(parsed)) {
84-
return parsed
85-
}
94+
parsed = JSON.parse(value)
8695
} catch (err) {
8796
// Fall through to the same validation error as a non-array value.
8897
}
8998
}
9099

91-
return helpers.error('array.base')
100+
if (!_.isArray(parsed)) {
101+
return helpers.error('array.base')
102+
}
103+
104+
if (!itemSchema) {
105+
return parsed
106+
}
107+
108+
const validation = itemArraySchema.validate(parsed, { abortEarly: false })
109+
if (validation.error) {
110+
return helpers.error('queryArray.items', { details: validation.error.message })
111+
}
112+
113+
return validation.value
92114
}).messages({
93-
'array.base': '{{#label}} must be an array'
115+
'array.base': '{{#label}} must be an array',
116+
'queryArray.items': '{{#label}} contains invalid values: {{#details}}'
94117
})
95118
}
96119

@@ -442,7 +465,7 @@ searchMembers.schema = {
442465
handles: queryArraySchema(),
443466
email: Joi.string(),
444467
userId: Joi.number(),
445-
userIds: queryArraySchema(),
468+
userIds: queryArraySchema(USER_ID_QUERY_ITEM_SCHEMA),
446469
term: Joi.string(),
447470
fields: Joi.string(),
448471
includeStats: Joi.string(),

test/unit/SearchService.test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,102 @@ describe('search service unit tests', () => {
5454
}
5555
})
5656

57+
it('searchMembers should accept qs decimal-string userIds without losing precision', async () => {
58+
const prisma = prismaManager.getClient()
59+
const originalMemberCount = prisma.member.count
60+
let memberFilter
61+
62+
try {
63+
prisma.member.count = async (filter) => {
64+
memberFilter = filter
65+
return 0
66+
}
67+
68+
await service.searchMembers(
69+
{ isMachine: true },
70+
{
71+
userIds: ['100000013', '9223372036854775807'],
72+
fields: 'handle,userId'
73+
}
74+
)
75+
76+
memberFilter.where.AND.should.deep.equal([
77+
{ userId: { in: ['100000013', '9223372036854775807'] } }
78+
])
79+
} finally {
80+
prisma.member.count = originalMemberCount
81+
}
82+
})
83+
84+
it('searchMembers should preserve an empty userIds array', async () => {
85+
const prisma = prismaManager.getClient()
86+
const originalMemberCount = prisma.member.count
87+
let memberFilter
88+
89+
try {
90+
prisma.member.count = async (filter) => {
91+
memberFilter = filter
92+
return 0
93+
}
94+
95+
await service.searchMembers(
96+
{ isMachine: true },
97+
{
98+
userIds: [],
99+
fields: 'handle,userId'
100+
}
101+
)
102+
103+
memberFilter.where.AND.should.deep.equal([])
104+
} finally {
105+
prisma.member.count = originalMemberCount
106+
}
107+
})
108+
109+
it('searchMembers should reject invalid userIds before querying Prisma', async () => {
110+
const prisma = prismaManager.getClient()
111+
const originalMemberCount = prisma.member.count
112+
let memberCountCalled = false
113+
let validationError
114+
115+
try {
116+
prisma.member.count = async () => {
117+
memberCountCalled = true
118+
return 0
119+
}
120+
121+
for (const userIds of [
122+
['TCConnCopilot'],
123+
[Number.MAX_SAFE_INTEGER + 1],
124+
['9223372036854775808'],
125+
['']
126+
]) {
127+
validationError = undefined
128+
129+
try {
130+
await service.searchMembers(
131+
{ isMachine: true },
132+
{
133+
userIds,
134+
fields: 'handle,userId'
135+
}
136+
)
137+
} catch (err) {
138+
validationError = err
139+
}
140+
141+
should.exist(validationError)
142+
validationError.isJoi.should.equal(true)
143+
validationError.details[0].path.should.deep.equal(['query', 'userIds'])
144+
validationError.details[0].type.should.equal('queryArray.items')
145+
}
146+
147+
memberCountCalled.should.equal(false)
148+
} finally {
149+
prisma.member.count = originalMemberCount
150+
}
151+
})
152+
57153
it('searchMembers should skip stats and skills hydration for explicit field-limited lookups', async () => {
58154
const prisma = prismaManager.getClient()
59155
const skillsPrisma = prismaManager.getSkillsClient()

0 commit comments

Comments
 (0)