From b5e967d8d3f00801b722597e111a9cc85aa03759 Mon Sep 17 00:00:00 2001 From: Madhura68 Date: Sat, 25 Apr 2026 15:11:51 +0200 Subject: [PATCH] Add analytics and documentation updates --- .env.example | 10 ++ .gitignore | 3 +- AGENTS.md | 33 ++++++ CLAUDE.md | 10 +- README.md | 173 +++++++++++++++++++++++++++---- app/layout.tsx | 2 + docs/agent-instruction-audit.md | 88 ++++++++++++++++ docs/patterns/route-handler.md | 11 +- docs/patterns/server-action.md | 15 ++- docs/patterns/sort-order.md | 10 ++ docs/scrum4me-architecture.md | 33 +++++- docs/scrum4me-backlog.md | 16 +-- docs/scrum4me-functional-spec.md | 3 +- package-lock.json | 43 ++++++++ package.json | 1 + 15 files changed, 414 insertions(+), 37 deletions(-) create mode 100644 .env.example create mode 100644 docs/agent-instruction-audit.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1cac979 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Database +DATABASE_URL="postgresql://user:password@host/dbname?sslmode=require" +DIRECT_URL="postgresql://user:password@host/dbname?sslmode=require" + +# Auth/session +# Generate with: openssl rand -base64 32 +SESSION_SECRET="replace-with-at-least-32-characters" + +# Optional; Vercel and Node set this automatically in deployed environments. +NODE_ENV="development" diff --git a/.gitignore b/.gitignore index 0bb6a2e..8f9cbb6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +!.env.example # vercel .vercel @@ -47,4 +48,4 @@ next-env.d.ts *.db-wal # Claude Code local settings -.claude/settings.local.json \ No newline at end of file +.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md index 8bd0e39..d097267 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,3 +3,36 @@ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. + +# Scrum4Me Codex Rules + +Read `CLAUDE.md` and the relevant files in `docs/` before changing behavior. The same product and security rules apply to Codex work. + +## Access Control + +- Product-scoped access is owner-or-member: use `productAccessFilter(userId)` from `lib/product-access.ts`. +- Use owner-only `user_id` checks only for actions that truly require ownership, such as product archiving and team management. +- Never trust client-provided IDs by themselves. For reorder, promotion, completion, or bulk updates, fetch the records with both `id in (...)` and the parent scope (`product_id`, `pbi_id`, `sprint_id`, or `story_id`) before writing. +- Reject duplicate IDs in ordered lists or decision payloads. +- Derive denormalized fields from database parents, for example `pbi.product_id`, not from form data or JSON bodies. +- Demo users and demo API tokens must receive 403 on write operations. + +## Documentation Sync + +When changing behavior, API responses, dependencies, environment variables, deployment behavior, or analytics, update the matching docs in the same change: + +- `README.md` for setup, dependencies, deployment, and API overview. +- `docs/scrum4me-functional-spec.md` for user-facing/API requirements. +- `docs/scrum4me-architecture.md` for stack, access model, data model, env vars, and deployment. +- `docs/patterns/` when a reusable implementation rule changes. +- `CLAUDE.md` and this file when an agent instruction would have prevented the issue. + +## Verification + +Before handing work back, run: + +```bash +npm run lint +npm test +npm run build +``` diff --git a/CLAUDE.md b/CLAUDE.md index 7a2b61d..dfb8924 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,7 @@ Lees het relevante document voordat je aan een feature begint. Nooit gokken over | `docs/scrum4me-personas.md` | Lars (primair), Dina, Remi — gebruik bij UI-beslissingen | | `docs/scrum4me-product-backlog.md` | Testdata voor de seed | | `docs/scrum4me-styling.md` | **Lees dit voor elk component** — MD3-kleuren, shadcn patronen | +| `docs/agent-instruction-audit.md` | Waarom de agent-instructies zijn aangescherpt; checklist voor toekomstige wijzigingen | --- @@ -58,6 +59,8 @@ dnd-kit (drag-and-drop) Prisma v7 + PostgreSQL (Neon) iron-session (auth cookies) bcryptjs + Zod + Sonner +Sharp (avatarverwerking) +Vercel Analytics (`@vercel/analytics/next`) ``` > ⚠️ **Stylingregel:** Gebruik **nooit** `bg-blue-500` of willekeurige Tailwind-kleuren. @@ -100,9 +103,13 @@ SESSION_SECRET="" # openssl rand -base64 32 - **Branches:** `feat/ST-001-scaffolding` - **Server Actions:** altijd in `actions/[domein].ts`, nooit inline in page.tsx - **Validatie:** altijd Zod, nooit handmatige checks -- **Eigenaarschap:** elke Server Action en Route Handler controleert dat de resource bij de ingelogde gebruiker hoort +- **Toegangsmodel:** product-scoped resources gebruiken `productAccessFilter(userId)` tenzij het expliciet een eigenaarsactie is +- **Bulk-ID's:** reorder- en beslissingsacties valideren dat alle meegegeven IDs binnen dezelfde parent-scope vallen voordat er geschreven wordt +- **Foreign keys:** denormalized keys zoals `story.product_id` worden afgeleid uit de database-parent (`pbi.product_id`), nooit uit client-input - **Demo-check:** elke Server Action controleert `session.isDemo` vóór schrijven - **Foutberichten:** Nederlands voor eindgebruikers — comments in code: Engels +- **Dependencies:** elke geïmporteerde runtime package staat direct in `dependencies`, niet alleen transitief in `package-lock.json` +- **Docs-sync:** elke gedrags-, dependency-, API- of deploymentwijziging werkt README, relevante docs en patterns bij in dezelfde change --- @@ -126,3 +133,4 @@ SESSION_SECRET="" # openssl rand -base64 32 - [ ] App opzetbaar via README zonder extra hulp - [ ] CI/CD actief — falende build blokkeert merge - [ ] Beveiligingsreview API geslaagd (cross-user toegang onmogelijk) +- [ ] Documentatie is bijgewerkt voor gewijzigde API's, dependencies, deployment en agent-instructies diff --git a/README.md b/README.md index 3bfe104..4771aeb 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,199 @@ # Scrum4Me – Agile Project Management Tool ## Portfolio samenvatting + Scrum4Me is een moderne fullstack webapplicatie voor agile projectmanagement. De applicatie is gebouwd als portfolio-project om mijn vaardigheden in moderne softwareontwikkeling, cloud deployment en AI-assisted development te demonstreren. ## Doel + Veel teams missen overzicht en flexibiliteit in agile workflows. Scrum4Me biedt een lichtgewicht, web-based oplossing voor het beheren van sprints, taken en teamprocessen. ## Mijn rol + - Architectuur en ontwerp - Fullstack development (frontend + backend) - Database ontwerp -- Implementatie van authenticatie en API’s +- Implementatie van authenticatie en API's - CI/CD en deployment +- AI-assisted development workflow ## Functionaliteiten -- Agile dashboards (scrum boards) -- Taakbeheer (create/update/delete) + +- Agile dashboards en product backlogs +- PBI-, story-, sprint- en taakbeheer - Authenticatie en gebruikersbeheer +- Teamtoegang via eigenaar of gekoppelde Developer - API tokens voor externe integraties -- Drag-and-drop interactie -- Integratie met AI tooling (Claude Code) +- REST API voor Claude Code workflows +- Drag-and-drop interactie voor planning +- Story-activiteitenlog voor plannen, testresultaten en commits +- Profielfoto, bio en rolbeheer ## Technologie stack -- Next.js (App Router) -- React + +- Next.js 16 (App Router) +- React 19 - TypeScript - Prisma ORM - PostgreSQL (Neon) -- Vercel (hosting) -- GitHub Actions (CI/CD) +- iron-session en bcryptjs +- Zustand +- dnd-kit +- Tailwind CSS en shadcn/ui +- Sharp voor avatarverwerking +- Vercel Analytics +- Vercel hosting +- GitHub Actions / CI-CD ## Architectuur (kort) -- Frontend en backend via Next.js + +- Frontend en backend via Next.js App Router +- Server Components voor data loading +- Server Actions voor UI-mutaties +- Route Handlers voor de externe REST API - Database via Prisma + PostgreSQL -- Auth en API via server routes -- Deployment via Vercel +- Auth via versleutelde sessiecookies +- Producttoegang via eigenaar of `product_members` +- Deployment via Vercel met Neon als database ## Live demo -👉 Voeg hier je Vercel link toe + +Voeg hier je Vercel link toe. ## Screenshots -👉 Voeg hier screenshots toe (dashboard, board, etc.) + +Voeg hier screenshots toe van dashboard, product backlog, sprint planning en instellingen. ## Wat ik geleerd heb -- Werken met moderne fullstack architectuur -- Integratie van database via Prisma -- Opzetten van CI/CD pipelines -- Structureren van schaalbare webapplicaties + +- Werken met moderne fullstack architectuur in Next.js +- Databaseontwerp met Prisma en PostgreSQL +- Server Actions combineren met REST API Route Handlers +- Beveiliging van cross-user en cross-scope toegang +- AI-assisted development integreren in een eigen workflow +- Cloud deployment en verificatie via Vercel +- Documentatie en agent-instructies verbeteren op basis van code review ## Toekomstige verbeteringen -- Multi-user samenwerking verbeteren + +- Multi-user samenwerking verder uitbreiden - Notificaties - Performance optimalisatie - Uitbreiding AI-functionaliteit +- Meer integratietests voor autorisatie en API-flows ## Repository + https://github.com/madhura68/Scrum4Me +--- +## Technische aanvulling +Deze sectie bevat de praktische projectinformatie die nodig is om de app lokaal te draaien, te deployen en veilig door te ontwikkelen. +### Lokale setup + +1. Installeer dependencies: + +```bash +npm ci +``` + +2. Maak lokale environment variabelen: + +```bash +cp .env.example .env.local +``` + +Vul daarna `DATABASE_URL` en `SESSION_SECRET` in. `DIRECT_URL` is optioneel lokaal, maar handig voor migraties in cloudomgevingen. + +3. Synchroniseer of migreer de database: + +```bash +npx prisma db push +``` + +4. Seed testdata indien nodig: + +```bash +npx prisma db seed +``` + +5. Start de app: + +```bash +npm run dev +``` + +De app draait standaard op `http://localhost:3000`. + +### Scripts + +```bash +npm run dev # lokale development server +npm run lint # ESLint +npm test # Vitest test suite +npm run build # productiebuild zoals Vercel die verwacht +``` + +### Environment variables + +Zie [.env.example](.env.example). + +| Variabele | Verplicht | Doel | +|---|---:|---| +| `DATABASE_URL` | Ja | PostgreSQL connection string voor Prisma | +| `DIRECT_URL` | Nee | Directe Neon connection string voor migraties | +| `SESSION_SECRET` | Ja | Minimaal 32 tekens; gebruikt door iron-session | +| `NODE_ENV` | Nee | Wordt door Node/Vercel gezet | + +Vercel Analytics gebruikt geen project-specifieke environment variabele in deze app; de component staat in `app/layout.tsx`. + +### API-overzicht + +Alle API-endpoints vereisen: + +```http +Authorization: Bearer +``` + +| Methode | Endpoint | Doel | +|---|---|---| +| `GET` | `/api/products` | Actieve producten waarvoor de tokengebruiker eigenaar of teamlid is | +| `GET` | `/api/products/:id/next-story` | Volgende story uit de actieve sprint | +| `GET` | `/api/sprints/:id/tasks?limit=10` | Eerste taken van een sprint | +| `PATCH` | `/api/stories/:id/tasks/reorder` | Taakvolgorde aanpassen; alle IDs moeten bij de story horen | +| `POST` | `/api/stories/:id/log` | Implementatieplan, testresultaat of commit vastleggen | +| `PATCH` | `/api/tasks/:id` | Taakstatus of implementatieplan bijwerken | +| `POST` | `/api/todos` | Todo aanmaken binnen een productcontext | + +### Security-regels + +- Server Actions en Route Handlers vertrouwen nooit op losse client-ID's zonder scope-check. +- Producttoegang loopt via eigenaar of `product_members`. +- Bulk-mutaties valideren eerst dat alle IDs bij dezelfde toegankelijke parent horen. +- Denormalized fields zoals `story.product_id` worden afgeleid van de database-parent, niet van form-data. +- Demo-gebruikers krijgen 403 op schrijfoperaties. + +### Deployment + +De productieomgeving is gericht op Vercel + Neon. + +1. Zet `DATABASE_URL`, eventueel `DIRECT_URL`, en `SESSION_SECRET` in Vercel. +2. Zorg dat de Neon-database gemigreerd is. +3. Push naar `main`; Vercel deployt automatisch. +4. Controleer na deployment: + - login en dashboard + - `/api/products` met een API-token + - avatar-upload + - Vercel Analytics in het Vercel dashboard + +### Documentatie + +- [Functionele specificatie](docs/scrum4me-functional-spec.md) +- [Technische architectuur](docs/scrum4me-architecture.md) +- [Backlog](docs/scrum4me-backlog.md) +- [Agent-instructie audit](docs/agent-instruction-audit.md) diff --git a/app/layout.tsx b/app/layout.tsx index 1c549d8..78b08fe 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import { Analytics } from "@vercel/analytics/next"; import { Toaster } from "sonner"; import "./globals.css"; @@ -43,6 +44,7 @@ export default function RootLayout({ > {children} + diff --git a/docs/agent-instruction-audit.md b/docs/agent-instruction-audit.md new file mode 100644 index 0000000..a3bc7c2 --- /dev/null +++ b/docs/agent-instruction-audit.md @@ -0,0 +1,88 @@ +# Agent Instruction Audit + +Datum: 2026-04-25 + +## Aanleiding + +Tijdens de code review kwamen twee soorten fouten naar voren: + +- Server Actions vertrouwden client-provided IDs te veel nadat alleen de parent-resource was gecontroleerd. +- Documentatie liep achter op dependency-, API- en deploymentwijzigingen. + +Dit document legt vast welke wijzigingen zijn gecontroleerd, welke documentatie is bijgewerkt en welke instructies Claude Code en Codex voortaan expliciet moeten volgen. + +## Gecontroleerde wijzigingen + +| Wijziging | Code-locatie | Documentatie bijgewerkt | +|---|---|---| +| Reorder-acties valideren alle IDs binnen de juiste parent-scope | `actions/stories.ts`, `actions/sprints.ts` | `docs/scrum4me-architecture.md`, `docs/patterns/server-action.md`, `docs/patterns/sort-order.md`, `CLAUDE.md`, `AGENTS.md` | +| Sprint afronden accepteert alleen stories uit de actieve sprint | `actions/sprints.ts` | `docs/scrum4me-architecture.md`, `docs/patterns/server-action.md`, `AGENTS.md` | +| Todo-promotie gebruikt scoped todo lookup en `pbi.product_id` als bron van waarheid | `actions/todos.ts` | `docs/scrum4me-architecture.md`, `docs/patterns/server-action.md`, `CLAUDE.md`, `AGENTS.md` | +| `GET /api/products` retourneert ook gedeelde producten via `product_members` | `app/api/products/route.ts` | `README.md`, `docs/scrum4me-functional-spec.md`, `docs/scrum4me-backlog.md`, `docs/patterns/route-handler.md` | +| `sharp` is directe runtime dependency | `package.json`, `package-lock.json` | `README.md`, `docs/scrum4me-architecture.md`, `CLAUDE.md` | +| Vercel Analytics is toegevoegd aan de root layout | `app/layout.tsx`, `package.json`, `package-lock.json` | `README.md`, `docs/scrum4me-architecture.md`, `CLAUDE.md` | +| `.env.example` ontbrak ondanks verwijzingen in architectuurdocs | `.env.example` | `README.md`, `docs/scrum4me-architecture.md` | + +## Preventieve regels + +### Access-control regels + +Agents mogen nooit aannemen dat een ID veilig is omdat de UI hem normaal gesproken correct doorgeeft. Elke Server Action en Route Handler moet bij writes de volledige resource-scope bewijzen. + +Concrete regels: + +- Gebruik `productAccessFilter(userId)` voor product-scoped resources waar eigenaar en teamlid toegang hebben. +- Gebruik owner-only filters alleen bij echte eigenaarsacties. +- Valideer bulk-ID's met `id in (...)` plus parent-scope voordat er wordt geschreven. +- Weiger dubbele IDs in reorder- en decision-payloads. +- Leid denormalized foreign keys af van de database-parent, niet van client-input. +- Valideer runtime waarden met Zod of expliciete runtime checks; TypeScript types alleen zijn onvoldoende. +- Gebruik scoped deletes/updates nadat ownership bewezen is. + +### Documentatie-regels + +Een codewijziging is niet klaar als de documentatie een oud contract beschrijft. Agents moeten bij elke wijziging nagaan of deze plekken geraakt worden: + +- `README.md`: setup, scripts, dependencies, deployment, API-overzicht. +- `docs/scrum4me-functional-spec.md`: functionele eisen en API-contracten. +- `docs/scrum4me-architecture.md`: stack, datamodel, securitymodel, env vars, deployment. +- `docs/scrum4me-backlog.md`: "done when"-criteria en scope van backlog-items. +- `docs/patterns/`: herbruikbare implementatiepatronen. +- `CLAUDE.md` en `AGENTS.md`: agent-regels die de fout in de toekomst voorkomen. + +### Dependency-regels + +- Elke runtime import moet als directe dependency in `package.json` staan. +- Als een dependency aan deploymentgedrag raakt, moet README of architectuurdocs dat noemen. +- Na `npm install` moet `package-lock.json` meegaan in dezelfde change. + +### Verification-regels + +Voor oplevering: + +```bash +npm run lint +npm test +npm run build +``` + +Bij securitywijzigingen hoort minimaal een test die laat zien dat cross-user of cross-scope input wordt geweigerd. Als die test nog niet bestaat, noteer dat expliciet in de oplevering. + +## Claude Code + +`CLAUDE.md` is aangescherpt met: + +- een verwijzing naar dit auditdocument; +- de directe stackwijzigingen voor Sharp en Vercel Analytics; +- expliciete access-control regels voor `productAccessFilter`, bulk-ID's en foreign-key afleiding; +- een docs-sync regel voor gedrags-, API-, dependency- en deploymentwijzigingen. + +## Codex + +`AGENTS.md` is uitgebreid van alleen Next.js-waarschuwing naar projectregels voor: + +- access control; +- documentatie-sync; +- verificatiecommands. + +Deze regels zijn korter dan `CLAUDE.md`, maar bewust dwingend: ze moeten direct gelezen worden bij elke Codex-taak in deze repo. diff --git a/docs/patterns/route-handler.md b/docs/patterns/route-handler.md index 35c2b32..1b33886 100644 --- a/docs/patterns/route-handler.md +++ b/docs/patterns/route-handler.md @@ -36,6 +36,7 @@ export async function authenticateApiRequest(request: Request) { // app/api/products/[id]/next-story/route.ts import { authenticateApiRequest } from '@/lib/api-auth' import { prisma } from '@/lib/prisma' +import { productAccessFilter } from '@/lib/product-access' export async function GET( request: Request, @@ -49,7 +50,7 @@ export async function GET( const { id } = await params const sprint = await prisma.sprint.findFirst({ - where: { product_id: id, status: 'ACTIVE', product: { user_id: auth.userId } }, + where: { product_id: id, status: 'ACTIVE', product: productAccessFilter(auth.userId) }, }) if (!sprint) { return Response.json({ error: 'Geen actieve Sprint gevonden' }, { status: 404 }) @@ -88,3 +89,11 @@ export async function GET( | POST | `/api/stories/:id/log` | Plan / testresultaat / commit vastleggen | | PATCH | `/api/tasks/:id` | Taakstatus bijwerken | | POST | `/api/todos` | Todo aanmaken | + +## Security-invarianten + +- Elk endpoint start met `authenticateApiRequest`. +- Schrijf-endpoints geven `403` voor demo-tokens. +- Product-scoped reads en writes gebruiken `productAccessFilter(auth.userId)`, zodat eigenaar en gekoppeld teamlid hetzelfde toegangsmodel volgen. +- Endpoints die geordende ID-lijsten ontvangen valideren dat elke ID bij de parent-resource hoort voordat er wordt geupdated. +- JSON bodies worden met Zod gevalideerd; TypeScript types zijn geen runtime-beveiliging. diff --git a/docs/patterns/server-action.md b/docs/patterns/server-action.md index f076dff..3f350fe 100644 --- a/docs/patterns/server-action.md +++ b/docs/patterns/server-action.md @@ -11,6 +11,7 @@ import { cookies } from 'next/headers' import { z } from 'zod' import { prisma } from '@/lib/prisma' import { SessionData, sessionOptions } from '@/lib/session' +import { productAccessFilter } from '@/lib/product-access' const schema = z.object({ productId: z.string().cuid(), @@ -32,9 +33,9 @@ export async function createPbi(formData: FormData) { }) if (!parsed.success) return { error: parsed.error.flatten().fieldErrors } - // 3. Eigenaarschap controleren + // 3. Toegang controleren: eigenaar of gekoppeld product member const product = await prisma.product.findFirst({ - where: { id: parsed.data.productId, user_id: session.userId } + where: { id: parsed.data.productId, ...productAccessFilter(session.userId) } }) if (!product) return { error: 'Product niet gevonden' } @@ -51,3 +52,13 @@ export async function createPbi(formData: FormData) { return { success: true, pbi } } ``` + +## Security-invarianten + +- Controleer auth en `session.isDemo` voordat er geschreven wordt. +- Gebruik `productAccessFilter(userId)` voor resources waar eigenaar en gekoppelde Developer beide toegang hebben. +- Gebruik eigenaar-only filters (`user_id: session.userId`) alleen voor eigenaarsacties zoals product archiveren, teamleden beheren of persoonlijke todos. +- Vertrouw nooit losse client-ID's. Als een action meerdere IDs ontvangt, haal ze eerst op met `id in (...)` plus de parent-scope en weiger de operatie als het aantal gevonden records niet exact gelijk is. +- Weiger dubbele IDs in reorder-lijsten of beslissingsobjecten. +- Leid denormalized foreign keys af uit de database-parent. Voorbeeld: gebruik `pbi.product_id` bij story creation, niet `formData.get('productId')`. +- Delete pas nadat ownership/scoping bewezen is; gebruik scoped `deleteMany` als een directe unique `delete` anders een cross-user record kan raken. diff --git a/docs/patterns/sort-order.md b/docs/patterns/sort-order.md index 5fd6d94..bcd2658 100644 --- a/docs/patterns/sort-order.md +++ b/docs/patterns/sort-order.md @@ -27,3 +27,13 @@ async function reindexIfNeeded(items: { id: string; sort_order: number }[]) { } } ``` + +## Reorder Server Actions + +Een drag-and-drop reorder stuurt altijd client-controlled ID-lijsten naar de server. Behandel die lijst als onbetrouwbaar. + +- Weiger dubbele IDs. +- Haal alle IDs op met de parent-scope, bijvoorbeeld `product_id`, `pbi_id`, `sprint_id` of `story_id`. +- Weiger de operatie als het aantal gevonden records niet exact gelijk is aan het aantal aangeleverde IDs. +- Update pas daarna `sort_order` in een transactie. +- Gebruik bij priority changes dezelfde parent uit de database, niet een los meegegeven `productId`. diff --git a/docs/scrum4me-architecture.md b/docs/scrum4me-architecture.md index ff3941e..9091009 100644 --- a/docs/scrum4me-architecture.md +++ b/docs/scrum4me-architecture.md @@ -7,7 +7,7 @@ ## Architectuursamenvatting -Scrum4Me is een desktop-first Next.js 16 webapplicatie die server-side wordt gerenderd en gedeployed op Vercel. De database is PostgreSQL via Neon, aangestuurd via Prisma v7. Authenticatie is custom username/password via iron-session — geen externe auth-provider, geen e-mail. De REST API voor Claude Code-integratie loopt via Next.js Route Handlers, beveiligd met API-tokens. Drag-and-drop in de planningsschermen wordt afgehandeld door dnd-kit. +Scrum4Me is een desktop-first Next.js 16 webapplicatie die server-side wordt gerenderd en gedeployed op Vercel. De database is PostgreSQL via Neon, aangestuurd via Prisma v7. Authenticatie is custom username/password via iron-session — geen externe auth-provider, geen e-mail. De REST API voor Claude Code-integratie loopt via Next.js Route Handlers, beveiligd met API-tokens. Drag-and-drop in de planningsschermen wordt afgehandeld door dnd-kit. Vercel Analytics meet pageviews via de root layout; profielfoto's worden server-side verwerkt met Sharp. --- @@ -25,6 +25,8 @@ Scrum4Me is een desktop-first Next.js 16 webapplicatie die server-side wordt ger | Authenticatie | Custom — iron-session + bcrypt | Username/password zonder e-mail vereist geen externe auth-provider; iron-session beheert versleutelde cookies server-side | | Drag-and-drop | dnd-kit | Actief onderhouden, React-native hooks, 60fps bij grote lijsten, ondersteuning voor meerdere containers | | REST API | Next.js Route Handlers (`/app/api/`) | Naast Server Actions nodig voor Claude Code-integratie; Route Handlers zijn volledig HTTP-compatibel | +| Image processing | Sharp | Avataruploads worden gevalideerd, geschaald en als WebP opgeslagen in PostgreSQL | +| Analytics | Vercel Analytics (`@vercel/analytics/next`) | Pageviews zonder extra client-configuratie; component staat in `app/layout.tsx` | | Hosting | Vercel | Zero-config Next.js deployment; preview-URLs per PR; gratis tier voldoende voor v1 | | CI/CD | GitHub Actions | Lint + typecheck + build op elke PR; Vercel handelt de daadwerkelijke deploy af | @@ -239,6 +241,27 @@ Koppelt Developer-gebruikers aan een product backlog. De eigenaar (`products.use --- +## Toegangsmodel en schrijfbeveiliging + +Producttoegang is centraal gedefinieerd als: + +- eigenaar: `products.user_id === gebruiker.id` +- teamlid: `product_members` bevat `(product_id, user_id)` + +Code gebruikt hiervoor `productAccessFilter(userId)` uit `lib/product-access.ts`. Route Handlers en Server Actions mogen geen eigenaar-only filter (`user_id`) gebruiken voor product-scoped resources tenzij het expliciet om eigenaarsbeheer gaat, zoals archiveren of teamleden beheren. + +Schrijfoperaties volgen deze invarianten: + +- Controleer eerst authenticatie en `session.isDemo`. +- Valideer input met Zod, maar behandel TypeScript types niet als runtime-beveiliging. +- Controleer de parent-resource met `productAccessFilter`. +- Vertrouw bulk-ID's nooit los: haal de records eerst op met `id in (...)` plus de parent-scope (`product_id`, `pbi_id`, `sprint_id` of `story_id`) en weiger de operatie als aantallen niet exact overeenkomen. +- Weiger dubbele IDs in reorder- en beslissingslijsten. +- Leid denormalized foreign keys af van de database-parent (`pbi.product_id`, `sprint.product_id`) en niet van form-data of JSON body. +- Delete of update alleen nadat de resource scoped is gevonden; gebruik scoped `deleteMany`/`updateMany` wanneer een unique `delete` anders onveilig zou zijn. + +--- + ## Prisma Schema (excerpt) ```prisma @@ -474,8 +497,9 @@ Inloggen: → bij mismatch: generieke foutmelding (geen onderscheid) Sessie per request: - middleware.ts → iron-session cookie uitlezen → user_id + is_demo in request - → beschermde routes: redirect /login als geen geldige sessie + proxy.ts → sessiecookie-aanwezigheid controleren + → beschermde routes: redirect /login als geen sessiecookie aanwezig is + → app layout valideert de volledige sessie server-side API-aanroepen (Claude Code): Authorization: Bearer header → SHA-256 hash → opzoeken in api_tokens @@ -554,7 +578,7 @@ scrum4me/ │ ├── schema.prisma │ ├── migrations/ │ └── seed.ts # Testdata uit Product Backlog document -├── middleware.ts # Sessiecheck op beschermde routes +├── proxy.ts # Next.js 16 proxy voor route protection ├── prisma.config.ts # Prisma v7 config (DATABASE_URL) └── .env.example ``` @@ -755,6 +779,7 @@ NODE_ENV="development" - [ ] `prisma migrate deploy` uitgevoerd op productiedatabase - [ ] Demo-gebruiker aangemaakt via seed of handmatig - [ ] API-token aangemaakt en getest met `curl`-aanroep naar `/api/products` +- [ ] Vercel Analytics actief in het Vercel dashboard na eerste productiebezoek - [ ] Vercel preview-deployments getest op een PR - [ ] `next build` lokaal geslaagd zonder TypeScript-fouten diff --git a/docs/scrum4me-backlog.md b/docs/scrum4me-backlog.md index 6f57482..82dffa2 100644 --- a/docs/scrum4me-backlog.md +++ b/docs/scrum4me-backlog.md @@ -53,8 +53,8 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan - Schrijf `lib/auth.ts` (registreer met bcrypt hash, verifieer bij inloggen); schrijf `lib/session.ts` (iron-session config); implementeer `/register` en `/login` pagina's met Server Actions; sla `{ userId, isDemo }` op in sessiecookie - Done when: registreren → ingelogde sessie → redirect `/dashboard`; inloggen met verkeerde credentials geeft generieke foutmelding; sessie blijft actief na paginaverversing -- [ ] **ST-007** Route-beveiliging via `middleware.ts` - - Schrijf `middleware.ts` die iron-session cookie uitleest; redirect naar `/login` bij alle `/dashboard`, `/products/*`, `/todos`, `/settings/*` routes zonder geldige sessie; authenticated users worden van `/login` en `/register` doorgestuurd naar `/dashboard` +- [ ] **ST-007** Route-beveiliging via `proxy.ts` + - Schrijf `proxy.ts` die sessiecookie-aanwezigheid controleert; redirect naar `/login` bij alle `/dashboard`, `/products/*`, `/todos`, `/settings/*` routes zonder sessiecookie; authenticated users worden van `/login` en `/register` doorgestuurd naar `/dashboard`; volledige sessievalidatie gebeurt server-side in de app layout - Done when: directe navigatie naar `/dashboard` zonder sessie redirect naar `/login`; ingelogde gebruiker op `/login` redirect naar `/dashboard` - [ ] **ST-008** Navigatieshell + dashboard-layout @@ -214,8 +214,8 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan - Done when: token aangemaakt en waarde zichtbaar; na sluiten dialoog niet meer te zien; intrekken maakt token onbruikbaar (getest via curl) - [ ] **ST-403** `GET /api/products` — productenlijst - - Route Handler; authenticatie via `api-auth.ts`; retourneer actieve producten `[{ id, name, repo_url }]` als JSON - - Done when: `curl -H "Authorization: Bearer " /api/products` retourneert correct JSON; 401 zonder token + - Route Handler; authenticatie via `api-auth.ts`; retourneer actieve producten `[{ id, name, repo_url }]` als JSON voor producten waar de tokengebruiker eigenaar of teamlid is + - Done when: `curl -H "Authorization: Bearer " /api/products` retourneert correct JSON inclusief gedeelde product backlogs; 401 zonder token - [ ] **ST-404** `GET /api/products/:id/next-story` — volgende story ophalen - Route Handler; haal hoogst geprioriteerde OPEN story op van actieve Sprint van het product (priority ASC, sort_order ASC); retourneer `{ id, title, description, acceptance_criteria, tasks[] }`; 404 als geen open stories @@ -318,17 +318,17 @@ De MVP is klaar wanneer Lars — de primaire persona — de volledige cyclus kan - Done when: 11 snelle inlogpogingen leiden tot 429-respons met duidelijke melding - [ ] **ST-609** Beveiligingsreview API-endpoints - - Controleer alle Route Handlers: elke schrijfoperatie valideert dat de resource bij de geverifieerde gebruiker hoort; cross-user reads zijn onmogelijk; voeg integratietests toe die cross-user toegang testen - - Done when: poging om andere gebruikers's product op te halen via API geeft 403; getest met twee test-gebruikers + - Controleer alle Route Handlers: elke schrijfoperatie valideert dat de resource binnen de toegankelijke product-scope valt; cross-scope reads zijn onmogelijk tenzij de gebruiker via `product_members` gekoppeld is; voeg integratietests toe die cross-user toegang testen + - Done when: poging om een niet-gedeeld product van een andere gebruiker op te halen via API geeft 403 of 404; gedeelde producten zijn wel zichtbaar; getest met twee test-gebruikers - [ ] **ST-610** CI/CD via GitHub Actions - Workflow: `lint` (ESLint), `typecheck` (tsc --noEmit), `prisma validate`, `build` (next build) op elke PR en push naar main; Vercel auto-deploy op main - Done when: een TypeScript-fout in een PR blokkeert merge; succesvolle merge triggert Vercel-deploy - [ ] **ST-611** README en lokale setup-documentatie - - Schrijf `README.md` met: wat is Scrum4Me, quickstart lokaal (clone → env → prisma push → seed → dev), cloud deployment (Vercel + Neon stappenplan), API-documentatie (alle 7 endpoints met voorbeelden), Claude Code-integratie uitleg + - Schrijf `README.md` met: wat is Scrum4Me, quickstart lokaal (clone → env → prisma push → seed → dev), cloud deployment (Vercel + Neon stappenplan), API-documentatie (alle 7 endpoints met voorbeelden), Claude Code-integratie uitleg, Vercel Analytics status en directe dependencies zoals Sharp - De in-app landingspagina (`/`) bevat al een gebruikershandleiding, Scrum-samenvatting en API-overzicht — de README richt zich op lokale setup en deployment - - Done when: iemand zonder context de app lokaal kan draaien op basis van alleen de README + - Done when: iemand zonder context de app lokaal kan draaien op basis van alleen de README en `.env.example` - [ ] **ST-612** End-to-end acceptatietest - Voer handmatig de volledige Lars-flow uit: product aanmaken → PBI's en stories aanmaken → Sprint starten → stories slepen → taken aanmaken → API-token aanmaken → curl `next-story` → curl `log` (plan, test, commit) → activiteitenlog controleren in UI diff --git a/docs/scrum4me-functional-spec.md b/docs/scrum4me-functional-spec.md index c3d2812..6dbe167 100644 --- a/docs/scrum4me-functional-spec.md +++ b/docs/scrum4me-functional-spec.md @@ -362,7 +362,7 @@ Een REST API waarmee Claude Code stories en taken kan ophalen, de taakvolgorde k **Acceptatiecriteria:** **Endpoints:** -- [ ] `GET /api/products` — lijst van actieve producten +- [ ] `GET /api/products` — lijst van actieve producten waarvoor de tokengebruiker eigenaar of teamlid is - [ ] `GET /api/products/:id/next-story` — hoogst geprioriteerde open story van de actieve Sprint - [ ] `GET /api/sprints/:id/tasks?limit=10` — eerste N taken in huidige volgorde - [ ] `PATCH /api/stories/:id/tasks/reorder` — accepteert geordende lijst van taak-id's @@ -474,6 +474,7 @@ De app is deployable op Vercel + Neon PostgreSQL en lokaal draaibaar met een Neo | Toegankelijkheid | Keyboard-navigatie voor alle primaire acties; WCAG AA voor de sprint planning flow | | Database | Prisma ORM; PostgreSQL via Neon | | Deployment | Vercel (cloud) of lokaal via `npm run dev` | +| Analytics | Vercel Analytics via `@vercel/analytics/next` in de root layout | | Sessiebeheer | Server-side sessie of JWT; geen opslag van gevoelige data in localStorage | --- diff --git a/package-lock.json b/package-lock.json index 183d1c5..fdf7dc4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@dnd-kit/utilities": "^3.2.2", "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", + "@vercel/analytics": "^2.0.1", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -4342,6 +4343,48 @@ "win32" ] }, + "node_modules/@vercel/analytics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@vercel/analytics/-/analytics-2.0.1.tgz", + "integrity": "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==", + "license": "MIT", + "peerDependencies": { + "@remix-run/react": "^2", + "@sveltejs/kit": "^1 || ^2", + "next": ">= 13", + "nuxt": ">= 3", + "react": "^18 || ^19 || ^19.0.0-rc", + "svelte": ">= 4", + "vue": "^3", + "vue-router": "^4" + }, + "peerDependenciesMeta": { + "@remix-run/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "nuxt": { + "optional": true + }, + "react": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + }, + "vue-router": { + "optional": true + } + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", diff --git a/package.json b/package.json index dae3058..ffbfb6d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@dnd-kit/utilities": "^3.2.2", "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", + "@vercel/analytics": "^2.0.1", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1",