Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f285806
Initial stats refactoring
jmgasper Feb 13, 2026
805eb18
PM-3920 Exclude profile picture from completion metric
himaniraghav3 Feb 20, 2026
2b2f323
Merge pull request #79 from topcoder-platform/PM-3920
kkartunov Feb 20, 2026
b23970f
PM-2440: derive fallback handle color from rating during import
jmgasper Feb 22, 2026
e630b50
PM-554: Chunk skill search scoring to avoid timeout
jmgasper Feb 22, 2026
822f2c9
PM-3992: add member SendGrid email activity endpoint
jmgasper Feb 23, 2026
40c1bf8
PM-3924 check for city and country in profile completeness
himaniraghav3 Feb 23, 2026
dc2df6d
Merge pull request #80 from topcoder-platform/PM-3924
kkartunov Feb 23, 2026
b6888fb
SQL script to fix colouring of handles
jmgasper Feb 23, 2026
e9bdb77
Avoid deleting these tables just temporarily, while we migrate the da…
jmgasper Feb 23, 2026
2b01dc9
Merge pull request #71 from topcoder-platform/stats
jmgasper Feb 23, 2026
5248b11
Add mostRecent flag to stats history
jmgasper Feb 24, 2026
db61536
Fix recalculation of mostRecent
jmgasper Feb 24, 2026
ce5300c
PM-3893 Throw error for duplicate phone numbers
himaniraghav3 Feb 24, 2026
dd83bff
add definition
himaniraghav3 Feb 24, 2026
1d807b3
PM-3847 #time 1h added extra logic to remove the nested links in pers…
hentrymartin Feb 24, 2026
0b1d9ac
deploy to dev
hentrymartin Feb 24, 2026
5047ead
Minor tweaks to rating specific stuff
jmgasper Feb 24, 2026
7edcc32
Additional flag to allow us to switch between new vs. old table layou…
jmgasper Feb 24, 2026
9bfa362
Merge pull request #83 from topcoder-platform/stats
jmgasper Feb 25, 2026
1087a97
Allow TMs to view full address
himaniraghav3 Feb 25, 2026
7ca487e
Merge pull request #84 from topcoder-platform/PM-3924
himaniraghav3 Feb 25, 2026
048a4c2
Merge pull request #82 from topcoder-platform/pm-3847_1
kkartunov Feb 25, 2026
d280595
Merge branch 'develop' into PM-3893
himaniraghav3 Feb 25, 2026
bd8aa1c
Merge pull request #81 from topcoder-platform/PM-3893
kkartunov Feb 25, 2026
81bd0cd
PM-3893 Only block duplicates within payload
himaniraghav3 Feb 25, 2026
bdebf26
Clean up code
himaniraghav3 Feb 25, 2026
1ce6ef9
Merge pull request #85 from topcoder-platform/PM-3893
himaniraghav3 Feb 25, 2026
bba3021
Fix for legacy stats retrieval fallback.
jmgasper Feb 25, 2026
27a5136
When auto-completing, show an exact match FIRST in the response list
jmgasper Feb 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ workflows:
branches:
only:
- develop
- PM-3893

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Adding specific feature branches like PM-3893 and pm-3847_1 to the build filters can be useful for testing, but ensure these branches are temporary and removed once the feature is merged to avoid clutter and potential confusion in the workflow configuration.

- pm-3847_1

# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
7 changes: 7 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ After that, please set db URL environment variables:
```bash
export DATABASE_URL="postgresql://johndoe:mypassword@localhost:5432/memberdb"
export SKILLS_DB_URL="postgresql://johndoe:mypassword@localhost:5432/skillsdb"
export CHALLENGE_DB_URL="postgresql://johndoe:mypassword@localhost:5432/challengedb?schema=challenges"
```

These variables are important since they're required by Prisma clients.
`CHALLENGE_DB_URL` should target the same PostgreSQL instance/database used by `challenge-api-v6`.

If you want to do anything with database, these variables are necessary.

Expand All @@ -35,6 +37,7 @@ Before running db scripts, please make sure you have setup db and config db url
# set db url values
export DATABASE_URL="postgresql://johndoe:mypassword@localhost:5432/memberdb"
export SKILLS_DB_URL="postgresql://johndoe:mypassword@localhost:5432/skillsdb"
export CHALLENGE_DB_URL="postgresql://johndoe:mypassword@localhost:5432/challengedb?schema=challenges"

# install dependencies
npm install
Expand Down Expand Up @@ -79,6 +82,8 @@ The following parameters can be set in config files or in env variables:
- PORT: the server port, default is 3000
- AUTH_SECRET: The authorization secret used during token verification.
- VALID_ISSUERS: The valid issuer of tokens.
- IDENTITY_DB_URL: PostgreSQL connection string for Identity schema reads.
- CHALLENGE_DB_URL: PostgreSQL connection string for challenge schema reads (format: `postgresql://user:password@host:port/database?schema=challenges`, and should point to the same database used by challenge-api-v6).
- VANILLA_DB_URL: MySQL connection string for the Vanilla forums database.
- AUTH0_URL: AUTH0 URL, used to get M2M token
- AUTH0_PROXY_SERVER_URL: AUTH0 proxy server URL, used to get M2M token
Expand All @@ -89,6 +94,7 @@ The following parameters can be set in config files or in env variables:
- BUSAPI_URL: Bus API URL
- KAFKA_ERROR_TOPIC: Kafka error topic used by bus API wrapper
- GROUPS_API_URL: Groups API URL
- SENDGRID_API_KEY: SendGrid API key used for member email activity lookups.
- AMAZON.AWS_ACCESS_KEY_ID: The Amazon certificate key to use when connecting.
- AMAZON.AWS_SECRET_ACCESS_KEY: The Amazon certificate access key to use when connecting.
- AMAZON.AWS.SESSION_TOKEN: The user session token, used when developing locally against the TC dev AWS services
Expand All @@ -106,6 +112,7 @@ The following parameters can be set in config files or in env variables:
- MEMBER_TRAIT_SECURE_FIELDS: Member traits identifiable info fields, only admin, M2M, or member himself can fetch these fields
- MISC_SECURE_FIELDS: Misc identifiable info fields, only admin, M2M, or member himself can fetch these fields
- STATISTICS_SECURE_FIELDS: Member Statistics identifiable info fields, only admin, M2M, or member himself can fetch these fields
- STATS_READ_SOURCE: Controls stats read backend, `unified` (default, new tables) or `legacy` (pre-refactor tables)
- HEALTH_CHECK_TIMEOUT: health check timeout in milliseconds

Set the following environment variables used by bus API to get TC M2M token (use 'set' insted of 'export' for Windows OS):
Expand Down
4 changes: 4 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
AUTH_SECRET: process.env.AUTH_SECRET || 'mysecret',
VALID_ISSUERS: process.env.VALID_ISSUERS || '["https://api.topcoder-dev.com", "https://api.topcoder.com", "https://topcoder-dev.auth0.com/", "https://auth.topcoder-dev.com/"]',
IDENTITY_DB_URL: process.env.IDENTITY_DB_URL,
CHALLENGE_DB_URL: process.env.CHALLENGE_DB_URL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
Ensure that CHALLENGE_DB_URL is properly validated and sanitized if it is used to construct database connections, to prevent potential security vulnerabilities such as SQL injection.

RESOURCES_DB_URL: process.env.RESOURCES_DB_URL,
VANILLA_DB_URL: process.env.VANILLA_DB_URL,

Expand Down Expand Up @@ -119,6 +120,8 @@ module.exports = {
STATISTICS_SECURE_FIELDS: process.env.STATISTICS_SECURE_FIELDS
? process.env.STATISTICS_SECURE_FIELDS.split(',')
: ['createdBy', 'updatedBy'],
// Select stats read source: 'unified' (new tables) or 'legacy' (pre-refactor tables)
STATS_READ_SOURCE: process.env.STATS_READ_SOURCE || 'unified',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[⚠️ maintainability]
Consider documenting the possible values for STATS_READ_SOURCE and their implications elsewhere in the codebase or documentation, as this choice could significantly impact data handling and performance.


// Public group id
PUBLIC_GROUP_ID: process.env.PUBLIC_GROUP_ID || '10',
Expand Down Expand Up @@ -146,6 +149,7 @@ module.exports = {

// Finance database connection string
FINANCE_DATABASE_URL: process.env.FINANCE_DATABASE_URL,
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[❗❗ security]
Ensure that SENDGRID_API_KEY is stored securely and not logged or exposed in any way, as it is a sensitive credential.


HUBSPOT_API_KEY: process.env.HUBSPOT_API_KEY,
HUBSPOT_BASE_URL: process.env.HUBSPOT_BASE_URL,
Expand Down
144 changes: 133 additions & 11 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,48 @@ paths:
description: Internal server error
schema:
$ref: '#/definitions/ErrorModel'
'/members/{handle}/sendgrid-emails':
get:
tags:
- Basic
description: |-
Get all SendGrid email activity records sent to the member's current email address in the last 30 days.

Authorization:
- JWT roles: Only `administrator` or `admin`.
- M2M scopes: `read:user_profiles` or `all:user_profiles`.
security:
- bearer: []
parameters:
- in: path
name: handle
required: true
type: string
description: The member handle
responses:
'200':
description: OK
schema:
type: array
items:
type: object
additionalProperties: true
'401':
description: Miss or wrong authentication credentials
schema:
$ref: '#/definitions/ErrorModel'
'403':
description: No permission
schema:
$ref: '#/definitions/ErrorModel'
'404':
description: Not found - Member with the specified handle does not exist
schema:
$ref: '#/definitions/ErrorModel'
'500':
description: Internal server error
schema:
$ref: '#/definitions/ErrorModel'
'/members/{handle}/skills/{skillid}':
get:
tags:
Expand Down Expand Up @@ -1368,7 +1410,14 @@ paths:
tags:
- Statistics
description: >
Retrieve member statistics, optionally filtered by group.
Retrieve member statistics, optionally filtered by group, challenge track, and challenge type.

Filtering behavior:
- `trackId` only: returns all statistics for that track across all types.
- `typeId` only: returns all statistics for that type across all tracks.
- `trackId` + `typeId`: returns statistics for the specific track-type combination.
- Neither `trackId` nor `typeId`: returns all statistics.
- For non-rated tracks/types (for example, Design or First2Finish), rating and rank fields are `null`.

Authorization:
- Public: Anonymous callers receive only public statistics.
Expand All @@ -1384,6 +1433,16 @@ paths:
required: false
type: string
description: comma separated group ids (returns public stats if group ids is not provided)
- name: trackId
in: query
required: false
type: string
description: Filter statistics by challenge track ID (e.g., "DATA_SCIENCE", "DEVELOP", "DESIGN", "QA")
- name: typeId
in: query
required: false
type: string
description: Filter statistics by challenge type ID (e.g., "CH" for Challenge, "F2F" for First2Finish, "TSK" for Task)
- name: fields
in: query
required: false
Expand Down Expand Up @@ -1540,6 +1599,14 @@ paths:
- Statistics
description: >
Get member history statistics, by default only returns the public Topcoder history statistics.
Results can be filtered by challenge track and challenge type for more granular historical views.

Filtering behavior:
- `trackId` only: returns all history statistics for that track across all types.
- `typeId` only: returns all history statistics for that type across all tracks.
- `trackId` + `typeId`: returns history statistics for the specific track-type combination.
- Neither `trackId` nor `typeId`: returns all history statistics.
- For non-rated tracks/types (for example, Design or First2Finish), rating and rank fields are `null`.

Authorization:
- Public: Anonymous callers receive only public history statistics.
Expand All @@ -1555,6 +1622,16 @@ paths:
required: false
type: string
description: comma separated group ids (returns members public history statistics if groupIds is not provided)
- name: trackId
in: query
required: false
type: string
description: Filter history statistics by challenge track ID (e.g., "DATA_SCIENCE", "DEVELOP", "DESIGN", "QA")
- name: typeId
in: query
required: false
type: string
description: Filter history statistics by challenge type ID (e.g., "CH" for Challenge, "F2F" for First2Finish, "TSK" for Task)
- name: fields
in: query
required: false
Expand Down Expand Up @@ -2779,9 +2856,16 @@ definitions:
description: >-
ISO-8601 formatted date times
(YYYY-MM-DDTHH:mm:ss.sssZ)
mostRecent:
type: boolean
description: True if this is the latest history row for the same user, track, and type.
oldRating:
type: number
format: int64
newRating:
type: number
format: int64
description: For mostRecent=true rows, this equals the current member stats rating for the same track/type.
DATA_SCIENCE:
type: object
properties:
Expand All @@ -2802,15 +2886,34 @@ definitions:
type: string
format: date-time
description: 'ISO-8601 formatted date times (YYYY-MM-DDTHH:mm:ss.sssZ)'
rating:
mostRecent:
type: boolean
description: True if this is the latest history row for the same user, track, and type.
oldRating:
type: number
format: double
placement:
format: int64
newRating:
type: number
format: int64
percentile:
description: For mostRecent=true rows, this equals the current member stats rating for the same track/type.
oldGlobalRank:
type: number
format: double
format: int64
newGlobalRank:
type: number
format: int64
oldCountryRank:
type: number
format: int64
newCountryRank:
type: number
format: int64
oldSchoolRank:
type: number
format: int64
newSchoolRank:
type: number
format: int64
MARATHON_MATCH:
type: object
properties:
Expand All @@ -2828,15 +2931,34 @@ definitions:
type: string
format: date-time
description: 'ISO-8601 formatted date times (YYYY-MM-DDTHH:mm:ss.sssZ)'
rating:
mostRecent:
type: boolean
description: True if this is the latest history row for the same user, track, and type.
oldRating:
type: number
format: double
placement:
format: int64
newRating:
type: number
format: int64
percentile:
description: For mostRecent=true rows, this equals the current member stats rating for the same track/type.
oldGlobalRank:
type: number
format: double
format: int64
newGlobalRank:
type: number
format: int64
oldCountryRank:
type: number
format: int64
newCountryRank:
type: number
format: int64
oldSchoolRank:
type: number
format: int64
newSchoolRank:
type: number
format: int64
updatedAt:
type: string
format: date-time
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "TopCoder Member V6 API",
"main": "app.js",
"scripts": {
"postinstall": "prisma generate --schema=prisma/schema.prisma",
"postinstall": "prisma generate --schema=prisma/schema.prisma && prisma generate --schema=prisma/identity-schema.prisma && prisma generate --schema=prisma/challenge-schema.prisma",
"start": "node app.js",
"start:dev": "nodemon app.js",
"lint": "standard",
Expand All @@ -14,7 +14,8 @@
"reset-db": "prisma migrate reset",
"test": "mocha -t 20000 test/unit/*.test.js --exit",
"test:cov": "nyc --reporter=html --reporter=text npm test",
"migrate-dynamo-data": "node src/scripts/migrate-dynamo-data.js"
"migrate-dynamo-data": "node src/scripts/migrate-dynamo-data.js",
"recalculate-member-stats": "node src/scripts/recalculateMemberStats.js"
},
"author": "TCSCODER",
"license": "none",
Expand All @@ -29,8 +30,8 @@
"standard": "^12.0.1"
},
"dependencies": {
"@prisma/client": "^6.10.1",
"@prisma/adapter-pg": "^7.0.1",
"@prisma/client": "^6.10.1",
"@react-pdf/renderer": "^3.4.4",
"@topcoder/challenge-api-v6": "github:topcoder-platform/challenge-api-v6#master",
"@topcoder/engagements-api-v6": "topcoder-platform/engagements-api-v6.git#master",
Expand All @@ -47,6 +48,7 @@
"config": "^3.0.1",
"cors": "^2.7.1",
"country-calling-code": "^0.0.3",
"dotenv": "^17.3.1",
"express": "^4.15.4",
"express-fileupload": "^1.1.4",
"express-interceptor": "^1.2.0",
Expand Down
Loading
Loading