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
18 changes: 14 additions & 4 deletions .github/workflows/trivy.yaml
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
name: Trivy Scanner

permissions:
actions: read
contents: read
security-events: write

on:
push:
branches:
- main
- master
- dev
- develop
pull_request:
workflow_dispatch:

jobs:
trivy-scan:
name: Use Trivy
name: Trivy SAST and SCA
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Run Trivy scanner in repo mode
uses: aquasecurity/trivy-action@0.35.0
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
version: "v0.73.0"
scan-type: "fs"
scan-ref: "."
ignore-unfixed: true
format: "sarif"
output: "trivy-results.sarif"
severity: "CRITICAL,HIGH,UNKNOWN"
scanners: vuln,secret,misconfig,license
limit-severities-for-sarif: true
scanners: "vuln,secret,misconfig,license"
github-pat: ${{ secrets.GITHUB_TOKEN }}

- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: "trivy-results.sarif"
56 changes: 49 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,34 @@ remain compatible. Database access uses Prisma 7 with the PostgreSQL driver
adapter. Domain events continue to be sent through the existing Bus API wrapper;
this service does not connect to Kafka directly.

The API runtime client is Prisma 7. The checked-in
`packages/challenge-prisma-client` artifact remains on Prisma 6 for its existing
downstream consumers, so the root generation command intentionally targets only
the API client. Upgrading that shared artifact requires a coordinated downstream
release.
The API runtime client and the checked-in external client use Prisma 7. The
external client has a stable package wrapper at
`packages/challenge-prisma-client`; generated Prisma files live below its
`generated` directory so regeneration cannot overwrite the public contract.

## External Prisma client

Services that need to aggregate challenge data directly can install the
`packages/challenge-prisma-client` Git subdirectory as
`@topcoder/challenge-api-v6`. The package exports all generated challenge
models, enums, Prisma helpers, and `PrismaClient`, plus this supported factory:

```ts
import { createChallengePrismaClient } from '@topcoder/challenge-api-v6';

const challenges = createChallengePrismaClient(process.env.CHALLENGE_DATABASE_URL, {
log: ['warn', 'error'],
});
```

`createChallengePrismaClient(connectionString, options?)` creates a Prisma 7
PostgreSQL driver adapter, preserves the optional `schema` query parameter in
the connection URL, and returns a disconnected client that connects lazily on
its first query. Call `$disconnect()` during application shutdown. The factory
throws `TypeError` when `connectionString` is empty or not a string; Prisma may
raise its normal configuration and database errors while creating or using the
client. Connection-defining `adapter` and `accelerateUrl` options are owned by
the factory and intentionally excluded from its options type.

## Configuration

Expand Down Expand Up @@ -90,7 +113,7 @@ configuration parameters.
Run `nvm use` before pnpm commands. Make sure `DATABASE_URL` is set before any
database operation or application startup.

1. Install dependencies and generate the Prisma client: `pnpm install`
1. Install dependencies and generate both Prisma clients: `pnpm install`
2. Build the API: `pnpm build`
3. Create or update local database tables: `pnpm create-tables`
4. Seed tables: `pnpm seed-tables`
Expand Down Expand Up @@ -130,7 +153,8 @@ database operation or application startup.
DATABASE_URL=
```

Then run `pnpm install`. The postinstall hook generates the Prisma 7 client.
Then run `pnpm install`. The postinstall hook generates both the internal and
external Prisma 7 clients.

2. 🚢 Start docker-compose with services which are required to start Topcoder Challenges API locally

Expand Down Expand Up @@ -239,6 +263,13 @@ To run unit tests alone
pnpm test
```

To smoke-test the packaged external Prisma factory without connecting to a
database:

```bash
pnpm test:external-client
```

To run unit tests with coverage report

```bash
Expand Down Expand Up @@ -310,6 +341,17 @@ Refer to the verification document `Verification.md`
bypass. Any update that starts in or transitions to a completed or cancelled status cannot change
the effective `is_test_challenge` value; omitting metadata preserves it. Normal authorization
checks still apply.
- Role-specific member competition searches use
`GET /v6/challenges?memberId={memberId}&resourceRoleId={resourceRoleUuid}`.
`resourceRoleId` is an exact UUID and requires `memberId`. The resource role is
applied before all challenge filters, global sorting, totals, and pagination;
omitting it preserves the existing any-resource behavior of `memberId`. This
public query only narrows results and never grants access: anonymous callers
retain anonymous visibility, and authenticated callers remain subject to
whitelist, group, and task rules based on the caller. For “My competitions,”
pass the configured Submitter resource-role UUID. Deploy migration
`20260813130000_add_role_to_member_access_view` before this service version,
because the generated Prisma client expects the view's new `roleId` column.
- API base configuration points to v6 in dev/local and v5 in prod (for compatibility):
- Dev: `work-manager/config/constants/development.js`.
- Local: `work-manager/config/constants/local.js`.
Expand Down
2 changes: 0 additions & 2 deletions app-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ const Topics = {
ChallengeAttachmentCreated: "challenge.action.attachment.created",
ChallengeAttachmentUpdated: "challenge.action.attachment.updated",
ChallengeAttachmentDeleted: "challenge.action.attachment.deleted",
ChallengeTimelineTemplateCreated: "challenge.action.challenge.timeline.created",
ChallengeTimelineTemplateUpdated: "challenge.action.challenge.timeline.updated",
ChallengeTimelineTemplateDeleted: "challenge.action.challenge.timeline.deleted",
ChallengePhaseUpdated: "challenge.action.phase.updated",
Expand Down Expand Up @@ -103,7 +102,6 @@ const DisabledTopics = [
Topics.ChallengeAttachmentCreated,
Topics.ChallengeAttachmentUpdated,
Topics.ChallengeAttachmentDeleted,
Topics.ChallengeTimelineTemplateCreated,
Topics.ChallengeTimelineTemplateUpdated,
Topics.ChallengeTimelineTemplateDeleted,
Topics.ChallengePhaseUpdated,
Expand Down
30 changes: 26 additions & 4 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ paths:
- Challenges
description: |
Retrieve challenges in the system.

`resourceRoleId` may be combined with `memberId` to restrict the member's
resource-associated challenges to one UUID resource role before challenge
filtering, sorting, totals, and pagination. It does not grant access: results
remain subject to the caller's whitelist, group, and task visibility. Anonymous
callers receive only challenges visible to anonymous users.
security:
- bearer: []
produces:
Expand Down Expand Up @@ -98,7 +104,9 @@ paths:
format: UUID
- name: trackIds
in: query
description: Filter by multiple track IDs, exact match.
description: >-
Filter by multiple exact track IDs using OR semantics. IDs are also ORed with any
track or tracks facets supplied in the same request.
required: false
type: array
items:
Expand All @@ -113,9 +121,13 @@ paths:
type: string
- name: tracks
in: query
description: Filter by multiple track abbreviation, exact match. If tracks is provided, trackIds will be ignored
description: >-
Filter by track facets using OR semantics. Persisted track abbreviations are exact
matches; AI is a synthetic facet that matches the exact canonical AI challenge tag.
Unknown abbreviations match no challenges. All values are ORed with trackIds.
required: false
type: array
collectionFormat: brackets
items:
type: string
- name: typeId
Expand All @@ -126,7 +138,9 @@ paths:
format: UUID
- name: trackId
in: query
description: Filter by track id, exact match. If track is provided, trackId will be ignored
description: >-
Filter by one exact track ID. The ID is ORed with any track or tracks facets supplied
in the same request.
required: false
type: string
format: UUID
Expand All @@ -137,7 +151,9 @@ paths:
type: string
- name: track
in: query
description: Filter by track, case-insensitive, partial matches are allowed.
description: >-
Filter by one exact track abbreviation. AI is a synthetic facet that matches the exact
canonical AI challenge tag. An unknown abbreviation matches no challenges.
required: false
type: string
- name: name
Expand Down Expand Up @@ -354,6 +370,12 @@ paths:
description: Filter challenges memberId has access to
required: false
type: string
- name: resourceRoleId
in: query
description: Filter the member resource association by exact resource-role UUID. Requires memberId. This only narrows results and does not grant challenge access.
required: false
type: string
format: UUID
- name: sortBy
in: query
description: Sort the results by the field.
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
"main": "dist/main.js",
"packageManager": "pnpm@11.15.1",
"scripts": {
"prebuild": "pnpm run prisma:generate",
"build": "nest build",
"start": "node dist/main.js",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main.js",
"postinstall": "pnpm run prisma:generate",
"prisma:generate": "prisma generate --generator client",
"prisma:generate": "prisma generate",
"lint": "eslint --no-error-on-unmatched-pattern \"*.ts\" \"src/**/*.ts\" \"prisma/**/*.ts\" \"test/**/*.ts\"",
"lint:fix": "pnpm lint --fix",
"format": "prettier --write \"*.ts\" \"src/**/*.ts\" \"prisma/**/*.ts\" \"test/**/*.ts\"",
Expand All @@ -21,6 +22,7 @@
"recalculate-winners": "node --require ts-node/register/transpile-only data-migration/src/scripts/recalculateChallengeWinners.js",
"recalculate-winners:csv": "node --require ts-node/register/transpile-only data-migration/src/scripts/recalculateChallengeWinners.js --csv-only",
"test": "NODE_ENV=test mocha --require ts-node/register/transpile-only \"test/unit/**/*.test.{js,ts}\" --exit",
"test:external-client": "node --test packages/challenge-prisma-client/factory.test.js",
"e2e": "NODE_ENV=test mocha --require ts-node/register/transpile-only \"test/e2e/*.test.{js,ts}\" --exit",
"test:cov": "nyc --reporter=html --reporter=text pnpm test",
"e2e:cov": "nyc --reporter=html --reporter=text pnpm e2e",
Expand Down
35 changes: 35 additions & 0 deletions packages/challenge-prisma-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `@topcoder/challenge-api-v6`

This package is the supported external Prisma client for the Challenge API v6
schema. It re-exports the generated Prisma surface and provides
`createChallengePrismaClient(connectionString, options?)`, which configures the
Prisma 7 PostgreSQL driver adapter and honors the connection URL's optional
`schema` query parameter.

```ts
import { createChallengePrismaClient } from '@topcoder/challenge-api-v6';

const client = createChallengePrismaClient(process.env.CHALLENGE_DATABASE_URL);
const activeCount = await client.challenge.count({
where: { status: 'ACTIVE' },
});
await client.$disconnect();
```

Callers that need bounded database work can pass PostgreSQL pool settings
without constructing the Prisma 7 adapter themselves:

```ts
const client = createChallengePrismaClient(databaseUrl, {
driverOptions: {
connectionTimeoutMillis: 5000,
query_timeout: 5000,
statement_timeout: 5000,
},
});
```

The client connects lazily. Applications own its lifecycle and must disconnect
it during shutdown. An empty or non-string connection URL raises `TypeError`;
Prisma reports its normal configuration and database errors during creation or
query execution.
Loading
Loading