Add analytics and documentation updates

This commit is contained in:
Janpeter Visser 2026-04-25 15:11:51 +02:00
parent e0efb65efb
commit b5e967d8d3
15 changed files with 414 additions and 37 deletions

10
.env.example Normal file
View file

@ -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"

1
.gitignore vendored
View file

@ -32,6 +32,7 @@ yarn-error.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
# vercel
.vercel

View file

@ -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.
<!-- END:nextjs-agent-rules -->
# 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
```

View file

@ -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

173
README.md
View file

@ -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 APIs
- 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 <token>
```
| 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)

View file

@ -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({
>
<body className="min-h-full flex flex-col">
{children}
<Analytics />
<Toaster richColors position="bottom-right" />
</body>
</html>

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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`.

View file

@ -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 <token> 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

View file

@ -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 <token>" /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 <token>" /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

View file

@ -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 |
---

43
package-lock.json generated
View file

@ -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",

View file

@ -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",