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"
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@ and post-commit watch notifications.
- `PUT /v6/forums/topics/:topicId/read-state`
- `PATCH /v6/forums/posts/:postId`
- `DELETE /v6/forums/posts/:postId`
- `PUT /v6/forums/posts/:postId/reaction`
- `DELETE /v6/forums/posts/:postId/reaction`
- `PUT /v6/forums/moderation/topics/:topicId/lock`
- `DELETE /v6/forums/moderation/topics/:topicId/lock`
- `PUT /v6/forums/moderation/member-bans/:memberId`
- `DELETE /v6/forums/moderation/member-bans/:memberId`
- `PUT /v6/forums/moderation/ip-bans/:ipAddress`
- `DELETE /v6/forums/moderation/ip-bans/:ipAddress`

This service includes the forums schema, Prisma client export, topic read workflows, transactional command-side workflows, centralized forums authorization and runtime moderation for topics, posts, watches, and explicit read-state updates, plus best-effort watch notification publishing for new posts and successful allowed child-topic starter posts. The policy resolves inherited topic restrictions, challenge access, resource-role/copilot elevation, role matching, ownership, scoped M2M write access, and M2M on-behalf target-member visibility before writes, reads, or notification delivery. Runtime moderation enforces active member bans, trusted exact-IP bans for human request traffic, and locked-topic mutation rules.
This service includes the forums schema, Prisma client export, topic read workflows, transactional command-side workflows, per-member post reactions, centralized forums authorization and runtime moderation for topics, posts, watches, and explicit read-state updates, plus best-effort watch notification publishing for new posts and successful allowed child-topic starter posts. The policy resolves inherited topic restrictions, challenge access, resource-role/copilot elevation, role matching, ownership, scoped M2M write access, and M2M on-behalf target-member visibility before writes, reads, or notification delivery. Runtime moderation enforces active member bans, trusted exact-IP bans for human request traffic, and locked-topic mutation rules.

Topic reads are exposed under `read:forums-topics`. `GET /v6/forums/topics` returns visible non-challenge root topics; `GET /v6/forums/topics/challenges/:challengeId` checks base challenge visibility before returning visible challenge roots; `GET /v6/forums/topics/:topicId/children` requires parent visibility before filtering direct children; and `GET /v6/forums/topics/:topicId` returns topic detail with an embedded post tree. Active member bans and trusted exact-IP bans return 403 before read policy or query work. Topic summaries include `locked`, `lockedBy`, and `lockedAt`; `lockedBy` and `lockedAt` may be null for imported legacy locked topics. Locked topics remain readable for callers that otherwise pass moderation and visibility checks. Detail embeds posts under `read:forums-topics`, keeps deleted post placeholders with null content, counts only non-deleted posts, and derives unread state from `TopicReadState.lastReadAt`. `read:forums-posts` remains reserved for future post-specific read APIs in v1.
Topic reads are exposed under `read:forums-topics`. `GET /v6/forums/topics` returns visible non-challenge root topics; `GET /v6/forums/topics/challenges/:challengeId` checks base challenge visibility before returning visible challenge roots; `GET /v6/forums/topics/:topicId/children` requires parent visibility before filtering direct children; and `GET /v6/forums/topics/:topicId` returns topic detail with an embedded post tree. Active member bans and trusted exact-IP bans return 403 before read policy or query work. Topic summaries include `locked`, `lockedBy`, and `lockedAt`; `lockedBy` and `lockedAt` may be null for imported legacy locked topics. Locked topics remain readable for callers that otherwise pass moderation and visibility checks. Detail embeds posts under `read:forums-topics`, keeps deleted post placeholders with null content, counts only non-deleted posts, derives unread state from `TopicReadState.lastReadAt`, and includes each post's shared `thumbsUpCount`/`thumbsDownCount` plus the authenticated member's nullable `viewerReaction`. `read:forums-posts` remains reserved for future post-specific read APIs in v1.

Human members set or switch their one-per-post reaction with `PUT /v6/forums/posts/:postId/reaction` and `{ "reaction": "THUMBS_UP" }` or `{ "reaction": "THUMBS_DOWN" }`. `DELETE` on the same route idempotently removes the member's reaction. Both commands return the resulting `viewerReaction` and current shared counts, enforce runtime bans and inherited post visibility, and reject deleted posts. They remain available on locked topics because reactions do not change discussion content. The reaction routes are human-member-only; M2M callers cannot own reaction state.

Top-level non-challenge topics may be created by human admins and scoped M2M callers. Top-level challenge topics may be created by eligible challenge members, challenge copilots, and admins; M2M callers cannot create challenge roots. Regular authenticated members may create child topics only under parents they can see and only when the resolved effective child context remains non-challenge; requests that inherit or introduce a non-null `challengeId` under a parent are rejected before writes. Allowed child topics must keep monotonic role restrictions: inherited roles cannot be cleared or replaced. Challenge-scoped visibility is verified through the configured challenge and resource adapters, including challenge-resource membership and challenge-copilot elevation. Challenge copilots may access or moderate challenge-scoped forums only when any effective `roleName` forum restriction is also satisfied; admin and scoped M2M bypass behavior is unchanged. Active member bans and trusted exact-IP bans return 403 for human writes before content, watch, or read-state changes. M2M on-behalf watch and read-state commands enforce active bans on the resolved target member and do not evaluate IP bans. Locked topics reject child-topic creation under the locked parent, replies, topic updates/deletes, and post updates/deletes unless the actor is an administrator or a human challenge copilot acting on a challenge-scoped topic.

Expand Down Expand Up @@ -158,7 +162,8 @@ Active `MemberBan` rows return 403 for reads, writes, human watch/read-state
commands, and M2M on-behalf watch/read-state commands targeting that member.
Active `IpBan` rows return 403 only for non-M2M requests with a trusted resolved
client IP; exact matching is performed by PostgreSQL `inet` equality. Locked
topics remain listable and readable but reject discussion mutations. Lock bypass
topics remain listable and readable but reject discussion content mutations.
Post reactions remain available to otherwise-authorized human members. Lock bypass
is limited to administrators everywhere and human challenge copilots on
challenge-scoped topics. Scoped M2M callers do not bypass locks unless they also
qualify as administrators.
Expand Down Expand Up @@ -223,6 +228,6 @@ interrupted, wipe the target forums dataset and rerun the full import.

## Prisma

The forums Prisma schema defines `Topic`, `Post`, `TopicClosure`, `TopicWatch`, `TopicReadState`, `MemberBan`, and `IpBan` in the dedicated `forums` schema. Topics store an explicit lock state plus nullable lock timestamp and lock actor member id. Ban rows keep active and removed audit metadata; the migration enforces one active row per member or exact IP value and validates IP bans as single IPv4/IPv6 host values. `pnpm prisma:generate` emits the local client at `prisma/generated/client` and the reusable exported client at `packages/forums-prisma-client`.
The forums Prisma schema defines `Topic`, `Post`, `PostReaction`, `TopicClosure`, `TopicWatch`, `TopicReadState`, `MemberBan`, and `IpBan` in the dedicated `forums` schema. `PostReaction` uses a composite post/member key so each member has at most one thumbs-up or thumbs-down value per post; deleting a post cascades its reactions. Topics store an explicit lock state plus nullable lock timestamp and lock actor member id. Ban rows keep active and removed audit metadata; the migration enforces one active row per member or exact IP value and validates IP bans as single IPv4/IPv6 host values. `pnpm prisma:generate` emits the local client at `prisma/generated/client` and the reusable exported client at `packages/forums-prisma-client`.

Topic creation is transactional: it creates the topic, starter post, closure rows, and, for human authors, author watch and read-state rows together. M2M topic creation uses the system author and skips member watch/read-state side effects. Topic deletion is soft deletion, and post deletion preserves the post row while setting content to null for placeholder reads. Topic summary reads use side-effect-free raw queries for topic lock state, visible-post counts, nullable latest visible activity, and unread derivation, then apply centralized forums policy filtering before pagination.
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