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
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ workflows:
only:
- develop
- PM-4931
- PM-4704
- skill-statistics
tags:
only: /^dev-.*/

Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.13.1
26.5.1
34 changes: 26 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# ---- Base Stage ----
FROM node:22-alpine AS base
ARG NODE_VERSION=26.5.1
ARG PNPM_VERSION=11.15.1

# ---- Build Base Stage ----
FROM node:${NODE_VERSION}-alpine AS base
WORKDIR /usr/src/app

# ---- Package Manager Stage ----
FROM base AS package-manager
# Pin pnpm so clean builds do not change behaviour when a new major is released.
RUN npm install --global pnpm@11.15.0
ARG PNPM_VERSION
RUN npm install --global pnpm@${PNPM_VERSION}

# ---- Dependencies Stage ----
FROM package-manager AS deps
Expand All @@ -22,17 +26,31 @@
RUN pnpm build

# ---- Production Stage ----
FROM base AS production
FROM alpine:3.24 AS production
ARG NODE_VERSION
ENV NODE_ENV=production
WORKDIR /usr/src/app

# Use Alpine's dynamically linked Node runtime so patched system OpenSSL packages
# can be upgraded independently. The final image intentionally contains no npm or
# pnpm executable and runs as a dedicated unprivileged account.
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates nodejs-current=${NODE_VERSION}-r0 \
&& addgroup -S -g 10001 app \
&& adduser -S -D -u 10001 -G app -h /home/app app

# Copy built application from the build stage
COPY --from=build /usr/src/app/dist ./dist
COPY --from=build /usr/src/app/sql ./sql
COPY --from=build /usr/src/app/data ./data
COPY --from=build --chown=app:app /usr/src/app/dist ./dist
COPY --from=build --chown=app:app /usr/src/app/sql ./sql
COPY --from=build --chown=app:app /usr/src/app/data ./data
# Copy production dependencies from the deps stage
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY --from=deps --chown=app:app /usr/src/app/node_modules ./node_modules

# Expose the application port
EXPOSE 3000

# Do not grant report execution or database access through the container user.
USER app

# The command to run the application
CMD ["node", "dist/main.js"]
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ Each report will be a separate SQL query, potentially with a few parameters (lik
- **Language**: TypeScript
- **Database**: PostgreSQL
- **ORM**: Prisma
- **Package Manager**: pnpm
- **Package Manager**: pnpm 11.15.1

## Prerequisites

- Node.js (v22 or later recommended)
- pnpm
- Node.js 26.5.1 (run `nvm use` to select the repository version)
- pnpm 11.15.1

## Getting Started

Expand Down Expand Up @@ -110,6 +110,18 @@ ENGAGEMENTS_DB_URL="postgresql://user:password@localhost:5432/engagements"
# DATABASE_URL connection, including members.member, members.memberAddress,
# members.memberPhone, identity.country, lookups.Country, and projects.projects.

# ---------------------------------------------------
# General statistics exclusions
# ---------------------------------------------------

# Challenge type names excluded from the general statistics win counts.
# JSON array or comma-separated. Defaults to '["Task","First2Finish"]'.
REPORTS_EXCLUDED_CHALLENGE_TYPES='["Task","First2Finish"]'

# Member user IDs excluded from the general statistics (country member details
# and top winners by country). JSON array or comma-separated; defaults to empty.
REPORTS_EXCLUDED_USER_IDS='["8547899","251280"]'

# Old tc-payments database URL (used by member-tax CSV export script)
OLD_PAYMENTS_DATABASE_URL="postgresql://user:password@localhost:5432/tc_payments?schema=public"

Expand Down Expand Up @@ -178,3 +190,11 @@ The following read-only endpoints are available without authentication to suppor
- `GET /v6/reports/statistics/mm/competitions-count` — Marathon Match number of competitions (static JSON)

Static datasets are stored under `data/statistics/srm` and `data/statistics/mm` and are packaged into the ECS image in the Dockerfile.

## Container Runtime

The production image uses Alpine 3.24's dynamically linked Node.js 26.5.1
package and upgrades Alpine packages during the build so system security fixes,
including OpenSSL updates, are applied. The runtime runs as the unprivileged
`app` account (UID 10001) and intentionally excludes npm and pnpm; package
installation and application compilation happen only in builder stages.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "reports-api-v6",
"version": "0.0.1",
"private": true,
"packageManager": "pnpm@11.15.0",
"packageManager": "pnpm@11.15.1",
"scripts": {
"build": "nest build",
"deploy:dev": "BRANCH=$(git rev-parse --abbrev-ref HEAD) && TAG=\"dev-${BRANCH}\" && git tag -d \"$TAG\" 2>/dev/null; git push origin \":refs/tags/$TAG\" 2>/dev/null; git tag \"$TAG\" && git push origin \"$TAG\"",
Expand All @@ -23,8 +23,8 @@
"@nestjs/cli": "^11.0.12",
"@nestjs/common": "^11.1.9",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.9",
"@nestjs/platform-express": "^11.1.9",
"@nestjs/core": "^11.1.18",
"@nestjs/platform-express": "^11.1.18",
"@nestjs/swagger": "^11.2.3",
"@prisma/client": "^7.0.1",
"@types/express": "^5.0.5",
Expand All @@ -39,7 +39,7 @@
"tc-core-library-js": "github:topcoder-platform/tc-core-library-js#master",
"@nestjs/schematics": "^11.0.9",
"@types/jest": "^29.5.8",
"@nestjs/testing": "^11.1.9"
"@nestjs/testing": "^11.1.18"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
Expand Down
Loading
Loading