Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ workflows:
- security_july_2026
- hotfix/deploy
- PM-3458
- PM-5919
- build-prod:
context: org-global
filters:
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26.5.0
26.5.1
33 changes: 32 additions & 1 deletion ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,35 @@ Useful commands are:

The API listens on port 3000 by default. Swagger UI is available at `/v6/resources/api-docs`, and the source definition is `docs/swagger.yaml`.

## Resource list filters and visibility

`GET /v6/resources` first establishes the caller's authorized resource set and
then intersects any supplied `memberId`, `memberHandle`, and exact `roleId`
filters with that set. These filters are applied before `X-Total`, ordering, and
pagination are calculated.

- Anonymous challenge reads expose only assignments with the configured
Submitter role.
- Ordinary authenticated members can see challenge Submitters plus their own
assignments for other roles. They may restrict by member only when the
requested ID or handle resolves to their own account; cross-member requests
return `403`.
- Administrators, machine callers, resource managers, members assigned the
challenge's Copilot resource role, and members with another challenge-wide
full-access resource retain their existing visibility, with the same exact
filters applied to their result candidates.

For a paginated registrant list, send the challenge UUID and canonical
Submitter role UUID together, for example:

```text
GET /v6/resources?challengeId=<challenge-uuid>&roleId=<submitter-role-uuid>&page=1&perPage=20
```

For a signed-in member's registration check, also provide that caller's own
member ID. The response pagination headers then describe only that exact
challenge/member/role combination.

## Configuration compatibility

The TypeScript conversion retains the existing environment-variable names and defaults. No deployment parameter rename is required.
Expand Down Expand Up @@ -98,7 +127,7 @@ The retained publishing parameters are:

`EMAIL_NOTIFICATIN_TOPIC` intentionally retains its existing spelling for deployment compatibility. The `KAFKA_*` parameter names are also retained even though this service reaches Kafka indirectly through Bus API.

Registration email payloads continue to use `EMAIL_FROM`, `SENDGRID_TEMPLATE_ID`, `SENDGRID_TEMPLATE_ID_NO_FORUM`, `SUBMIT_URL`, `REVIEW_APP_URL`, `HELP_URL`, and `SUPPORT_EMAIL`. `TOPCROWD_CHALLENGE_TEMPLATE_ID` continues to control whether the registration email event is emitted.
Registration email payloads continue to use `EMAIL_FROM`, `SENDGRID_TEMPLATE_ID`, `SENDGRID_TEMPLATE_ID_NO_FORUM`, `SUBMIT_URL`, `REVIEW_APP_URL`, `HELP_URL`, and `SUPPORT_URL`. `TOPCROWD_CHALLENGE_TEMPLATE_ID` continues to control whether the registration email event is emitted. The `support` and `supportUrl` template fields point members to the Support App at of `support.topcoder.com`.

Publishing remains synchronous. A command that emits an event waits for Bus API to accept it, preserving the existing endpoint success and failure behavior.

Expand Down Expand Up @@ -161,6 +190,8 @@ pnpm run test:newman:clear

The Postman entrypoint uses Newman 6 directly and retains the existing token coverage, per-folder execution order, cleanup, and `newman/reports.html` CircleCI artifact. The `html` reporter name selects that local aggregate report; the supported `cli`, `json`, and `junit` reporter names are passed through to Newman.

The exported manual Postman environment intentionally leaves bearer-token values blank. Populate those values only in a local, uncommitted environment before using the collection; never commit live or example JWTs.

CircleCI performs the Docker build and deployment using the existing `APPNAME`, `DEPLOY_ENV`, and parameter-store paths. The automated-test workflow uses Node 26.5.0, pnpm 11.15.1, and the same deployment environment names.

See `Verification.md` for the regression checklist.
4 changes: 2 additions & 2 deletions app-bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
*
* This module is loaded before controllers and services so their Joi schemas and
* BigInt values retain the behavior exposed by the original JavaScript runtime.
* Node's native Promise remains in place because current HTTP dependencies use
* standard methods such as `Promise.withResolvers` that Bluebird does not expose.
*/

global.Promise = require('bluebird')

const config = require('config')
const Joi = require('joi')

Expand Down
2 changes: 1 addition & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ module.exports = {
SUBMIT_URL: process.env.SUBMIT_URL || 'https://www.topcoder.com/challenges/:id/submit/',
REVIEW_APP_URL: process.env.REVIEW_APP_URL || 'https://review.topcoder.com/active-challenges/',
HELP_URL: process.env.HELP_URL || 'https://help.topcoder.com',
SUPPORT_EMAIL: process.env.SUPPORT_EMAIL || 'support@topcoder.com'
SUPPORT_URL: process.env.SUPPORT_URL || 'https://support.topcoder.com'
},

AUTOMATED_TESTING_NAME_PREFIX: process.env.AUTOMATED_TESTING_NAME_PREFIX || 'POSTMANE2E-',
Expand Down
43 changes: 22 additions & 21 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# syntax=docker/dockerfile:1

ARG NODE_VERSION=26.5.0
ARG ALPINE_VERSION=3.23
ARG NODE_VERSION=26.5.1

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS build
FROM node:${NODE_VERSION}-alpine AS build

ARG PNPM_VERSION=11.15.1

Expand All @@ -30,29 +29,31 @@ RUN mkdir -p src/scripts/seed \
&& pnpm build \
&& pnpm prune --prod

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS production
FROM alpine:3.24 AS production

ARG NODE_VERSION

ENV NODE_ENV=production
WORKDIR /resources-api

RUN apk upgrade --no-cache \
&& apk add --no-cache bash openssl \
&& rm -rf /usr/local/lib/node_modules/npm \
&& rm -f /usr/local/bin/npm /usr/local/bin/npx

COPY --from=build --chown=node:node /resources-api/dist ./dist
COPY --from=build --chown=node:node /resources-api/node_modules ./node_modules
COPY --from=build --chown=node:node /resources-api/package.json ./package.json
COPY --from=build --chown=node:node /resources-api/config ./config
COPY --from=build --chown=node:node /resources-api/prisma.config.ts ./prisma.config.ts
COPY --from=build --chown=node:node /resources-api/prisma ./prisma
COPY --from=build --chown=node:node /resources-api/docs ./docs
COPY --from=build --chown=node:node /resources-api/packages/resources-prisma-client ./packages/resources-prisma-client
COPY --from=build --chown=node:node /resources-api/src/generated ./src/generated
COPY --from=build --chown=node:node /resources-api/src/scripts/seed ./src/scripts/seed
COPY --from=build --chown=node:node --chmod=755 /resources-api/appStartUp.sh ./appStartUp.sh

USER node
&& apk add --no-cache bash "nodejs-current=${NODE_VERSION}-r0" \
&& addgroup -S -g 10001 app \
&& adduser -S -D -H -u 10001 -G app app

COPY --from=build --chown=app:app /resources-api/dist ./dist
COPY --from=build --chown=app:app /resources-api/node_modules ./node_modules
COPY --from=build --chown=app:app /resources-api/package.json ./package.json
COPY --from=build --chown=app:app /resources-api/config ./config
COPY --from=build --chown=app:app /resources-api/prisma.config.ts ./prisma.config.ts
COPY --from=build --chown=app:app /resources-api/prisma ./prisma
COPY --from=build --chown=app:app /resources-api/docs ./docs
COPY --from=build --chown=app:app /resources-api/packages/resources-prisma-client ./packages/resources-prisma-client
COPY --from=build --chown=app:app /resources-api/src/generated ./src/generated
COPY --from=build --chown=app:app /resources-api/src/scripts/seed ./src/scripts/seed
COPY --from=build --chown=app:app --chmod=755 /resources-api/appStartUp.sh ./appStartUp.sh

USER app
EXPOSE 3000

CMD ["./appStartUp.sh"]
20 changes: 16 additions & 4 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ paths:
description: |
Retrieve resources assigned to a challenge with optional filtering and sorting.

Results are filtered in two stages: the API first determines the
caller's visible resource assignments, then intersects the optional
`memberId`, `memberHandle`, and exact `roleId` filters with that set.
Filtering happens before the total, sort order, and page are computed.

Anonymous callers can list only Submitter assignments for a challenge.
Ordinary authenticated members can list challenge Submitters and their
own other assignments. An ordinary member may use `memberId` or
`memberHandle` only for their own account; requesting another member is
rejected with `403`. Administrators, M2M callers, and users with
challenge-wide resource access retain their broader visibility.

### Authentication
- JWT roles: `administrator`, `copilot`, `Connect Manager`, `Topcoder User`
- M2M scopes: `read:resources`, `all:resources`
Expand All @@ -79,15 +91,15 @@ paths:
required: true
- name: memberId
type: integer
description: The member id
description: Exact member id. Ordinary authenticated members may request only their own id.
in: query
- name: memberHandle
type: string
description: The member handle
description: Member handle resolved to an exact member filter. Ordinary authenticated members may request only their own handle.
in: query
- name: roleId
type: string
description: role id to filter on
description: Exact resource-role UUID intersected with the caller's visible assignments before pagination.
format: UUID
in: query
required: false
Expand All @@ -103,7 +115,7 @@ paths:
required: false
responses:
'200':
description: OK - the request was successful
description: OK - the request was successful. Pagination metadata is returned in X-Page, X-Per-Page, X-Total, and X-Total-Pages headers.
schema:
type: array
items:
Expand Down
18 changes: 9 additions & 9 deletions docs/topcoder-challenge-resource-api.postman_environment.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,22 @@
},
{
"key": "user1_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJkZW5pcyIsImV4cCI6MTY4MjgwMDE2OSwidXNlcklkIjoiMjUxMjgwIiwiaWF0IjoxNTQ5Nzk5NTY5LCJlbWFpbCI6ImVtYWlsQGRvbWFpbi5jb20ueiIsImp0aSI6IjljNDUxMWM1LWMxNjUtNGExYi04OTllLWI2NWFkMGUwMmI1NSJ9.BCF6xW3aQfHDDFbgGvvOKzvwEXVLWGf-TgF5JrtM9Tg",
"value": "",
"enabled": true
},
{
"key": "user2_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiY29waWxvdCJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoiaG9ob3NreSIsImV4cCI6MTY4MTc5MjM3MCwidXNlcklkIjoiMTYwOTY4MjMiLCJpYXQiOjE1NDk3OTE3NzAsImVtYWlsIjoiZW1haWxAZG9tYWluLmNvbS56IiwianRpIjoiZjFlNjEzYmUtZDViOS00MjMxLWJhYWUtZWU5ZjJkMjI3MjM0In0.YfzhT9rLFK6U2suPdiCSx_n7Ce6ZIt_oLZ0BcvfMqAk",
"value": "",
"enabled": true
},
{
"key": "admin_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBTdXBwb3J0IiwiYWRtaW5pc3RyYXRvciIsInRlc3RSb2xlIiwiYWFhIiwidG9ueV90ZXN0XzEiLCJDb25uZWN0IE1hbmFnZXIiLCJDb25uZWN0IEFkbWluIiwiY29waWxvdCIsIkNvbm5lY3QgQ29waWxvdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJUb255SiIsImV4cCI6MTY4MTA1MjIxMSwidXNlcklkIjoiODU0Nzg5OSIsImlhdCI6MTU0OTc5MTYxMSwiZW1haWwiOiJ0amVmdHMrZml4QHRvcGNvZGVyLmNvbSIsImp0aSI6ImY5NGQxZTI2LTNkMGUtNDZjYS04MTE1LTg3NTQ1NDRhMDhmMSJ9.ONh2RSE5NeJzyjBD68TSUdNFMs5v5BXtga4LchR2x3s",
"value": "",
"enabled": true
},
{
"key": "m2m_all_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiZW5qdzE4MTBlRHozWFR3U08yUm4yWTljUVRyc3BuM0JAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNTUwOTA2Mzg4LCJleHAiOjE2ODA5OTI3ODgsImF6cCI6ImVuancxODEwZUR6M1hUd1NPMlJuMlk5Y1FUcnNwbjNCIiwic2NvcGUiOiJhbGw6cmVzb3VyY2VzIGFsbDpyZXNvdXJjZV9yb2xlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.UhuqMGrrNzj-VQz2-BogN28YZTE9uauIfdP7EOhrKB0",
"value": "",
"enabled": true
},
{
Expand All @@ -54,17 +54,17 @@
},
{
"key": "expire_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiQ29ubmVjdCBTdXBwb3J0IiwiYWRtaW5pc3RyYXRvciIsInRlc3RSb2xlIiwiYWFhIiwidG9ueV90ZXN0XzEiLCJDb25uZWN0IE1hbmFnZXIiLCJDb25uZWN0IEFkbWluIiwiY29waWxvdCIsIkNvbm5lY3QgQ29waWxvdCBNYW5hZ2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJUb255SiIsImV4cCI6MTU1MTA2MzIxMSwidXNlcklkIjoiODU0Nzg5OSIsImlhdCI6MTU1MTA1MzIxMSwiZW1haWwiOiJ0amVmdHMrZml4QHRvcGNvZGVyLmNvbSIsImp0aSI6ImY5NGQxZTI2LTNkMGUtNDZjYS04MTE1LTg3NTQ1NDRhMDhmMSJ9.97-pjuSGGqDAqK2FG2yi_3nmzB7ZMXQwtG0bi8_PlKk",
"value": "",
"enabled": true
},
{
"key": "m2m_read_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiZW5qdzE4MTBlRHozWFR3U08yUm4yWTljUVRyc3BuM0JAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNTUwOTA2Mzg4LCJleHAiOjE2ODA5OTI3ODgsImF6cCI6ImVuancxODEwZUR6M1hUd1NPMlJuMlk5Y1FUcnNwbjNCIiwic2NvcGUiOiJyZWFkOnJlc291cmNlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.1EdekIx1jU2lDSIXlMFCPmXkzLWQ_ZvxPuutQWuTKu0",
"value": "",
"enabled": true
},
{
"key": "m2m_modify_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiZW5qdzE4MTBlRHozWFR3U08yUm4yWTljUVRyc3BuM0JAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNTUwOTA2Mzg4LCJleHAiOjE2ODA5OTI3ODgsImF6cCI6ImVuancxODEwZUR6M1hUd1NPMlJuMlk5Y1FUcnNwbjNCIiwic2NvcGUiOiJjcmVhdGU6cmVzb3VyY2VzIGRlbGV0ZTpyZXNvdXJjZXMgdXBkYXRlOnJlc291cmNlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyJ9.f_3FLT3jMTnKwGIg8fcFP2lmShy5S9d5IvLNbXSj2Eo",
"value": "",
"enabled": true
},
{
Expand All @@ -84,7 +84,7 @@
},
{
"key": "user3_token",
"value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJsYXJzMjUyMCIsImV4cCI6MTY4MjgwMDE2OSwidXNlcklkIjoiMjg3MTMxIiwiaWF0IjoxNTQ5Nzk5NTY5LCJlbWFpbCI6ImVtYWlsQGRvbWFpbi5jb20ueiIsImp0aSI6IjljNDUxMWM1LWMxNjUtNGExYi04OTllLWI2NWFkMGUwMmI1NSJ9.QIXF4-qSkH7n-i0_4mzF3peFTZ5PjaSYHjm9EjhTfS4",
"value": "",
"enabled": true
},
{
Expand Down Expand Up @@ -118,4 +118,4 @@
"_postman_variable_scope": "environment",
"_postman_exported_at": "2020-03-20T09:04:47.996Z",
"_postman_exported_using": "Postman/7.13.0"
}
}
12 changes: 6 additions & 6 deletions migrator/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions migrator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@
},
"devDependencies": {
"jest": "^29.7.0"
},
"overrides": {
"brace-expansion": "1.1.18",
"js-yaml": "3.15.1"
}
}
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"view-data": "node dist/src/scripts/view-data.js",
"mock-api": "NODE_ENV=test node --require ts-node/register/transpile-only mock/mock-api.js",
"mock-challenge-api": "NODE_ENV=test node --require ts-node/register/transpile-only mock/mock-challenge-api.js",
"test": "nyc --reporter=html --reporter=text mocha --require ts-node/register/transpile-only test/unit/logger.test.ts test/unit/prisma-adapter.test.ts test/unit/test.js --require test/common/prepare.js --timeout 60000 --exit",
"test": "nyc --reporter=html --reporter=text mocha --require ts-node/register/transpile-only test/unit/logger.test.ts test/unit/outbound-url.test.ts test/unit/prisma-adapter.test.ts test/unit/test.js --require test/common/prepare.js --timeout 60000 --exit",
"test:logger": "mocha --require ts-node/register/transpile-only test/unit/logger.test.ts --timeout 10000",
"test:adapter": "mocha --require ts-node/register/transpile-only test/unit/prisma-adapter.test.ts",
"test:newman": "NODE_ENV=test node --require ts-node/register/transpile-only test/postman/newman.js",
Expand Down Expand Up @@ -58,7 +58,6 @@
"@nestjs/platform-express": "^11.1.8",
"@prisma/adapter-pg": "7.8.0",
"@prisma/client": "7.8.0",
"bluebird": "^3.5.1",
"body-parser": "1.20.6",
"config": "^3.0.1",
"cors": "^2.7.1",
Expand Down Expand Up @@ -99,6 +98,6 @@
]
},
"volta": {
"node": "26.5.0"
"node": "26.5.1"
}
}
Loading
Loading