chore: align migrations, docs, and frontends with crewli.app setup

- Replace dated migrations with ordered 2026_04_07_* chain; fold users update into base migration
- Update OrganisationScope, AppServiceProvider, seeders, api routes, and .env.example
- Refresh Cursor rules, CLAUDE.md, Makefile, README, and docs (API, SCHEMA, SETUP)
- Adjust admin/app/portal HTML, packages, api-client, events types, and theme config
- Update docker-compose and VS Code settings; remove stray Office lock files from resources

Made-with: Cursor
This commit is contained in:
2026-04-07 10:45:34 +02:00
parent 5e2ede14b4
commit fda161ee09
53 changed files with 355 additions and 446 deletions

171
CLAUDE.md
View File

@@ -1,129 +1,132 @@
# EventCrew — Claude Code Instructies
# Crewli — Claude Code Instructions
## Project Context
## Project context
EventCrew is een multi-tenant SaaS platform voor event- en festivalbeheer.
Gebouwd voor een professionele vrijwilligersorganisatie, met SaaS-uitbreidingspotentieel.
Design Document: /docs/EventCrew_Design_Document_v1.3.docx
Crewli is a multi-tenant SaaS platform for event and festival management.
Built for a professional volunteer organisation, with potential to expand as SaaS.
Design document: `/resources/design/Crewli_Design_Document_v1.3.docx`
## Tech Stack
## Tech stack
- Backend: PHP 8.2+, Laravel 12, Sanctum, Spatie Permission, MySQL 8, Redis
- Frontend: TypeScript, Vue 3 (Composition API), Vuexy/Vuetify, Pinia, TanStack Query
- Testing: PHPUnit (backend), Vitest (frontend)
## Repository Structuur
## Repository layout
- api/ Laravel backend
- apps/admin/ Super Admin SPA
- apps/app/ Organizer SPA (hoofdapp)
- apps/portal/ Externe portals (vrijwilliger, artiest, leverancier)
- `api/` Laravel backend
- `apps/admin/` Super Admin SPA
- `apps/app/` Organizer SPA (main product app)
- `apps/portal/` External portal (volunteers, artists, suppliers, etc.)
## Apps & Portal Architectuur
## Apps and portal architecture
- apps/admin/ = Super Admin platformbeheer, organisaties aanmaken
- apps/app/ = Organizer event management per organisatie
- apps/portal/ = Externe gebruikers — één app, twee toegangsmodi:
- Login-based (auth:sanctum): vrijwilligers, crew — persons met user_id
- Token-based (portal.token middleware): artiesten, leveranciers, pers — persons zonder user_id
- `apps/admin/` Super Admin: platform management, creating organisations
- `apps/app/` Organizer: event management per organisation
- `apps/portal/` External users: one app, two access modes:
- Login-based (`auth:sanctum`): volunteers, crew — persons with `user_id`
- Token-based (`portal.token` middleware): artists, suppliers, press — persons without `user_id`
### CORS
Drie frontend origins configureren in zowel Laravel (config/cors.php via env) als Vite dev server proxy:
- admin: localhost:5173
- app: localhost:5174
- portal: localhost:5175
Configure three frontend origins in both Laravel (`config/cors.php` via env) and the Vite dev server proxy:
## Backend Regels (STRIKT VOLGEN)
- admin: `localhost:5173`
- app: `localhost:5174`
- portal: `localhost:5175`
**Production (`crewli.app`):** API `https://api.crewli.app`, SPAs `https://admin.crewli.app`, `https://app.crewli.app`, `https://portal.crewli.app` — see `api/.env.example` for `FRONTEND_*` and `SANCTUM_STATEFUL_DOMAINS`. **`crewli.nl`** is only for a future marketing site; this application stack uses **`crewli.app`** (not `.nl` for API, SPAs, or transactional mail).
## Backend rules (strict)
### Multi-tenancy
- ELKE query op event-data MOET scoperen op organisation_id
- Gebruik OrganisationScope als Eloquent Global Scope op alle event-gerelateerde modellen
- Nooit directe id-checks in controllers — gebruik altijd Policies
- Every query on event data **must** scope on `organisation_id`
- Use `OrganisationScope` as an Eloquent global scope on all event-related models
- Never use raw ID checks in controllers — always use policies
### Controllers
- Gebruik Resource Controllers (index/show/store/update/destroy)
- Namespace: App\Http\Controllers\Api\V1\
- Alle responses via API Resources (nooit model-attributen direct teruggeven)
- Validatie via Form Requests (nooit inline validate())
- Use resource controllers (`index` / `show` / `store` / `update` / `destroy`)
- Namespace: `App\Http\Controllers\Api\V1\`
- Return all responses through API resources (never return raw model attributes)
- Validate with form requests (never inline `validate()`)
### Modellen
### Models
- Gebruik HasUlids trait op alle business-modellen (GEEN UUID v4)
- Soft deletes op: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist
- GEEN soft deletes op: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit-records)
- JSON kolommen ALLEEN voor opaque configuratie — nooit voor queryable data
- Use the `HasUlids` trait on all business models (not UUID v4)
- Soft deletes on: Organisation, Event, FestivalSection, Shift, ShiftAssignment, Person, Artist
- No soft deletes on: CheckIn, BriefingSend, MessageReply, ShiftWaitlist (audit records)
- JSON columns **only** for opaque configuration — never for queryable/filterable data
### Database
- Primaire sleutels: ULID via HasUlids (niet UUID v4, niet auto-increment voor business tables)
- Elke migratie in volgorde aanmaken: eerst foundation, dan afhankelijke tabellen
- ALTIJD composite indexes toevoegen zoals gedocumenteerd in het design document sectie 3.5
- Primary keys: ULID via `HasUlids` (not UUID v4, not auto-increment on business tables)
- Create migrations in dependency order: foundation first, then dependent tables
- Always add composite indexes as documented in the design document (section 3.5)
### Rollen & Permissies
### Roles and permissions
- Gebruik Spatie laravel-permission
- Check rollen via $user->hasRole() en Policies — nooit hardcoded role strings in controllers
- Drie niveaus: app (super_admin), organisatie (org_admin/org_member), event (event_manager etc.)
- Use Spatie `laravel-permission`
- Check roles via `$user->hasRole()` and policies — never hard-code role strings in controllers
- Three levels: app (`super_admin`), organisation (`org_admin` / `org_member`), event (`event_manager`, etc.)
### Testing
- Schrijf PHPUnit Feature Tests per controller
- Minimaal per endpoint: happy path + unauthenticated (401) + wrong organisation (403)
- Gebruik factories voor alle test-data
- Draai tests NA elke module: php artisan test --filter=ModuleNaam
- Write PHPUnit feature tests per controller
- Minimum per endpoint: happy path + unauthenticated (401) + wrong organisation (403)
- Use factories for all test data
- After each module: `php artisan test --filter=ModuleName`
## Frontend Regels (STRIKT VOLGEN)
## Frontend rules (strict)
### Vue Componenten
### Vue components
- Altijd <script setup lang='ts'> — nooit Options API
- Props altijd getypeerd met defineProps<{...}>()
- Emits altijd gedeclareerd met defineEmits<{...}>()
- Always `<script setup lang="ts">` — never the Options API
- Type props with `defineProps<{...}>()`
- Declare emits with `defineEmits<{...}>()`
### API Calls
### API calls
- Gebruik TanStack Query (useQuery / useMutation) voor ALLE API calls
- Nooit direct axios in een component — altijd via een composable in composables/api/
- Pinia stores voor cross-component state — nooit prop drilling
- Use TanStack Query (`useQuery` / `useMutation`) for **all** API calls
- Never call axios directly from a component — always via a composable under `composables/`
- Use Pinia stores for cross-component state — no prop drilling
### Naamgeving
### Naming
- DB kolommen: snake_case
- TypeScript/JS variabelen: camelCase
- Vue componenten: PascalCase (bijv. ShiftAssignPanel.vue)
- Composables: use-prefix (bijv. useShifts.ts)
- Pinia stores: use-suffix store (bijv. useEventStore.ts)
- DB columns: `snake_case`
- TypeScript / JS variables: `camelCase`
- Vue components: PascalCase (e.g. `ShiftAssignPanel.vue`)
- Composables: `use` prefix (e.g. `useShifts.ts`)
- Pinia stores: `use` prefix + `Store` suffix (e.g. `useEventStore.ts`)
### UI
- Gebruik ALTIJD Vuexy/Vuetify componenten voor layout, forms, tabellen, dialogen
- Nooit custom CSS schrijven als een Vuetify klasse bestaat
- Responsief: mobile-first, minimaal werkend op 375px breedte
- Always use Vuexy/Vuetify for layout, forms, tables, dialogs
- Do not write custom CSS when a Vuetify utility class exists
- Responsive: mobile-first, usable from 375px width
## Verboden Patronen
## Forbidden patterns
- NOOIT: $user->role === 'admin' (gebruik policies)
- NOOIT: Model::all() zonder where-clausule (altijd scopen)
- NOOIT: dd() of var_dump() achterlaten in code
- NOOIT: .env waarden hardcoden in code
- NOOIT: JSON kolommen gebruiken voor data waarop gefilterd wordt
- NOOIT: UUID v4 als primaire sleutel (gebruik HasUlids)
- Never: `$user->role === 'admin'` (use policies)
- Never: `Model::all()` without a `where` clause (always scope)
- Never: leave `dd()` or `var_dump()` in code
- Never: hard-code `.env` values in code
- Never: use JSON columns for data you need to filter on
- Never: UUID v4 as primary key (use `HasUlids`)
## Volgorde bij elke nieuwe module
## Order of work for each new module
1. Migratie(s) aanmaken en draaien
2. Eloquent Model met relaties, scopes en HasUlids
3. Factory voor test-data
4. Policy voor autorisatie
5. Form Request(s) voor validatie
6. API Resource voor response transformatie
7. Resource Controller
8. Routes registreren in api.php
9. PHPUnit Feature Test schrijven en draaien
10. Vue composable voor API calls (useModuleNaam.ts)
11. Pinia store indien cross-component state nodig
12. Vue pagina component
13. Route toevoegen in Vue Router
1. Create and run migration(s)
2. Eloquent model with relationships, scopes, and `HasUlids`
3. Factory for test data
4. Policy for authorization
5. Form request(s) for validation
6. API resource for response shaping
7. Resource controller
8. Register routes in `api.php`
9. Write and run PHPUnit feature tests
10. Vue composable for API calls (e.g. `useShifts.ts`)
11. Pinia store if cross-component state is needed
12. Vue page component
13. Add route in Vue Router