diff --git a/.github/workflows/cd-backend-springboot.yml b/.github/workflows/cd-backend-springboot.yml index d0aa761..fe9b06c 100644 --- a/.github/workflows/cd-backend-springboot.yml +++ b/.github/workflows/cd-backend-springboot.yml @@ -17,8 +17,8 @@ jobs: docker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -33,7 +33,7 @@ jobs: -Dspring-boot.build-image.imageName=$IMAGE_NAME:${{ github.ref_name }} \ -q - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9d27fcb..90bcfc7 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -17,13 +17,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 - run: pnpm install --frozen-lockfile --prefer-offline diff --git a/.github/workflows/ci-backend-springboot.yml b/.github/workflows/ci-backend-springboot.yml index dde8a5e..31f9d3e 100644 --- a/.github/workflows/ci-backend-springboot.yml +++ b/.github/workflows/ci-backend-springboot.yml @@ -17,10 +17,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -33,10 +33,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -49,10 +49,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -65,10 +65,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -84,10 +84,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -101,10 +101,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm @@ -118,10 +118,10 @@ jobs: run: working-directory: backend-springboot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: java-version: 25 distribution: graalvm diff --git a/.github/workflows/ci-backend.yml b/.github/workflows/ci-backend.yml index 469c092..80273ea 100644 --- a/.github/workflows/ci-backend.yml +++ b/.github/workflows/ci-backend.yml @@ -14,11 +14,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -32,11 +32,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -50,11 +50,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -70,11 +70,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -89,11 +89,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -108,11 +108,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -127,11 +127,11 @@ jobs: run: working-directory: backend-nestjs steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: actions/checkout@v6 + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml index f2fee42..b93eab9 100644 --- a/.github/workflows/ci-e2e.yml +++ b/.github/workflows/ci-e2e.yml @@ -44,7 +44,7 @@ jobs: --health-timeout 2s --health-retries 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - uses: pnpm/action-setup@v4 @@ -104,7 +104,7 @@ jobs: --health-timeout 2s --health-retries 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - uses: pnpm/action-setup@v4 @@ -115,7 +115,7 @@ jobs: node-version: 24 cache: pnpm cache-dependency-path: frontend/pnpm-lock.yaml - - uses: actions/setup-java@v4 + - uses: actions/setup-java@v5 with: distribution: temurin java-version: 25 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6cc970..76beb13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,13 +17,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -37,13 +37,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -57,13 +57,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -79,13 +79,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -100,13 +100,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -121,13 +121,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -142,13 +142,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -163,13 +163,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm @@ -184,13 +184,13 @@ jobs: run: working-directory: frontend steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa + - uses: pnpm/action-setup@v4 with: version: 10 - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + - uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm diff --git a/README.md b/README.md index 1211482..79d5bcd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ Map-based platform for tracking reforestation and cleanup efforts across Algeria | **Auth (NestJS)** | BetterAuth + `@thallesp/nestjs-better-auth` | | **Auth (Spring Boot)** | Form login, BCrypt, session-based | | **Quality** | TypeScript strict, ESLint, Prettier, knip, depcruise, husky | -| **CI** | GitHub Actions — frontend + backend workflows with path filters | +| **CI** | GitHub Actions — frontend + backend (NestJS + Spring Boot) with path filters | +| **CD** | Cloudflare Pages (frontend on `main` push) + GHCR (Spring Boot native Docker image on annotated tag `backend-springboot-v*`) | +| **E2E** | Playwright tests against NestJS & Spring Boot backends (manual trigger) | | **Runtime** | Docker (PostgreSQL), pnpm, Bun | ## Quick Start @@ -131,15 +133,17 @@ Run from `backend-springboot/`: | Command | Description | |---------|-------------| -| `./mvnw spring-boot:run` | Dev server (port 8080, H2 console) | -| `./mvnw compile -q` | Compile | -| `./mvnw test -Ptest-it` | Integration tests (Testcontainers + PostgreSQL) | -| `./mvnw test -Ptest-arch` | Architecture tests (ArchUnit) | -| `./mvnw test -Ptest-unit` | Domain unit tests | -| `./mvnw spotless:check -Pquality` | Format check (Palantir) | -| `./mvnw spotless:apply -Pquality` | Auto-format | -| `./mvnw -DskipTests clean package` | Build JAR | -| `./mvnw -Pnative spring-boot:build-image` | Build native Docker image | +| `make dev` | Dev server (port 8080) | +| `make build` | Build JAR (skip tests) | +| `make check` | Test + lint | +| `make lint` | Format check (Palantir) | +| `make format` | Auto-format | +| `make test-unit` | Domain unit tests | +| `make test-arch` | Architecture tests (ArchUnit) | +| `make test-it` | Integration tests (Testcontainers + PostgreSQL) | +| `make native` | Native compile (GraalVM binary) | +| `make native-image` | Build native Docker image | +| `make depgraph` | Generate module dependency graph | API docs at `http://localhost:8080/swagger-ui/index.html` (Swagger UI). @@ -192,7 +196,9 @@ green-algeria-map/ │ ├── it/ # Integration tests (Testcontainers + Postgres) │ └── setup/ # Test module helper ├── backend-springboot/ # Spring Boot CQRS API (Testcontainers, Flyway, GraalVM) -├── .github/workflows/ # CI (frontend + backend with path filters) +│ └── src/main/java/... # domain/ + application/ + infrastructure/ + controller/ +├── benchmark/ # k6 scripts + results for performance benchmarks +├── .github/workflows/ # CI/CD (frontend, NestJS, Spring Boot) + E2E ├── .skills/ # Project-specific AI skills ├── .husky/ # Pre-commit hooks ├── CONTEXT.md # Domain architecture documentation @@ -200,12 +206,16 @@ green-algeria-map/ └── .tmux.conf # Tmux config ``` -## CI +## CI / CD | Workflow | Triggers | Jobs | |----------|----------|------| -| CI (frontend) | Changes in `frontend/` or `ci.yml` | check → lint → knip → depcruise → test → build | -| CI Backend | Changes in `backend-nestjs/` or `ci-backend.yml` | check → lint → knip → depcruise → test → build | +| CI Frontend | Changes in `frontend/` or `ci.yml` | check → lint → knip → depcruise → test-unit → test-ui → test-it → coverage → build | +| CI Backend NestJS | Changes in `backend-nestjs/` or `ci-backend.yml` | check → lint → knip → depcruise → test-unit → test-it → build | +| CI Backend Spring Boot | Changes in `backend-springboot/` or `ci-backend-springboot.yml` | check → lint → arch → depgraph → test-unit → test-it → build | +| CD Frontend | Push to `main` (frontend/ or cd.yml) + manual | Build → Deploy to Cloudflare Pages | +| CD Backend Spring Boot | Annotated tag `backend-springboot-v*` + manual | Test → Build native Docker image → Push to GHCR | +| E2E | manual (workflow_dispatch) | Playwright tests against NestJS & Spring Boot | ## Status @@ -233,7 +243,9 @@ green-algeria-map/ - Repository abstraction: domain interfaces decoupled from JPA - Zero-mock test suite: 48 integration tests via Testcontainers + singleton PostgreSQL - Architecture enforcement: 7 ArchUnit rules -- Native Docker image: GraalVM via Paketo Buildpacks, pushed to GHCR on tag +- Native Docker image: GraalVM via Paketo Buildpacks, pushed to GHCR on tag (`backend-springboot-v*`) +- Makefile for all dev/build/test commands (no direct `mvnw` usage) +- GitHub Actions CI: compile, lint, arch tests, depgraph, unit tests, integration tests, build ## License diff --git a/benchmark/PLAN.md b/benchmark/PLAN.md new file mode 100644 index 0000000..5a900b9 --- /dev/null +++ b/benchmark/PLAN.md @@ -0,0 +1,185 @@ +# Benchmark — Phase 0: Fair Scenario Isolation + +## Goal + +Bias-resistant benchmark for **NestJS** (Express + Fastify) and **Spring Boot** (JVM + Native) that isolates each layer to identify real bottlenecks. + +Current benchmarks test everything together (framework + DB + auth + serialization), making it impossible to tell where the bottleneck is. + +## Hard Rules + +- Exact same endpoint semantics, payloads, validation, auth/session logic, errors, status codes, headers, DB schema, indexes, query behavior, pool size, seed data, timeouts +- Run only one backend at a time +- Identical Docker CPU/RAM limits per backend (`--cpus=1 --memory=512m`) +- Same load tool (k6), same scenarios, same VUs, same ramp, same duration, same concurrency levels, same payloads +- **Production builds only.** No dev mode, hot reload, debug, tracing, request logs, SQL logs +- Warm all runtimes equally. Measure startup separately. +- Repeat each run **>=3 times**. Report median + variance. No cherry-picking. +- No stack-specific shortcut unless equivalent shortcut exists for all stacks +- Same DB access strategy across stacks: **all ORM or all raw query builder.** No mixing ORM in one and raw SQL in another. +- Verify query count and SQL. No N+1 allowed. +- Never run PostgreSQL on the same CPU allocation as the backend + +## Scenarios + +### 1. HTTP Baseline: `GET /ping` + +- Returns `{ "status": "ok" }` — static JSON, no DB, no auth, no validation +- What it measures: raw framework overhead (routing, middleware pipeline, JSON serialization) + +### 2. JSON Serialization: `POST /echo` + +- Accepts and returns a medium JSON payload (15-20 fields, nested objects, arrays) +- Validates input (class-validator / Jakarta Validation) +- No DB, no auth +- What it measures: serialization/deserialization + validation overhead +- Payload: + ```json + { + "name": "string", + "email": "string", + "age": 25, + "address": { "street": "string", "city": "string", "zip": "string" }, + "tags": ["a", "b", "c"], + "metadata": { "key1": "value1", "key2": 42 } + } + ``` + +### 3. Validation: `POST /validate` + +- Same DTO validation rules as /echo but returns only `{ "valid": true }` +- No DB, no auth +- Isolates validation cost from serialization cost + +### 4. Auth CPU: login/session flow + +- In-memory fake user store (no DB) +- Same hashing (bcrypt rounds), same JWT settings, same session handling +- Flow: sign-up → sign-in → get-session → sign-out +- What it measures: CPU-bound crypto + hashing overhead without DB interference + +### 5. CRUD In-Memory Repository + +- Same CRUD API (create, read, list, update, delete) using in-memory data store +- No PostgreSQL +- What it measures: framework request lifecycle + business logic overhead without DB + +### 6. CRUD with PostgreSQL (runs separately) + +- Run phases 1-5 first without DB, then run this phase with DB +- One backend at a time against PostgreSQL +- Same schema, same indexes, same pool size (explicitly fixed, e.g. 10 connections) +- Preload identical seed data (exact same rows) +- Reset DB before each measured run +- Capture: query count, query duration, pool wait time, slow queries (>100ms) + +### 7. Mixed 80/20 Read-Write + +- Run with both in-memory and PostgreSQL variants +- 80% reads / 20% writes +- Same operation mix across all stacks + +## PostgreSQL Isolation + +- Never run backends concurrently against PostgreSQL +- Reset DB before each measured run +- Same seed data (identical rows, same IDs) +- Explicit identical pool size (e.g. 10 connections) +- Capture PostgreSQL CPU/RAM, active/idle connections, locks, slow queries, query count, query latency, pool wait, transaction duration +- Prefer PostgreSQL on separate CPU allocation if possible + +## Stack-Specific Fairness + +### NestJS + +- Test both Express and Fastify adapters +- Disable `class-transformer` unless Spring uses equivalent transformation cost +- Avoid request-scoped providers +- Capture event-loop delay +- Detect sync crypto/logging/blocking awaits +- Ensure `NODE_ENV=production` and `--max-old-space-size` set correctly + +### Spring Boot + +- Test both JVM and Native (GraalVM) images +- JVM: production profile, record JVM/GC/heap/GC pauses, fair warmup +- Native: same code path/config, report native build config, startup measured separately +- Disable DevTools +- `spring.jpa.show-sql=false`, `logging.level.root=WARN` + +## Output + +Per scenario: +| Metric | NestJS (Express) | NestJS (Fastify) | Spring JVM | Spring Native | +|--------|------------------|------------------|------------|---------------| +| avg | | | | | +| med | | | | | +| p90 | | | | | +| p95 | | | | | +| p99 | | | | | +| max | | | | | +| failed | | | | | +| req/s | | | | | + +Resource usage: +| Metric | NestJS (Express) | NestJS (Fastify) | Spring JVM | Spring Native | +|--------|------------------|------------------|------------|---------------| +| CPU avg | | | | | +| CPU max | | | | | +| RAM avg | | | | | +| RAM max | | | | | +| GC/event-loop delay | | | | | +| DB query count | | | | | +| DB latency | | | | | +| DB pool wait | | | | | + +## Bias Audit Checklist + +- [ ] Same endpoint semantics across all stacks +- [ ] Same payloads, same validation rules +- [ ] Same auth hashing cost (bcrypt rounds) +- [ ] Same DB pool size +- [ ] Same seed data (verified row count) +- [ ] Production builds for all stacks +- [ ] All request/SQL/tracing logs disabled +- [ ] Warmup period applied equally +- [ ] >=3 runs per scenario, report median + variance +- [ ] No stack-specific optimizations without equivalent for all +- [ ] Same ORM strategy (ORM vs raw — not mixed) +- [ ] Query count verified (no N+1) +- [ ] PostgreSQL on separate CPU allocation +- [ ] Startup time measured separately +- [ ] Results reproducible via provided scripts + +## Attribution + +Explain whether the bottleneck is: +- Framework (routing, middleware pipeline) +- Runtime (GC, event loop, JIT warmup) +- DB (queries, pool, schema) +- Crypto (bcrypt rounds, JWT signing) +- Validation (class-validator / Jakarta Validation) +- Serialization (JSON encoding/decoding) +- Logging (sync I/O, log levels) + +No global winner unless consistent across all scenarios. + +## Deliverables + +- Docker Compose files per stack +- Dockerfiles (production builds) +- Benchmark k6 scripts (one per scenario) +- Seed/reset scripts +- Schema + index definitions +- Reproducibility steps in README +- Result tables (median + variance across 3 runs) +- Bias audit checklist (completed) +- Final analysis report + +## Appendix: Why This Matters + +The current results show Spring Boot JVM 2-3x faster than NestJS, but preview feedback suggests NestJS numbers are anomalously low (13 req/s for a single scenario). By isolating layers with strict bias controls, we can: +- Confirm whether the bottleneck is NestJS itself or specific configuration (ORM, validation, adapter) +- Test if Fastify adapter closes the gap with Express +- Measure GraalVM Native vs JVM warmup penalties +- Produce a defendable, reproducible benchmark worth publishing diff --git a/docs/adr/002-ci-cd-action-version-strategy.md b/docs/adr/002-ci-cd-action-version-strategy.md new file mode 100644 index 0000000..089630b --- /dev/null +++ b/docs/adr/002-ci-cd-action-version-strategy.md @@ -0,0 +1,46 @@ +# ADR 002: CI/CD Action Version Strategy + +**Status:** Accepted +**Date:** 2026-06-06 + +## Context + +GitHub Actions workflows use third-party actions whose versions must be +managed. Two approaches exist: pinning to a commit SHA (immutable, secure) +or pinning to a semver tag (convenient, auto-picks patch updates). The +project had a mix of both approaches, with some workflows using SHAs and +others using version tags. + +Additionally, GitHub Actions runners will stop supporting the Node.js 20 +runtime on **June 16, 2026**, requiring actions to target Node.js 24 or +later. + +## Decision + +1. **Use version tags, not commit SHAs, for all actions.** Semver tags + (`@v4`, `@v5`, `@v6`) provide the right balance of stability and + maintenance for this project's scope. Pinning to SHAs adds friction + with no meaningful security benefit for an open-source side project + without supply-chain attack risk. + +2. **Pin to the major version only** (e.g., `@v6`, `@v4`), accepting + automatic minor/patch updates within the major version. + +3. **Prefer actions targeting Node.js 24** where available. Actions still + on Node.js 20 (`actions/setup-node@v4`, `pnpm/action-setup@v4`, + `cloudflare/wrangler-action@v3`, `actions/upload-artifact@v4`) will be + re-evaluated when upstream releases Node.js 24-compatible versions. + +4. **Annotated tags** (`git tag -a`) are required for Spring Boot CD + because GitHub Actions tag-push triggers fire reliably on annotated + tags but may miss lightweight tags. + +## Consequences + +- Positive: Consistent format across all workflow files +- Positive: Easier to bump actions by editing a single tag +- Positive: CD trigger is reliable with annotated tags +- Negative: Non-deterministic builds if a major version ships a breaking + change as a minor/patch (unlikely per semver convention) +- Negative: Node.js 20 actions will emit deprecation warnings until + upstream releases updates (June 16 deadline) diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index 982a766..1653f7a 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -3,10 +3,12 @@ ## [frontend-v0.9.0] - 2026-06-04 ### Changed + - Frontend folder structure flattened - Docker Compose integration (unified profiles) ### Fixed + - Remaining Frontend audit issues (H4 + H5) - useAuth respects VITE_API_BACKEND env var - formatDate locale handling in tests @@ -15,19 +17,23 @@ ## [frontend-v0.8.0] - 2026-05-30 ### Added + - Tree species autocomplete in create zone form (`TreeSearchInput` wired to iNaturalist API) (#74) - GitHub Actions CD workflow for auto-deploy to Cloudflare Pages on push to main ### Fixed + - wrangler.jsonc `not_found_handling` value to kebab-case (`single-page-application`) for Workers + Assets ## [frontend-v0.7.1] - 2026-05-30 ### Fixed + - Cloudflare Workers deployment: `_redirects` SPA fallback infinite loop — replaced with `wrangler.jsonc` `single_page_application` mode - Auth env var consolidated to single `VITE_API_URL` (drop `VITE_AUTH_URL`) ### Changed + - E2E tests: routes auth via `storageState` project dependency (ObserveOne pattern) - CI E2E workflow: RustFS credentials match local dev (`greenalgeria-access`/`greenalgeria-secret-change-me`) - CI E2E workflow: bucket creation step before tests run @@ -35,6 +41,7 @@ ## [frontend-v0.7.0] - 2026-05-26 ### Added + - Public map API endpoint (unauthenticated GET /public/map) - Frontend usePublicMapData hook replacing demo data with real API - Crowdsourced zone creation via public POST /zones @@ -45,6 +52,7 @@ - R2 storage bucket configuration for photo uploads ### Fixed + - SSL for Render PostgreSQL connection (data-source, app.module, better-auth) - Session refetch after signin/signup (navbar was stale) - Toast theme mismatch (dark mode Sonner was light) @@ -52,12 +60,14 @@ - Storage service graceful startup without R2 config ### Changed + - Config files moved into config/ directories - VITE_API_URL baked to production URLs ## [frontend-v0.6.0] - 2026-05-25 ### Added + - Zone photo upload with RustFS (S3-compatible object storage) - ZonePhotoUploader component with drag-and-drop, preview, 5MB limit - Volunteer CTA per zone with sessionStorage dedup @@ -65,6 +75,7 @@ - E2E test type in test matrix ### Changed + - Mobile-responsive nav: hamburger menu for < md screens - Map responsive height (50vh mobile, 60vh desktop) - Legend repositioned higher on mobile (bottom-20, lg:bottom-6) @@ -73,22 +84,26 @@ - MapContainer switched from inline style to className ### Fixed + - Mobile map popup scrollable (max-h-[50vh] overflow-y-auto) ## [frontend-v0.5.0] - 2026-05-24 ### Added + - Tree species info lookup with iNaturalist autocomplete + species detail + GBIF Algeria observations - TreeSearchInput with debounced autocomplete component - TreeInfoModal with scientific name, common name, photos, GBIF count, Wikipedia link - Unified dev setup: docker-compose.dev.yml, scripts/dev/{setup,start}.sh ### Changed + - Backend health endpoint improved (liveness/readiness split, storage health check) ## [frontend-v0.4.0] - 2026-05-17 ### Added + - Sign-in and sign-up pages with form validation - Protected dashboard route with route guards - Auth service layer: interfaces decoupled from BetterAuth (port/adapter pattern) @@ -100,23 +115,27 @@ - TanStack Query setup with query hooks for zones ### Changed + - Extracted hooks and helpers from components (Wave 0 refactor) - Renamed helpers/ → utils/ - Frontend source reorganized by domain: features/, shared/, app/ ### Fixed + - Typecheck errors from route refactoring - Login tests rewritten for new auth flow ## [frontend-v0.3.0] - 2026-05-16 ### Added + - Axios instance with base URL pointing to backend - API module for zones (getAll, getById, create, update, remove) - Shared Zone type at `src/types/zone.ts` - TanStack Router loaders: home page fetches from API, falls back to demo data ### Changed + - Map component now accepts `zones` as a prop (data-agnostic) - Home route uses `useLoaderData` instead of hardcoded demo imports - Zone type moved from `components/map/types.ts` to `types/zone.ts`