Add analytics and documentation updates
This commit is contained in:
parent
e0efb65efb
commit
b5e967d8d3
15 changed files with 414 additions and 37 deletions
88
docs/agent-instruction-audit.md
Normal file
88
docs/agent-instruction-audit.md
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
||||
---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue