Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
48c72f8
feat(api/src): :sparkles: Initial commit
Nudelsuppe42 Jun 11, 2025
643d2d1
feat(api/v2): :construction_worker: Finish basic structure, get ready…
Nudelsuppe42 Jul 12, 2025
9d99877
feat(api/auth): :passport_control: Add authentication and access toke…
Nudelsuppe42 Jul 12, 2025
ff9c899
chore(api/src): :memo: Add missing JSDocs to services and incerceptors
Nudelsuppe42 Jul 12, 2025
3bbb4b8
feat(api/v2): :sparkles: Add GET /applications
kyanvde Jul 13, 2025
f288d18
feat(api/v2): :art: Add Sortable Decorator
kyanvde Jul 13, 2025
1e2b96c
feat(api/v2): :art: Add Paginated Decorator
kyanvde Jul 13, 2025
7bc0149
feat(api/v2): :poop: Improve Paginated Decorator
kyanvde Jul 13, 2025
77a565e
fix(api/pagination): :bug: Limit page size of pagination to static bo…
Nudelsuppe42 Jul 13, 2025
ff51a4b
refactor(api/v2): :art: Move application logic to service
kyanvde Jul 13, 2025
23cccec
feat(api/sorting): :sparkles: Sorting decorator to be inline with pag…
Nudelsuppe42 Jul 13, 2025
94bdf90
merge(api/v2): Merge incoming changes
Nudelsuppe42 Jul 13, 2025
6a11a7d
fix(api/sorting): :bug: Do accept undefined as sorting and ordering v…
Nudelsuppe42 Jul 13, 2025
f86ab34
fix(api/auth): :bug: Rename token store to token and add type support…
Nudelsuppe42 Jul 13, 2025
e4ea98f
feat(api/v2): :wheelchair: Add dynamic examples to error responses
kyanvde Jul 13, 2025
fe3816d
fix(api/auth): Update request parameter type and return token in getP…
Nudelsuppe42 Jul 13, 2025
92a1d68
feat(api/filter): :sparkles: Add filtering decorators
Nudelsuppe42 Jul 13, 2025
8601aad
fix(api/applications): :truck: rename method of getapplications to be…
Nudelsuppe42 Jul 13, 2025
4f2c9a8
feat(api/v2): :sparkles: Add create application dto
kyanvde Jul 13, 2025
15e8032
refactor(api/v2): :art: Change application dto to better reflect crea…
kyanvde Jul 13, 2025
1bb9f1b
chore(api/src): :art: Prettier
Nudelsuppe42 Jul 13, 2025
9a469ca
ci(api/src): :bug: Fix lint
Nudelsuppe42 Jul 13, 2025
798877b
fix(api/v2): :bug: Remove weird artifact in api error response decorator
kyanvde Jul 13, 2025
ca064cc
feat(api/v2): :sparkles: Implement GET /applications properly
kyanvde Jul 13, 2025
6af820a
docs(api/v2): :memo: Update roadmap
kyanvde Jul 13, 2025
692c6fa
refactor(api/v2): :recycle: Move ApplicationStatus import to shared l…
Nudelsuppe42 Jul 13, 2025
7dab634
feat(api/v2): :sparkles: Add POST /applications
kyanvde Jul 14, 2025
bca8083
docs(api/v2): :memo: Update roadmap and add todo
kyanvde Jul 15, 2025
d449910
build(api/v2): :rocket: Update tsconfig to use correct resolution for…
Nudelsuppe42 Dec 10, 2025
89d973f
fix(api/v2): :zap: Refractor src to: remove eslint errors, clear up c…
Nudelsuppe42 Dec 10, 2025
1c5bc0a
feat(api/v2): :sparkles: Start implementation of cachet API connection
Nudelsuppe42 Dec 10, 2025
b24a644
feat(api/v2): :sparkles: Implement components and incidents from cachet
Nudelsuppe42 Dec 13, 2025
6064795
chore: Misc cleanup (#80)
SidneyNemzer Feb 18, 2026
ee1c071
chore: Use docker compose to run database in development (#81)
SidneyNemzer Mar 11, 2026
7d3f3a7
Implement `GET /claims` (#82)
SidneyNemzer Apr 27, 2026
2d354b0
test(api/v2): :white_check_mark: Add UtilityController tests (#117)
kyanvde May 20, 2026
13bbae2
feat(api/v2): Implement application routes (#134)
kyanvde Aug 26, 2026
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
6 changes: 4 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"**/*.js.map": true
},
"typescript.tsdk": "node_modules\\typescript\\lib",
"typescript.tsdk": "node_modules/typescript/lib",

"conventionalCommits.scopes": [
"mono",
Expand All @@ -31,6 +31,8 @@
"api/claims",
"frontend/legal",
"frontend/gallery",
"dash/editor"
"dash/editor",
"api/v2",
"api/auth"
]
}
89 changes: 89 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository

Turborepo monorepo (Yarn 4 workspaces) for BuildTheEarth's web presence: `apps/*` (frontend, dashboard, api, api-v2) and `packages/*` (db, prettier-config, typescript-config). TypeScript only — ignore/do not add JavaScript files.

## Commands

All commands run from the repo root. `yarn ws <workspace> <script>` is an alias for `yarn workspace`.

```bash
yarn install # install (Yarn 4, never npm/pnpm)
yarn dev # all apps (turbo) — starts docker postgres, waits, migrates, then dev
yarn dev:frontend # frontend only
yarn build # all (turbo caches; only changed packages rebuild)
yarn build:api # or build:frontend
yarn lint # turbo lint across workspaces
yarn prettier # format all workspaces
```

Database (`packages/db` owns the single Prisma schema):

```bash
yarn generate:db # prisma generate — required after schema changes or a fresh clone
yarn migrate:db # prisma migrate deploy
yarn studio:db # prisma studio
yarn ws @repo/db up # docker compose up -d (postgres:18 on :5432)
yarn ws @repo/db pull:db # introspect an existing database into the schema
yarn git:pull # git pull + install + generate:db
```

Each app has its own `env:copy` script (`yarn ws frontend env:copy`); `packages/db` and `apps/api-v2` also ship `.env.example`. The README's root-level `yarn env:copy` does not exist.

### Tests

Only `apps/api-v2` has tests (Jest + ts-jest). There is no turbo `test` task, so run them through the workspace:

```bash
yarn ws api-v2 test # all specs
yarn ws api-v2 test test/sections/claims # by path
yarn ws api-v2 test -t "should apply pagination" # single test by name
yarn ws api-v2 test:watch
yarn ws api-v2 test:cov
```

Jest's `testRegex` is `test/.*\.spec\.ts$` — specs live in `apps/api-v2/test/`, mirroring `src/`, never beside the source file. `^src/(.*)$` is mapped, so specs import from `src/...`.

On a fresh clone the suite fails to resolve `@repo/db` until that package has been compiled, because it resolves through `main: dist/index.js`. Run `yarn ws @repo/db build` (after `yarn generate:db`) once before running tests. `eslint` currently reports 6 pre-existing `@typescript-eslint/unbound-method` errors in `test/bootstrap/main.spec.ts`, `test/common/db/prisma.service.spec.ts` and the three `test/common/decorators/*` specs, so a non-zero `lint` exit is not necessarily caused by your change.

## Architecture

### Two API generations

`apps/api` (v1, in production) and `apps/api-v2` (v2, actively being built) are separate services with different stacks and auth models. New API work goes in `api-v2` unless the task explicitly concerns v1.

**`apps/api` (v1)** — Express 5, ESM (`"type": "module"`, so relative imports need `.js` extensions). A single `Core` class (`src/Core.ts`) constructs and owns every subsystem — Winston logger, Keycloak + KeycloakAdmin, Prisma, AWS S3, Discord integration, cron jobs — and hands itself to `Web` (`src/web/Web.ts`), which instantiates all controllers and registers routes via `src/web/routes/index.ts` using a `Router` helper. Auth is Keycloak (`keycloak-connect`) plus permission middleware in `src/web/routes/utils/`. Validation is express-validator/yup declared inline at route registration. Everything is served under `/api/v1`.

**`apps/api-v2`** — NestJS 11, URI versioning with default version `2` (routes are `/v2/...`), Swagger at `/v2/docs` (`docs.json` / `docs.yaml`). Source layout: `src/sections/<name>/` holds `<name>.module.ts`, `.controller.ts`, `.service.ts` and `dto/`; nested resources nest further (`sections/applications/questions/`). Cross-cutting code lives in `src/common/{db,decorators,dto,guards,interceptors}`.

Conventions that matter when adding an api-v2 endpoint:

- **Response envelope** is applied globally by `ResponseInterceptor`. Controllers return raw data (typed `ControllerResponse`) and get `{ status, message, data }`; returning `{ data, meta }` (typed `PaginatedControllerResponse`) yields the paginated envelope. Errors go through `ExceptionsFilter` as `{ status, timestamp, path, error, message }` — throw Nest HTTP exceptions rather than shaping errors by hand.
- **Query features come in decorator pairs**: a method decorator declaring options + Swagger params (`@Paginated`, `@Sortable`, `@Filtered`) and a param decorator reading them back off reflector metadata (`@Pagination`, `@Sorting`, `@Filter`). Both must be present, and `@Sortable`'s `allowedFields` is enforced — an unlisted `sortBy` throws 400.
- **Swagger schemas** use `@ApiDefaultResponse(Dto)`, `@ApiPaginatedResponseDto(Dto)`, `@ApiErrorResponse({ status, description })` from `common/decorators/api-response.decorator.ts` rather than raw `@ApiResponse`.
- **Auth is deny-by-default**: `AuthGuard` is registered as a global `APP_GUARD`. Opt out per route with `@SkipAuth()` (public) or `@OptionalAuth()` (token parsed if present, rejected if invalid). Authenticated requests carry `req.token` (a `BuildTeamProfileDto`, typed in `src/typings/express.d.ts`) — scope queries by `req.token.id`, which is the BuildTeam id.
- **Auth model is per-BuildTeam, not per-user**: a team exchanges its stored `token` (client secret) for a JWT via `POST /auth`, signed with `JWT_SECRET`. There is no Keycloak in v2.
- Modules must list `PrismaService` in their own `providers`; it is not a global module.
- `src/main.ts` exports `bootstrap()` and only self-invokes under `require.main === module`, so tests can import it.
- `apps/api-v2/roadmap.md` documents the intended URL/response/auth contract for v2 — consult it before designing a new endpoint.

### Frontends

`apps/frontend` (public site, port 3000) and `apps/dashboard` (team/admin dashboard, port 3001) are both Next.js 15 + Mantine 7 + Mapbox, authenticated with next-auth against Keycloak and gated by `src/middleware.ts`. They differ in data access: the frontend is a pages-router app that talks to the v1 API over HTTP via `src/utils/Fetcher.tsx` + SWR, while the dashboard is an app-router app that uses server actions in `src/actions/` querying Prisma directly through the singleton in `src/util/db.ts` (which adds a computed `upload.src` CDN URL). The frontend is localized with next-i18next/Crowdin.

### Shared packages

`packages/db` is the only place a Prisma schema exists; it re-exports the entire generated client (`export * from '@prisma/client'`) so apps import types and enums from `@repo/db`, never from `@prisma/client`. Turbo makes `build` depend on `generate:db`. `packages/typescript-config` exposes `base/expressjs/nestjs/nextjs` presets; `packages/prettier-config` is the root `prettier` config.

## Conventions

- **Commits**: conventional commits with a gitmoji, scope `<subrepo>/<scope>` — e.g. `feat(api/v2): :sparkles: Implement GET /applications/:id`. Use `mono` as the scope for repo-wide changes.
- **Formatting**: Prettier with tabs, single quotes, 120 columns, from `@repo/prettier-config`, everywhere. `apps/api-v2` does not declare `prettier` itself, so its `yarn ws api-v2 prettier` script fails with "command not found" — run `npx prettier <paths> --write` from the repo root instead. `apps/api-v2/test/` is not covered by that script's `./src` glob and is currently unformatted.
- `apps/api-v2` lint is `eslint --fix` with type-checked rules; the other apps lint with `--max-warnings 0`.

## CI/CD

Pushes to `main` trigger per-app GitHub Actions (`.github/workflows/{api,dashboard,frontend}.yml`) that use `turbo-ignore` to skip unchanged apps, then build the app's Dockerfile and push to ghcr.io. `apps/api-v2` has no Dockerfile or workflow yet — it is not deployed.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ To install all dependencies, run:
```bash
yarn install
# and optionally
yarn db:generate
yarn generate:db
```
Now, copy the example `.env` file and change all its options:

Expand Down
1 change: 1 addition & 0 deletions apps/api-v2/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
JWT_SECRET=topsecret
56 changes: 56 additions & 0 deletions apps/api-v2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
.turbo
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# temp directory
.temp
.tmp

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
44 changes: 44 additions & 0 deletions apps/api-v2/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @ts-check
import eslint from '@eslint/js';
import globals from 'globals';
import { dirname } from 'path';
import tseslint from 'typescript-eslint';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-unsafe-argument': 'warn',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/require-await': 'warn',
'@typescript-eslint/no-unused-vars': 'warn',
},
},
);
36 changes: 36 additions & 0 deletions apps/api-v2/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Config } from 'jest';

const config: Config = {
moduleFileExtensions: ['js', 'json', 'ts'],

// IMPORTANT: make project root stable
rootDir: '.',

testRegex: 'test/.*\\.spec\\.ts$',

transform: {
'^.+\\.(t|j)s$': [
'ts-jest',
{
tsconfig: '<rootDir>/tsconfig.spec.json',
},
],
},

moduleNameMapper: {
'^src/(.*)$': '<rootDir>/src/$1',
},

collectCoverageFrom: [
'src/**/*.{ts,js}',
'!src/**/*.spec.{ts,js}',
'!src/**/*.test.{ts,js}',
'!src/main.ts',
],

coverageDirectory: '<rootDir>/coverage',

testEnvironment: 'node',
};

export default config;
8 changes: 8 additions & 0 deletions apps/api-v2/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
66 changes: 66 additions & 0 deletions apps/api-v2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "api-v2",
"version": "2.0.0",
"private": true,
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
"scripts": {
"build": "nest build",
"prettier": "prettier ./src --write --ignore-unknown",
"start": "nest start",
"dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/axios": "^4.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/jwt": "^11.0.0",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.0",
"@repo/db": "*",
"axios": "^1.13.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"helmet": "^8.1.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.1.21",
"@repo/prettier-config": "*",
"@repo/typescript-config": "*",
"@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.6.3",
"typescript-eslint": "^8.20.0"
},
"prettier": "@repo/prettier-config",
"lint-staged": {
"*": "prettier --write --ignore-unknown"
}
}
Loading