chore(docs): delete obsolete bootstrap and prompt-template docs
Five files removed, all describing project states that no longer apply post-WS-TOOLING-001: - .cursor/instructions.md (8.4 KB): Phase 1-4 roadmap with all checkboxes empty; Phase 1 has been done for ~6 months. Broken 'make portal' target. Content overlaps with CLAUDE.md. - .cursor/ARCHITECTURE.md (18.9 KB): pre-WS-3 framing (dual SPA, dual cookies, dual SANCTUM_STATEFUL_DOMAINS) AND pre-Form-Builder schema (volunteer_profiles, public_forms with JSON fields). All six sections superseded by SCHEMA.md, AUTH_ARCHITECTURE.md, design-document.md, API.md, 102_multi_tenancy.mdc. - dev-docs/MASTER_PROMPT_CC.md (13 KB): 'paste this above every task' workflow superseded by auto-loaded CLAUDE.md and structured Claude Chat-authored prompts. Stale dual-SPA + pre-Form-Builder assumptions throughout. - dev-docs/MASTER_PROMPT_CURSOR.md (7.5 KB): same workflow obsoletion; Cursor is now IDE-only (Claude Code does all implementation). .cursor/rules/ system handles IDE-level guidance. - dev-docs/dev-guide.md (32 KB): bootstrap-from-scratch document containing embedded snapshots of pre-Form-Builder CLAUDE.md, pre-Form-Builder SCHEMA.md, pre-Form-Builder API.md as copy-paste templates. Section 5 prompts pre-WS-TOOLING-001 era. Section 6 (agents) overlaps with CLAUDE_CODE_TOOLING.md. Total: ~80 KB doc-rot removed. Cross-reference check found four files outside the deleted set referencing the deleted paths; all updated in the same commit: - README.md: Documentation table rebuilt around CLAUDE.md + dev-docs/* (also dropped stale resources/design/ row pointing at a directory that no longer exists, and corrected docs/* paths to dev-docs/*) - dev-docs/CLAUDE_DESKTOP_SETUP.md: dropped MASTER_PROMPT_CC, MASTER_PROMPT_CURSOR, dev-guide entries from the bewust-verwijderd exclusion list; updated Gerelateerd pointer from dev-guide.md -> SETUP.md - dev-docs/ARCH-CONSOLIDATION-2026-04.md: updated future-distribution pointer from dev-guide.md -> SETUP.md (sprint briefing is historical so the change is purely doc-hygiene) - dev-docs/VIBE_CODING_CHECKLIST.md: removed Dev guide row from the bestandspaden table Remaining references in dev-docs/BACKLOG.md (lines 862-869) live inside the TECH-DOCS-APPS-PORTAL-PURGE entry that closes in the next commit. Canonical replacements: dev-docs/SETUP.md (rewritten this PR), CLAUDE.md, CLAUDE_CODE_TOOLING.md, and the ARCH-*.md series.
This commit is contained in:
@@ -1,436 +0,0 @@
|
||||
# Crewli - Architecture
|
||||
|
||||
> Multi-tenant SaaS platform for event- and festival management.
|
||||
> Source of truth: `/resources/design/design-document.md`
|
||||
|
||||
## System Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ INTERNET │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐
|
||||
│ Organizer + │ │ Portal SPA │
|
||||
│ Admin SPA │ │ (External) │
|
||||
│ :5174 │ │ :5175 │
|
||||
└───────┬───────┘ └───────┬───────┘
|
||||
│ │
|
||||
└───────────────┼───────────────┘
|
||||
│ CORS + Sanctum tokens
|
||||
▼
|
||||
┌───────────────────────┐
|
||||
│ Laravel 12 REST API │
|
||||
│ (JSON only, no │
|
||||
│ Blade views) │
|
||||
│ :8000 │
|
||||
└───────────┬───────────┘
|
||||
│
|
||||
┌───────────────┼───────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────┐ ┌───────────┐ ┌───────────┐
|
||||
│ MySQL 8 │ │ Redis │ │ Mailpit │
|
||||
│ :3306 │ │ :6379 │ │ :8025 │
|
||||
└───────────┘ └───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
**Golden Rule:** Laravel is exclusively a JSON REST API. No Blade views, no Mix, no Inertia. Every response is `application/json`. Vue handles ALL UI via two SPAs.
|
||||
|
||||
---
|
||||
|
||||
## Applications
|
||||
|
||||
### Organizer App (`apps/app/`)
|
||||
|
||||
**Purpose**: Main application for event management per organisation. Also serves as the platform admin interface for `super_admin` users via `/platform/*` routes.
|
||||
|
||||
**Users**: Organisation Admins, Event Managers, Staff Coordinators, Artist Managers, Volunteer Coordinators, Super Admins (platform management via `/platform/*`).
|
||||
|
||||
**Features**:
|
||||
- Event lifecycle management (Draft through Closed)
|
||||
- Festival Sections, Time Slots, Shift planning
|
||||
- Person & Crowd management (Crew, Volunteers, Artists, Guests, Press, Partners, Suppliers)
|
||||
- Accreditation engine (categories, items, access zones)
|
||||
- Artist booking & advancing
|
||||
- Timetable & stage management
|
||||
- Briefing builder & communication hub
|
||||
- Mission Control (show day operations)
|
||||
- Form builder with conditional logic
|
||||
- Supplier & production management
|
||||
- Reporting & insights
|
||||
- Platform admin: organisation management, billing, platform users (`/platform/*` routes, `super_admin` only)
|
||||
|
||||
**Vuexy Version**: `typescript-version/full-version` (customized navigation)
|
||||
|
||||
---
|
||||
|
||||
### Portal (`apps/portal/`)
|
||||
|
||||
**Purpose**: External-facing portal with two access modes.
|
||||
|
||||
**Users**: Volunteers, Crew (login-based), Artists, Suppliers, Press (token-based).
|
||||
|
||||
**Access Modes**:
|
||||
|
||||
| User | Access Mode | Rationale |
|
||||
|------|-------------|-----------|
|
||||
| Volunteer / Crew | Login (`auth:sanctum`) | Long-term relationship, festival passport, shift history |
|
||||
| Artist / Tour Manager | Token (`portal.token` middleware) | Per-event, advance portal via signed URL |
|
||||
| Supplier / Partner | Token (`portal.token` middleware) | Per-event, production request via token |
|
||||
| Press / Media | Token (`portal.token` middleware) | Per-event accreditation, no recurring relationship |
|
||||
|
||||
**Router guard logic**: If `route.query.token` -> token mode. If `authStore.isAuthenticated` -> login mode. Otherwise -> redirect to `/login`.
|
||||
|
||||
**Vuexy Version**: `typescript-version/starter-kit` (stripped: no sidebar, no customizer, no dark mode toggle; uses Vuetify components + Vuexy SCSS)
|
||||
|
||||
---
|
||||
|
||||
## Multi-Tenant Data Model
|
||||
|
||||
Shared database schema with organisation scoping on all tables. No row-level security at DB level; scoping enforced via Laravel Policies and Eloquent Global Scopes.
|
||||
|
||||
**Scoping Rule**: EVERY query on event data MUST have an `organisation_id` scope via `OrganisationScope` Global Scope.
|
||||
|
||||
**Tenancy Hierarchy**:
|
||||
|
||||
```
|
||||
Platform (Super Admin)
|
||||
└─ Organisation (client A)
|
||||
└─ Event (event 1)
|
||||
└─ Festival Section (Bar, Hospitality, Technical, ...)
|
||||
├─ Time Slots (DAY1-EARLY-CREW, DAY1-EARLY-VOLUNTEER, ...)
|
||||
└─ Shifts (Bar x DAY1-EARLY-VOLUNTEER, 5 slots)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Three-Level Role & Permission Model
|
||||
|
||||
Managed via Spatie `laravel-permission` with team-based permissions.
|
||||
|
||||
| Level | Scope | Roles | Implementation |
|
||||
|-------|-------|-------|----------------|
|
||||
| App Level | Whole platform | `super_admin`, `support_agent` | Spatie role |
|
||||
| Organisation Level | Within one org | `org_admin`, `org_member`, `org_readonly` | Spatie team = organisation |
|
||||
| Event Level | Within one event | `event_manager`, `artist_manager`, `staff_coordinator`, `volunteer_coordinator`, `accreditation_officer` | `event_user_roles` pivot table |
|
||||
|
||||
**Middleware**: `OrganisationRoleMiddleware` and `EventRoleMiddleware` check per route.
|
||||
|
||||
---
|
||||
|
||||
## Event Lifecycle
|
||||
|
||||
| Phase | Description |
|
||||
|-------|-------------|
|
||||
| `draft` | Created but not published. Only admin sees it. |
|
||||
| `published` | Active in planning. Internal modules available. External portals closed. |
|
||||
| `registration_open` | Volunteer registration and artist advance portals open. |
|
||||
| `buildup` | Setup days. Crew shifts begin. Accreditation distribution starts. |
|
||||
| `showday` | Active event days. Mission Control active. Real-time check-in. |
|
||||
| `teardown` | Breakdown days. Inventory return. Shift closure. |
|
||||
| `closed` | Event completed. Read-only. Reports available. |
|
||||
|
||||
---
|
||||
|
||||
## API Structure
|
||||
|
||||
### Base URL
|
||||
- Development: `http://localhost:8000/api/v1`
|
||||
|
||||
### Route Groups
|
||||
|
||||
```
|
||||
# Public (no auth)
|
||||
POST /auth/login
|
||||
POST /portal/token-auth Token-based portal access
|
||||
POST /portal/form-submit Public form submission
|
||||
|
||||
# Protected (auth:sanctum)
|
||||
POST /auth/logout
|
||||
GET /auth/me Returns user + organisations + event roles
|
||||
|
||||
# Organisations
|
||||
GET/POST /organisations
|
||||
GET/PUT /organisations/{id}
|
||||
POST /organisations/{id}/invite
|
||||
GET /organisations/{id}/members
|
||||
|
||||
# Events (nested under organisations)
|
||||
GET/POST /organisations/{org}/events
|
||||
GET/PUT/DELETE /events/{id}
|
||||
PUT /events/{id}/status
|
||||
|
||||
# Festival Sections
|
||||
GET/POST /events/{event}/sections
|
||||
GET/PUT/DELETE /sections/{id}
|
||||
GET /sections/{id}/dashboard
|
||||
|
||||
# Time Slots
|
||||
GET/POST /events/{event}/time-slots
|
||||
PUT/DELETE /time-slots/{id}
|
||||
|
||||
# Shifts
|
||||
GET/POST /sections/{section}/shifts
|
||||
PUT/DELETE /shifts/{id}
|
||||
POST /shifts/{id}/assign
|
||||
POST /shifts/{id}/claim Volunteer self-service
|
||||
|
||||
# Persons
|
||||
GET/POST /events/{event}/persons
|
||||
GET/PUT /persons/{id}
|
||||
POST /persons/{id}/approve
|
||||
POST /persons/{id}/checkin
|
||||
|
||||
# Crowd Types & Lists
|
||||
GET/POST /organisations/{org}/crowd-types
|
||||
GET/POST /events/{event}/crowd-lists
|
||||
|
||||
# Artists & Advancing
|
||||
GET/POST /events/{event}/artists
|
||||
GET/PUT /artists/{id}
|
||||
GET/POST /artists/{id}/sections Advance sections
|
||||
POST /sections/{id}/submit Advance submission
|
||||
|
||||
# Accreditation
|
||||
GET/POST /events/{event}/accreditation-items
|
||||
POST /persons/{id}/accreditations
|
||||
GET/POST /events/{event}/access-zones
|
||||
|
||||
# Briefings & Communication
|
||||
GET/POST /events/{event}/briefings
|
||||
POST /briefings/{id}/send
|
||||
GET/POST /events/{event}/campaigns
|
||||
POST /campaigns/{id}/send
|
||||
|
||||
# Mission Control
|
||||
GET /events/{event}/mission-control
|
||||
POST /persons/{id}/checkin-item
|
||||
|
||||
# Scanners & Inventory
|
||||
GET/POST /events/{event}/scanners
|
||||
POST /scan
|
||||
GET/POST /events/{event}/inventory
|
||||
|
||||
# Reports
|
||||
GET /events/{event}/reports/{type}
|
||||
|
||||
# Portal (token-based, portal.token middleware)
|
||||
GET /portal/artist
|
||||
POST /portal/advancing
|
||||
GET /portal/supplier
|
||||
POST /portal/production-request
|
||||
```
|
||||
|
||||
### API Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { ... },
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"per_page": 15,
|
||||
"total": 100,
|
||||
"last_page": 7
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Database Schema
|
||||
|
||||
**Primary Keys**: ULID on all business tables via `HasUlids` trait. Pure pivot tables use auto-increment integer PK.
|
||||
|
||||
**Soft Deletes ON**: organisations, events, festival_sections, shifts, shift_assignments, persons, artists, companies, production_requests.
|
||||
|
||||
**Soft Deletes OFF** (audit records): check_ins, briefing_sends, message_replies, shift_waitlist, volunteer_festival_history.
|
||||
|
||||
**JSON Columns**: ONLY for opaque config (blocks, fields, settings, items). NEVER for dates, status values, foreign keys, booleans, or anything filtered/sorted/aggregated.
|
||||
|
||||
### 1. Foundation
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `users` | id (ULID), name, email, password, timezone, locale, avatar, deleted_at | Platform-wide, unique per email. |
|
||||
| `organisations` | id (ULID), name, slug, billing_status, settings (JSON: display prefs only), deleted_at | hasMany events, crowd_types. |
|
||||
| `organisation_user` | id (int AI), user_id, organisation_id, role | Pivot. Integer PK. |
|
||||
| `user_invitations` | id (ULID), email, invited_by_user_id, organisation_id, event_id (nullable), role, token (ULID unique), status, expires_at | INDEX: (token), (email, status). |
|
||||
| `events` | id (ULID), organisation_id, name, slug, start_date, end_date, timezone, status (enum), deleted_at | INDEX: (organisation_id, status). |
|
||||
| `event_user_roles` | id (int AI), user_id, event_id, role | Pivot. Integer PK. |
|
||||
|
||||
### 2. Locations
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `locations` | id (ULID), event_id, name, address, lat, lng, description, access_instructions | INDEX: (event_id). |
|
||||
|
||||
### 3. Festival Sections, Time Slots & Shifts
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `festival_sections` | id (ULID), event_id, name, sort_order, deleted_at | INDEX: (event_id, sort_order). |
|
||||
| `time_slots` | id (ULID), event_id, name, person_type (CREW/VOLUNTEER/PRESS/...), date, start_time, end_time | INDEX: (event_id, person_type, date). |
|
||||
| `shifts` | id (ULID), festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, status, deleted_at | INDEX: (festival_section_id, time_slot_id). |
|
||||
| `shift_assignments` | id (ULID), shift_id, person_id, time_slot_id (denormalized), status (pending_approval/approved/rejected/cancelled/completed), auto_approved, deleted_at | UNIQUE(person_id, time_slot_id). |
|
||||
| `volunteer_availabilities` | id (ULID), person_id, time_slot_id, submitted_at | UNIQUE(person_id, time_slot_id). |
|
||||
| `shift_waitlist` | id (ULID), shift_id, person_id, position, added_at | UNIQUE(shift_id, person_id). |
|
||||
| `shift_swap_requests` | id (ULID), from_assignment_id, to_person_id, status, auto_approved | |
|
||||
| `shift_absences` | id (ULID), shift_assignment_id, person_id, reason, status | |
|
||||
|
||||
### 4. Volunteer Profile & History
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `volunteer_profiles` | id (ULID), user_id (unique), bio, tshirt_size, first_aid, driving_licence, reliability_score (0.00-5.00) | Platform-wide, 1:1 with users. |
|
||||
| `volunteer_festival_history` | id (ULID), user_id, event_id, hours_planned, hours_completed, no_show_count, coordinator_rating, would_reinvite | UNIQUE(user_id, event_id). Never visible to volunteer. |
|
||||
| `post_festival_evaluations` | id (ULID), event_id, person_id, overall_rating, would_return, feedback_text | |
|
||||
| `festival_retrospectives` | id (ULID), event_id (unique), KPI columns, top_feedback (JSON) | |
|
||||
|
||||
### 5. Crowd Types, Persons & Crowd Lists
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `crowd_types` | id (ULID), organisation_id, name, system_type (CREW/GUEST/ARTIST/VOLUNTEER/PRESS/PARTNER/SUPPLIER), color, icon | Org-level config. |
|
||||
| `persons` | id (ULID), user_id (nullable), event_id, crowd_type_id, company_id (nullable), name, email, phone, status, is_blacklisted, custom_fields (JSON), deleted_at | user_id nullable for externals. UNIQUE(event_id, user_id) WHERE user_id IS NOT NULL. |
|
||||
| `companies` | id (ULID), organisation_id, name, type, contact_*, deleted_at | Shared across events within org. |
|
||||
| `crowd_lists` | id (ULID), event_id, crowd_type_id, name, type (internal/external), auto_approve, max_persons | |
|
||||
| `crowd_list_persons` | id (int AI), crowd_list_id, person_id | Pivot. |
|
||||
|
||||
### 6. Accreditation Engine
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `accreditation_categories` | id (ULID), organisation_id, name, sort_order, icon | Org-level. |
|
||||
| `accreditation_items` | id (ULID), accreditation_category_id, name, is_date_dependent, barcode_type, cost_price | Org-level items. |
|
||||
| `event_accreditation_items` | id (ULID), event_id, accreditation_item_id, max_quantity_per_person, is_active | Activates item per event. UNIQUE(event_id, accreditation_item_id). |
|
||||
| `accreditation_assignments` | id (ULID), person_id, accreditation_item_id, event_id, date, quantity, is_handed_out | |
|
||||
| `access_zones` | id (ULID), event_id, name, zone_code (unique per event) | |
|
||||
| `access_zone_days` | id (int AI), access_zone_id, day_date | UNIQUE(access_zone_id, day_date). |
|
||||
| `person_access_zones` | id (int AI), person_id, access_zone_id, valid_from, valid_to | |
|
||||
|
||||
### 7. Artists & Advancing
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `artists` | id (ULID), event_id, name, booking_status (concept/requested/option/confirmed/contracted/cancelled), portal_token (ULID unique), deleted_at | |
|
||||
| `performances` | id (ULID), artist_id, stage_id, date, start_time, end_time, check_in_status | INDEX: (stage_id, date, start_time). |
|
||||
| `stages` | id (ULID), event_id, name, color, capacity | |
|
||||
| `stage_days` | id (int AI), stage_id, day_date | UNIQUE(stage_id, day_date). |
|
||||
| `advance_sections` | id (ULID), artist_id, name, type, is_open, sort_order | |
|
||||
| `advance_submissions` | id (ULID), advance_section_id, data (JSON), status | |
|
||||
| `artist_contacts` | id (ULID), artist_id, name, email, role | |
|
||||
| `artist_riders` | id (ULID), artist_id, category (technical/hospitality), items (JSON) | |
|
||||
|
||||
### 8. Communication & Briefings
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `briefing_templates` | id (ULID), event_id, name, type, blocks (JSON) | |
|
||||
| `briefings` | id (ULID), event_id, briefing_template_id, name, target_crowd_types (JSON), status | |
|
||||
| `briefing_sends` | id (ULID), briefing_id, person_id, status (queued/sent/opened/downloaded) | NO soft delete. |
|
||||
| `communication_campaigns` | id (ULID), event_id, type (email/sms/whatsapp), status | |
|
||||
| `messages` | id (ULID), event_id, sender_user_id, recipient_person_id, urgency (normal/urgent/emergency) | |
|
||||
| `broadcast_messages` | id (ULID), event_id, sender_user_id, body, urgency | |
|
||||
|
||||
### 9. Forms, Check-In & Operational
|
||||
|
||||
| Table | Key Columns | Notes |
|
||||
|-------|-------------|-------|
|
||||
| `public_forms` | id (ULID), event_id, crowd_type_id, fields (JSON), conditional_logic (JSON), iframe_token | |
|
||||
| `form_submissions` | id (ULID), public_form_id, person_id, data (JSON) | |
|
||||
| `check_ins` | id (ULID), event_id, person_id, scanned_by_user_id, scanned_at | NO soft delete. Immutable audit record. |
|
||||
| `scanners` | id (ULID), event_id, name, type, pairing_code | |
|
||||
| `inventory_items` | id (ULID), event_id, name, item_code, assigned_to_person_id | |
|
||||
| `production_requests` | id (ULID), event_id, company_id, title, status, token (ULID unique) | |
|
||||
| `material_requests` | id (ULID), production_request_id, category, name, quantity, status | |
|
||||
|
||||
---
|
||||
|
||||
## Model Relationships
|
||||
|
||||
**User**
|
||||
- belongsToMany Organisations (via `organisation_user`)
|
||||
- belongsToMany Events (via `event_user_roles`)
|
||||
|
||||
**Organisation**
|
||||
- hasMany Events
|
||||
- hasMany CrowdTypes
|
||||
- hasMany AccreditationCategories
|
||||
- hasMany Companies
|
||||
- belongsToMany Users (via `organisation_user`)
|
||||
|
||||
**Event**
|
||||
- belongsTo Organisation
|
||||
- hasMany FestivalSections
|
||||
- hasMany TimeSlots
|
||||
- hasMany Persons
|
||||
- hasMany Artists
|
||||
- hasMany Briefings
|
||||
- hasMany Locations
|
||||
- hasMany AccessZones
|
||||
- hasMany PublicForms
|
||||
|
||||
**FestivalSection**
|
||||
- belongsTo Event
|
||||
- hasMany Shifts
|
||||
|
||||
**TimeSlot**
|
||||
- belongsTo Event
|
||||
- hasMany Shifts
|
||||
- hasMany ShiftAssignments (denormalized)
|
||||
|
||||
**Shift**
|
||||
- belongsTo FestivalSection
|
||||
- belongsTo TimeSlot
|
||||
- belongsTo Location (nullable)
|
||||
- hasMany ShiftAssignments
|
||||
|
||||
**Person**
|
||||
- belongsTo Event
|
||||
- belongsTo CrowdType
|
||||
- belongsTo User (nullable)
|
||||
- belongsTo Company (nullable)
|
||||
- hasMany ShiftAssignments
|
||||
- hasMany AccreditationAssignments
|
||||
- hasMany CheckIns
|
||||
|
||||
**Artist**
|
||||
- belongsTo Event
|
||||
- hasMany Performances
|
||||
- hasMany AdvanceSections
|
||||
- hasMany ArtistContacts
|
||||
|
||||
---
|
||||
|
||||
## Security & CORS
|
||||
|
||||
Two frontend origins in `config/cors.php` (via env):
|
||||
|
||||
| App | Dev URL | Env Variable |
|
||||
|-----|---------|--------------|
|
||||
| App | `http://localhost:5174` | `FRONTEND_APP_URL` |
|
||||
| Portal | `http://localhost:5175` | `FRONTEND_PORTAL_URL` |
|
||||
|
||||
Production (registered domain **crewli.app**): API `https://api.crewli.app` (`APP_URL`); SPAs `https://crewli.app`, `https://portal.crewli.app` via the same env keys. Frontends use `VITE_API_URL=https://api.crewli.app/api/v1`. `SANCTUM_STATEFUL_DOMAINS` = comma-separated SPA hostnames only (e.g. `crewli.app,portal.crewli.app`). **`crewli.nl`** is reserved for a future marketing site only — not used for this application stack.
|
||||
|
||||
---
|
||||
|
||||
## Real-time Events (WebSocket)
|
||||
|
||||
Via Laravel Echo + Pusher/Soketi:
|
||||
- `PersonCheckedIn`
|
||||
- `ShiftFillRateChanged`
|
||||
- `ArtistCheckInStatusChanged`
|
||||
- `AdvanceSectionSubmitted`
|
||||
- `AccreditationItemHandedOut`
|
||||
- `BriefingSendQueued`
|
||||
|
||||
---
|
||||
|
||||
*Source: Crewli Design Document v1.3, March 2026*
|
||||
@@ -1,222 +0,0 @@
|
||||
# Crewli - Cursor AI Instructions
|
||||
|
||||
> Multi-tenant SaaS platform for event- and festival management.
|
||||
> Design Document: `/resources/design/design-document.md`
|
||||
> Dev Guide: `/resources/design/dev-guide.md`
|
||||
> Start Guide: `/resources/design/start-guide.md`
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Name**: Crewli
|
||||
**Type**: Multi-tenant SaaS platform (API-first architecture)
|
||||
**Status**: Development
|
||||
|
||||
### Description
|
||||
|
||||
Crewli is a multi-tenant SaaS platform for professional event and festival management. It supports the full operational cycle: artist booking and advancing, staff planning and volunteer management, accreditation, briefings, and real-time show-day operations (Mission Control). Built for a professional volunteer organisation, with SaaS expansion potential.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Component | Technology | Location | Port |
|
||||
|-----------|------------|----------|------|
|
||||
| API | Laravel 12 + Sanctum + Spatie Permission | `api/` | 8000 |
|
||||
| Organizer + Admin App (Main) | Vue 3 + Vuexy (full) | `apps/app/` | 5174 |
|
||||
| Portal (External) | Vue 3 + Vuexy (stripped) | `apps/portal/` | 5175 |
|
||||
| Database | MySQL 8 | Docker | 3306 |
|
||||
| Cache / Queues | Redis | Docker | 6379 |
|
||||
| Mail | Mailpit | Docker | 8025 |
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
```
|
||||
.cursor/
|
||||
├── instructions.md # This file - overview and quick start
|
||||
├── ARCHITECTURE.md # System architecture, schema, API routes
|
||||
└── rules/
|
||||
├── 001_workspace.mdc # Project structure, conventions, multi-tenancy
|
||||
├── 100_laravel.mdc # Laravel API patterns and templates
|
||||
├── 101_vue.mdc # Vue + Vuexy patterns and templates
|
||||
└── 200_testing.mdc # Testing strategies and templates
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Modules
|
||||
|
||||
### Phase 1 - Foundation
|
||||
- [ ] Multi-tenant architecture + Auth (Sanctum + Spatie)
|
||||
- [ ] Users, Roles & Permissions (three-level model)
|
||||
- [ ] Organisations CRUD + User Invitations
|
||||
- [ ] Events CRUD with lifecycle status
|
||||
- [ ] Crowd Types (org-level configuration)
|
||||
- [ ] Festival Sections + Time Slots + Shifts
|
||||
- [ ] Persons & Crowd Lists
|
||||
- [ ] Accreditation Engine (categories, items, access zones)
|
||||
|
||||
### Phase 2 - Core Operations
|
||||
- [ ] Briefings & Communication (template builder, queue-based sending)
|
||||
- [ ] Staff & Crew Management (crowd pool, accreditation matrix)
|
||||
- [ ] Volunteer Management + Portal (registration, shift claiming, approval flow)
|
||||
- [ ] Form Builder (drag-drop, conditional logic, iframe embed)
|
||||
- [ ] Artist Advancing + Portal (token-based access)
|
||||
- [ ] Timetable & Stage management
|
||||
- [ ] Show Day Mode
|
||||
- [ ] Shift Swap & Waitlist
|
||||
- [ ] Volunteer Profile + Festival Passport
|
||||
- [ ] Communication Hub (email/SMS/WhatsApp via Zender, urgency levels)
|
||||
|
||||
### Phase 3 - Advancing & Show Day
|
||||
- [ ] Guests & Hospitality
|
||||
- [ ] Suppliers & Production (production requests, supplier portal)
|
||||
- [ ] Mission Control (real-time check-in, artist handling, scanner management)
|
||||
- [ ] Communication Campaigns (email + SMS batch)
|
||||
- [ ] Allocation Sheet PDF (Browsershot)
|
||||
- [ ] Scan infrastructure (hardware pairing)
|
||||
- [ ] Reporting & Insights
|
||||
- [ ] No-show automation
|
||||
- [ ] Post-festival evaluation + retrospective
|
||||
|
||||
### Phase 4 - Differentiators
|
||||
- [ ] Real-time WebSocket notifications (Echo + Pusher/Soketi)
|
||||
- [ ] Cross-event crew pool with reliability score
|
||||
- [ ] Global search (cmd+K)
|
||||
- [ ] Crew PWA
|
||||
- [ ] Public REST API + webhook system
|
||||
- [ ] CO2/sustainability reporting
|
||||
|
||||
---
|
||||
|
||||
## Module Development Order (per module)
|
||||
|
||||
Always follow this sequence:
|
||||
|
||||
1. Migration(s) - ULID PKs, composite indexes, constrained FKs
|
||||
2. Eloquent Model - HasUlids, relations, scopes, OrganisationScope
|
||||
3. Factory - realistic Dutch test data
|
||||
4. Policy - authorization via Spatie roles
|
||||
5. Form Request(s) - Store + Update validation
|
||||
6. API Resource - computed fields, `whenLoaded()`, permission-dependent fields
|
||||
7. Resource Controller - index/show/store/update/destroy
|
||||
8. Routes in `api.php`
|
||||
9. PHPUnit Feature Test - happy path (200/201) + unauthenticated (401) + wrong organisation (403) + validation (422)
|
||||
10. Vue Composable (`useModuleName.ts`) - TanStack Query
|
||||
11. Pinia Store (if cross-component state needed)
|
||||
12. Vue Page Component
|
||||
13. Vue Router entry
|
||||
|
||||
---
|
||||
|
||||
## Getting Started Prompts
|
||||
|
||||
### 1. Phase 1 Foundation (Backend)
|
||||
|
||||
```
|
||||
Read CLAUDE.md. Then generate Phase 1 Foundation:
|
||||
|
||||
1. Migrations: Update users (add timezone, locale, deleted_at). Create organisations (ULID, name, slug, billing_status, settings JSON, deleted_at), organisation_user pivot, user_invitations, events (ULID, organisation_id, name, slug, start_date, end_date, timezone, status enum, deleted_at), event_user_roles pivot.
|
||||
2. Models: User (update), Organisation, UserInvitation, Event. All with HasUlids, SoftDeletes where applicable, OrganisationScope on Event.
|
||||
3. Spatie Permission: RoleSeeder with roles: super_admin, org_admin, org_member, event_manager, staff_coordinator, volunteer_coordinator.
|
||||
4. Auth: LoginController, LogoutController, MeController (returns user + organisations + active event roles).
|
||||
5. Organisations: Controller, Policy, Request, Resource.
|
||||
6. Events: Controller nested under organisations, Policy, Request, Resource.
|
||||
7. Feature tests per step. Run php artisan test after each step.
|
||||
```
|
||||
|
||||
### 2. Phase 1 Foundation (Frontend)
|
||||
|
||||
```
|
||||
Build auth flow in apps/app/:
|
||||
1. stores/useAuthStore.ts - token storage, isAuthenticated, me() loading
|
||||
2. pages/login.vue - use Vuexy login layout
|
||||
3. Router guard - redirect to login if not authenticated
|
||||
4. Replace Vuexy demo navigation with Crewli structure
|
||||
5. CASL permissions: connect to Spatie roles from auth/me response
|
||||
```
|
||||
|
||||
### 3. Module Generation (example: Shifts)
|
||||
|
||||
```
|
||||
Build the Shifts module following CLAUDE.md module order:
|
||||
- Migration with ULID PK, festival_section_id, time_slot_id, location_id, slots_total, slots_open_for_claiming, status. Composite indexes.
|
||||
- Model with HasUlids, SoftDeletes, relations, computed accessors (slots_filled, fill_rate).
|
||||
- shift_assignments with denormalized time_slot_id, status machine (pending_approval > approved/rejected/cancelled/completed), UNIQUE(person_id, time_slot_id).
|
||||
- Configurable auto-approve per shift.
|
||||
- Queued notification jobs using ZenderService for WhatsApp.
|
||||
- Feature tests covering 200/401/403/422.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Add a New API Endpoint
|
||||
|
||||
1. Create/update Controller in `app/Http/Controllers/Api/V1/`
|
||||
2. Create Form Request in `app/Http/Requests/Api/V1/`
|
||||
3. Create/update API Resource in `app/Http/Resources/Api/V1/`
|
||||
4. Add route in `routes/api.php`
|
||||
5. Create Service class if complex business logic needed
|
||||
6. Write PHPUnit Feature Test (200/401/403/422)
|
||||
|
||||
### Add a New Vue Page
|
||||
|
||||
1. Create page component in `src/pages/`
|
||||
2. Route added automatically by file-based routing (or add to router)
|
||||
3. Add navigation item in `src/navigation/`
|
||||
4. Create composable for API calls in `src/composables/`
|
||||
5. Use Vuexy/Vuetify components for UI
|
||||
|
||||
### Add a New Database Table
|
||||
|
||||
1. Create migration with ULID PK, composite indexes
|
||||
2. Create model with HasUlids, relations, OrganisationScope (if applicable)
|
||||
3. Create factory with realistic Dutch test data
|
||||
4. Create Policy, Form Request, Resource, Controller
|
||||
5. Register routes in `api.php`
|
||||
6. Write PHPUnit Feature Test
|
||||
|
||||
---
|
||||
|
||||
## Code Generation Preferences
|
||||
|
||||
When generating code, always:
|
||||
|
||||
- Use PHP 8.2+ features (typed properties, enums, match, readonly)
|
||||
- Use `declare(strict_types=1);`
|
||||
- Use ULID primary keys via HasUlids trait
|
||||
- Use Spatie laravel-permission for roles (never hardcode role strings)
|
||||
- Scope all queries on `organisation_id` via Global Scope
|
||||
- Use `<script setup lang="ts">` for Vue components
|
||||
- Use TanStack Query for all API calls
|
||||
- Use VeeValidate + Zod for form validation
|
||||
- Use Vuetify/Vuexy components for UI (never custom CSS if Vuetify class exists)
|
||||
|
||||
---
|
||||
|
||||
## Environment Setup
|
||||
|
||||
### Docker Services
|
||||
```bash
|
||||
make services # Start MySQL, Redis, Mailpit
|
||||
make services-stop # Stop services
|
||||
```
|
||||
|
||||
### Development Servers
|
||||
```bash
|
||||
make api # Laravel on :8000
|
||||
make app # Organizer + Admin SPA on :5174
|
||||
make portal # Portal SPA on :5175
|
||||
```
|
||||
|
||||
### Database
|
||||
```bash
|
||||
make migrate # Run migrations
|
||||
make fresh # Fresh migrate + seed
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
cd api && php artisan test # All tests
|
||||
cd api && php artisan test --filter=ShiftTest # Specific test
|
||||
cd api && php artisan test --coverage # With coverage
|
||||
```
|
||||
Reference in New Issue
Block a user