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
44 changes: 44 additions & 0 deletions .github/workflows/trivy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +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: 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@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"
limit-severities-for-sarif: true
scanners: "vuln,secret,misconfig,license"
github-pat: ${{ secrets.GITHUB_TOKEN }}

- name: Upload Trivy scan results to GitHub Security tab
if: always()
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: "trivy-results.sarif"
14 changes: 11 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,29 @@ RUN DATABASE_URL="postgresql://user:password@localhost:5432/forums" pnpm prisma:
&& pnpm lint \
&& pnpm build \
&& rm -rf node_modules \
&& pnpm install --prod --frozen-lockfile --ignore-scripts
&& pnpm install --prod --frozen-lockfile --ignore-scripts \
&& pnpm rebuild @prisma/engines

FROM node:${NODE_VERSION}-alpine AS production

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

ENV NODE_ENV=production
# External Prisma clients are copied beside the application under /usr/src.
# Expose the application's production dependencies to their CommonJS imports.
ENV NODE_ENV=production \
NODE_PATH=/usr/src/app/node_modules
WORKDIR /usr/src/app

COPY --from=build --chown=node:node /usr/src/app/dist ./dist
COPY --from=build --chown=node:node /usr/src/app/node_modules ./node_modules
COPY --from=build --chown=node:node /usr/src/app/prisma/generated ./prisma/generated
COPY --from=build --chown=node:node /usr/src/app/prisma/migrations ./prisma/migrations
COPY --from=build --chown=node:node /usr/src/app/prisma/schema.prisma ./prisma/schema.prisma
COPY --from=build --chown=node:node /usr/src/app/prisma.config.ts ./prisma.config.ts
COPY --from=build --chown=node:node /usr/src/app/package.json ./package.json
COPY --from=build --chown=node:node --chmod=0555 /usr/src/app/appStartUp.sh ./appStartUp.sh
COPY --from=external-clients --chown=node:node /clients/challenge-api-v6/packages/challenge-prisma-client /usr/src/challenge-api-v6/packages/challenge-prisma-client
COPY --from=external-clients --chown=node:node /clients/identity-api-v6/packages/identity-prisma-client /usr/src/identity-api-v6/packages/identity-prisma-client
COPY --from=external-clients --chown=node:node /clients/member-api-v6/packages/member-prisma-client /usr/src/member-api-v6/packages/member-prisma-client
Expand All @@ -58,4 +66,4 @@ COPY --from=external-clients --chown=node:node /clients/resource-api-v6/packages
USER node
EXPOSE 3000

CMD ["node", "dist/main.js"]
CMD ["./appStartUp.sh"]
62 changes: 54 additions & 8 deletions README.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions appStartUp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
set -eu

printf '%s\n' 'Applying forums database migrations.'
./node_modules/.bin/prisma migrate deploy

printf '%s\n' 'Starting Forums API v6.'
exec node dist/main.js
67 changes: 67 additions & 0 deletions docs/email-templates/forum-notification.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en" style="font-family: 'Roboto', Verdana, sans-serif;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>New forum post: {{topicTitle}}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Barlow+Condensed:500" type="text/css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto" type="text/css">
</head>
<body bgcolor="#E9E9E9" style="margin: 0; padding: 0; font-family: 'Roboto', Verdana, sans-serif; font-size: 16px; color: #2A2A2A; background-color: #E9E9E9;">
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0; mso-hide: all;">
{{authorHandle}} posted in {{topicTitle}} on Topcoder.
</div>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" bgcolor="#E9E9E9" style="width: 100%; background-color: #E9E9E9;">
<tr>
<td align="center" style="padding: 52px 16px 32px;">
<!--[if mso]>
<table role="presentation" width="600" align="center" cellspacing="0" cellpadding="0" border="0"><tr><td>
<![endif]-->
<table role="presentation" align="center" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; max-width: 600px;">
<tr>
<td align="center" style="padding: 0 0 24px;">
<img src="https://cdn.mcauto-images-production.sendgrid.net/a5c0eaad02bb03cd/96566d5e-c763-4bb7-8ed1-e1f0d372eab5/383x58.png" alt="Topcoder" width="198" height="30" style="display: block; width: 198px; height: 30px; border: 0;">
</td>
</tr>
<tr>
<td bgcolor="#FFFFFF" style="padding: 28px; background-color: #FFFFFF; border-radius: 8px; font-family: 'Roboto', Verdana, sans-serif; font-size: 16px; line-height: 24px;">
<h1 style="margin: 0 0 24px; font-family: 'Barlow Condensed', 'Roboto', Verdana, sans-serif; font-size: 34px; line-height: 40px; font-weight: 500; text-align: center; color: #0D61BF;">New forum post</h1>
<p style="margin: 0 0 24px;"><strong>{{authorHandle}}</strong> posted in <strong>{{topicTitle}}</strong> on Topcoder.</p>

<h2 style="margin: 0 0 12px; font-size: 18px; line-height: 26px; font-weight: 700;">Post details</h2>
<p style="margin: 0 0 12px; overflow-wrap: anywhere; word-break: break-word;"><strong>Topic:</strong> {{topicTitle}}</p>
<p style="margin: 0 0 12px; overflow-wrap: anywhere; word-break: break-word;"><strong>Topic ID:</strong> {{topicId}}</p>
{{#if challengeTitle}}
<p style="margin: 0 0 12px; overflow-wrap: anywhere; word-break: break-word;"><strong>Challenge:</strong> {{challengeTitle}}</p>
{{/if}}
{{#if challengeId}}
<p style="margin: 0 0 12px; overflow-wrap: anywhere; word-break: break-word;"><strong>Challenge ID:</strong> {{challengeId}}</p>
{{/if}}
<p style="margin: 0 0 12px;"><strong>Author:</strong> {{authorHandle}}</p>
<p style="margin: 0 0 24px;"><strong>Posted at (UTC):</strong> {{createdAt}}</p>

<h2 style="margin: 0 0 12px; font-size: 18px; line-height: 26px; font-weight: 700;">Post content</h2>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="width: 100%; table-layout: fixed;">
<tr>
<td bgcolor="#F5F5F5" style="padding: 16px; background-color: #F5F5F5; border-left: 3px solid #0D61BF; font-family: 'Roboto', Verdana, sans-serif; font-size: 16px; line-height: 24px; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;">{{postContent}}</td>
</tr>
</table>

<p style="margin: 24px 0;">You received this notification because you watch this topic or one of its parent topics.</p>
<p style="margin: 0;">Thanks,<br>Topcoder</p>
</td>
</tr>
<tr>
<td align="center" style="padding: 24px 0 0; font-family: 'Roboto', Verdana, sans-serif; font-size: 12px; line-height: 18px; color: #555555;">
Copyright &copy; Topcoder, All rights reserved.
</td>
</tr>
</table>
<!--[if mso]>
</td></tr></table>
<![endif]-->
</td>
</tr>
</table>
</body>
</html>
9 changes: 9 additions & 0 deletions docs/email-templates/forum-notification.sample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"challengeId": "12345678-1234-4234-8234-123456789abc",
"challengeTitle": "Topcoder Forum Export Challenge",
"topicId": "87654321-4321-4321-8321-cba987654321",
"topicTitle": "Challenge questions and clarifications",
"postContent": "Hi everyone,\n\nThe requirements have been updated with an example for the export flow. Please review the discussion and share any questions.\n\nThanks!",
"authorHandle": "sampleCopilot",
"createdAt": "2026-09-08T04:30:00.000Z"
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"express": "^5.1.0",
"mysql2": "^3.14.3",
"pg": "^8.16.3",
"prisma": "^7.9.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2",
"tc-bus-api-wrapper": "github:topcoder-platform/tc-bus-api-wrapper",
Expand All @@ -51,7 +52,6 @@
"globals": "^16.3.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"prisma": "^7.9.0",
"supertest": "^7.0.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
Expand Down
27 changes: 25 additions & 2 deletions packages/forums-prisma-client/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Forums Prisma Client

Generated output path for the exported forums Prisma client generated from `prisma/schema.prisma`.
Reusable Prisma client generated from `prisma/schema.prisma` for services that
need direct, typed access to forum data.

The Batch 1 scaffold intentionally does not define forum domain models. The generated client is ready for imports and will expose forum models after later batches add the final schema.
The package exports the Forums `Topic`, `Post`, `PostReaction`,
`PostReactionType`, `TopicClosure`, `TopicWatch`, `TopicReadState`, `MemberBan`,
and `IpBan` models. Consumers import this client from the Forums API package and
must supply the Prisma 7 PostgreSQL driver adapter required by their runtime:

```ts
import { PrismaClient } from '@topcoder/forums-api-v6/packages/forums-prisma-client';
import { PrismaPg } from '@prisma/adapter-pg';

const forumsDbUrl = process.env.FORUMS_DB_URL;
if (!forumsDbUrl) {
throw new Error('FORUMS_DB_URL is required');
}
const adapter = new PrismaPg(
{ connectionString: forumsDbUrl },
{ schema: 'forums' },
);
const forums = new PrismaClient({ adapter });
```

The caller owns the client lifecycle and must call `$disconnect()` during
shutdown. Regenerate this package with `pnpm prisma:generate` whenever the
Forums Prisma schema changes.
Loading
Loading