The admin SPA (apps/admin/) has been retired. Its functionality now lives in apps/app/ under /platform/* routes for super_admin users. Updated all documentation to reflect: 2 SPAs instead of 3, removed FRONTEND_ADMIN_URL/port 5173 references, changed production URL from app.crewli.app to crewli.app. Retired admin-specific security audit findings (A13-2, A13-4, A13-5, A13-7) and APPS-01 backlog item. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
295 lines
13 KiB
Markdown
295 lines
13 KiB
Markdown
# Crewli — Claude Code Master Prompt
|
|
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
|
|
|
|
## MANDATORY PREAMBLE — READ BEFORE DOING ANYTHING
|
|
|
|
Read `/CLAUDE.md` and `/dev-docs/SCHEMA.md` in full before starting. These are
|
|
your single source of truth. Do not deviate from them. Do not make assumptions
|
|
about the database schema — always verify against SCHEMA.md. Also read
|
|
`/dev-docs/API.md` for the existing API contract.
|
|
|
|
If the task involves a module that already has existing code, read that code
|
|
first. Understand the current state before making changes.
|
|
|
|
## PROJECT CONTEXT
|
|
|
|
Crewli is a multi-tenant SaaS platform for event and festival management.
|
|
|
|
- **Backend:** Laravel 12 REST API (no Blade views, no Inertia), Sanctum auth,
|
|
Spatie Permission, MySQL 8, Redis
|
|
- **Frontend:** Two standalone Vue 3 + TypeScript SPAs on Vuexy 9.5 /
|
|
Vuetify 3.10 — `apps/app/` (port 5174), `apps/portal/` (port 5175)
|
|
- **State:** Pinia + TanStack Vue Query
|
|
- **Forms:** VeeValidate + Zod
|
|
- **API base path:** `/api/v1/`
|
|
- **Testing:** PHPUnit (backend), Vitest (frontend)
|
|
|
|
### Repository structure
|
|
|
|
```
|
|
crewli/
|
|
├── api/ # Laravel 12 backend
|
|
│ ├── app/
|
|
│ │ ├── Http/
|
|
│ │ │ ├── Controllers/Api/V1/
|
|
│ │ │ ├── Middleware/
|
|
│ │ │ └── Requests/ # Form Requests per endpoint
|
|
│ │ ├── Models/
|
|
│ │ ├── Policies/
|
|
│ │ ├── Services/ # Business logic (NOT in controllers)
|
|
│ │ ├── Enums/ # PHP Enums for all status/type fields
|
|
│ │ ├── Events/ + Listeners/
|
|
│ │ └── Jobs/ # Queued jobs (briefings, notifications)
|
|
│ ├── database/
|
|
│ │ ├── migrations/
|
|
│ │ ├── factories/
|
|
│ │ └── seeders/
|
|
│ └── tests/Feature/Api/V1/
|
|
├── apps/
|
|
│ ├── app/ # Organizer + Platform Admin SPA (main app)
|
|
│ │ └── src/
|
|
│ │ ├── lib/axios.ts # THE ONLY axios instance
|
|
│ │ ├── composables/api/ # TanStack Query composables
|
|
│ │ ├── stores/ # Pinia stores
|
|
│ │ ├── types/ # TypeScript interfaces
|
|
│ │ └── pages/
|
|
│ └── portal/ # Dual-mode portal
|
|
├── dev-docs/ # Developer documentation (source of truth)
|
|
│ ├── SCHEMA.md
|
|
│ ├── API.md
|
|
│ ├── BACKLOG.md
|
|
│ ├── design-document.md
|
|
│ ├── dev-guide.md
|
|
│ └── start-guide.md
|
|
├── docs/ # VitePress end-user documentation (Dutch)
|
|
├── CLAUDE.md # This file's companion — workspace rules
|
|
└── .cursorrules
|
|
```
|
|
|
|
### Key architectural decisions (final, non-negotiable)
|
|
|
|
- **ULID** primary keys on all business tables via `HasUlids` — NEVER UUID v4
|
|
- **Integer auto-increment** on pure pivot tables only
|
|
- **JSON columns** exclusively for opaque config (settings, blocks) — never
|
|
for queryable data
|
|
- **Multi-tenancy** via `OrganisationScope` Eloquent Global Scope on all
|
|
event-related models
|
|
- **Soft delete** per table type as documented in SCHEMA.md — immutable audit
|
|
records (check_ins, form_submissions, briefing_sends) do NOT get soft delete
|
|
- **Portal dual-mode:** Sanctum session for volunteers/crew (persistent
|
|
identity), `portal.token` middleware for artists/suppliers/press
|
|
(event-specific, no account)
|
|
|
|
---
|
|
|
|
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
|
|
|
|
These rules are absolute. No workarounds. No "we'll fix it later". No partial
|
|
implementations. If something cannot be done properly, STOP and report it.
|
|
|
|
### Architecture & design
|
|
|
|
1. **ARCHITECTURE FIRST.** If the task requires a new pattern, data structure,
|
|
or integration that isn't documented in CLAUDE.md or SCHEMA.md — stop and
|
|
ask. Do not invent architecture. Write an Architecture Decision Record (ADR)
|
|
in `/dev-docs/decisions/` if a significant design choice is needed.
|
|
|
|
2. **DELETE OVER ADAPT.** If you find duplicate logic, conflicting patterns, or
|
|
legacy code that does the same thing differently: delete the worse version.
|
|
Do not build alongside it. Do not "wrap" it. If two implementations exist,
|
|
one must die. Consolidate to one source of truth.
|
|
|
|
3. **STRICT LAYERING.** Business logic belongs in `app/Services/`, NEVER in
|
|
controllers. Controllers handle HTTP concerns only: receive request, call
|
|
service, return resource. Data access patterns go through Eloquent models
|
|
with proper scopes. No "quick fixes" in the wrong layer.
|
|
- Controller → receives FormRequest, calls Service, returns API Resource
|
|
- Service → contains business logic, validation beyond FormRequest,
|
|
orchestration
|
|
- Model → relationships, scopes, accessors, mutators
|
|
- Job → async work dispatched by Service
|
|
|
|
4. **CONTRACT-FIRST.** Before implementing any module:
|
|
- Define the PHP Enum(s) in `app/Enums/` for all status/type fields
|
|
- Define the API Resource (response shape) first
|
|
- Define the Form Request (input validation) first
|
|
- Then implement the Service and Controller
|
|
Types and contracts define behaviour. Implementation follows.
|
|
|
|
5. **CONSISTENCY OVER CLEVERNESS.** Use the same pattern for every similar
|
|
problem. If existing modules use a specific approach for pagination, error
|
|
handling, or resource loading — use that exact same approach. Never
|
|
introduce a "better" alternative pattern without refactoring ALL existing
|
|
code to match. One pattern per problem type, across the entire codebase.
|
|
|
|
6. **SINGLE SOURCE OF TRUTH.** Every piece of information exists in exactly
|
|
one place:
|
|
- Enum values → PHP Enum class (not string literals)
|
|
- Validation rules → Form Request (not duplicated in frontend)
|
|
- Response shape → API Resource (not ad-hoc arrays)
|
|
- Config values → .env / config files (not hardcoded)
|
|
- Schema definition → SCHEMA.md (not guessed)
|
|
|
|
### Code quality
|
|
|
|
7. **NO TODO / FIXME / HACK.** Zero tolerance. If you cannot complete
|
|
something, stop and report it. Do not leave stubs, placeholders, or
|
|
"implement later" comments. Every file you touch must be production-ready.
|
|
|
|
8. **NO UNTYPED CODE.** PHP: `declare(strict_types=1)` on every file. Return
|
|
types on all methods. Typed properties. Use PHP Enums (not string literals)
|
|
for all status, type, and role fields. No `mixed` where a concrete type
|
|
or union type would work.
|
|
|
|
9. **NO GENERIC NAMES.** Names must be specific and self-documenting:
|
|
- ❌ `DataService`, `Helper`, `Manager`, `handleData()`, `processItem()`
|
|
- ✅ `ShiftAssignmentService`, `VolunteerAvailabilityChecker`,
|
|
`resolveShiftConflict()`, `calculateFillRate()`
|
|
|
|
10. **NO SILENT ERROR HANDLING.** No empty catch blocks. No `catch { return
|
|
null; }`. Every error must be: logged (via `Log::error()` with context),
|
|
or rethrown, or handled with a proper API error response. Use
|
|
`report($e)` for unexpected errors.
|
|
|
|
### Testing
|
|
|
|
11. **TESTS ARE DESIGN, NOT AFTERTHOUGHT.** Tests define expected behaviour.
|
|
Every controller needs feature tests covering:
|
|
- 200/201 (happy path for each action)
|
|
- 401 (unauthenticated access)
|
|
- 403 (wrong organisation — cross-tenant access attempt)
|
|
- 403 (insufficient role/permission)
|
|
- 422 (validation errors with specific field assertions)
|
|
- Edge cases specific to the module (e.g., shift conflict, capacity full)
|
|
Run `php artisan test` after EVERY module. Fix failures before proceeding.
|
|
Never skip, comment out, or mark tests as incomplete.
|
|
|
|
12. **FACTORIES ARE REALISTIC.** Use realistic Dutch test data (names,
|
|
addresses, company names) that reflects actual usage. Factories must create
|
|
valid, complete records — no missing required fields or placeholder values.
|
|
|
|
### Data & persistence
|
|
|
|
13. **DATA MODEL IS SACRED.** Never deviate from SCHEMA.md. Every column,
|
|
every constraint, every index documented there must be in the migration.
|
|
If SCHEMA.md is unclear, ask — do not guess.
|
|
|
|
14. **EVERY MIGRATION HAS down().** The `down()` method must cleanly reverse
|
|
the `up()`. Drop tables, remove columns, restore previous state. No
|
|
`down()` methods that throw or do nothing.
|
|
|
|
15. **INDEXES ARE MANDATORY.** Every foreign key column, every column used in
|
|
WHERE/ORDER BY clauses, every unique constraint from SCHEMA.md must have
|
|
an explicit index. Verify composite indexes match the documented patterns.
|
|
|
|
### Security & multi-tenancy
|
|
|
|
16. **NO UNSCOPED QUERIES.** Every query on event-related models must be
|
|
scoped to `organisation_id` via `OrganisationScope`. Mental test: can
|
|
User A from Org 1 ever see, modify, or infer the existence of data from
|
|
Org 2? If yes → security bug → fix immediately.
|
|
|
|
17. **POLICIES ARE COMPLETE.** Every policy method checks:
|
|
- Does the user belong to the correct organisation?
|
|
- Does the user have the required Spatie role/permission for this action?
|
|
- No `return true` placeholders. No missing methods.
|
|
|
|
18. **FORM REQUESTS ARE COMPLETE.** Every store/update has a Form Request
|
|
with full validation matching SCHEMA.md constraints: required, nullable,
|
|
max length, enum values (referencing the PHP Enum), exists rules with
|
|
proper scoping (e.g., `exists:events,id` scoped to organisation).
|
|
|
|
### API responses
|
|
|
|
19. **NO BARE MODEL RETURNS.** Every API response goes through an API
|
|
Resource. Never `return $model`, `$model->toArray()`, or raw arrays.
|
|
Resources define the public contract. Computed fields (fill_rate,
|
|
status_label, slot counts) belong in the Resource.
|
|
|
|
20. **CONSISTENT RESPONSE STRUCTURE.** Follow the existing pattern:
|
|
`{ data: {...}, meta: {...} }` for paginated lists. Consistent error
|
|
format with `{ message: "...", errors: {...} }` for validation failures.
|
|
|
|
### Resilience & operations
|
|
|
|
21. **IDEMPOTENT OPERATIONS.** Every queued job must be safe to retry.
|
|
Check-before-act: verify state hasn't changed. Use database transactions
|
|
for multi-step mutations. Queued notifications and external API calls
|
|
(Zender SMS/WhatsApp) must handle duplicates gracefully.
|
|
|
|
22. **OBSERVABILITY.** Log significant business events using
|
|
`spatie/laravel-activitylog` (already installed). Every create, update,
|
|
delete, and status change on business entities must be logged with:
|
|
- `causedBy($user)` — who did it
|
|
- `performedOn($model)` — what was affected
|
|
- `withProperties([...])` — relevant context (old values, new values)
|
|
|
|
23. **API VERSIONING.** All routes under `/api/v1/`. Controllers in
|
|
`App\Http\Controllers\Api\V1\`. Never introduce breaking changes to
|
|
existing endpoints — add new fields, don't rename or remove.
|
|
|
|
### Process
|
|
|
|
24. **MODULE GENERATION ORDER.** Always follow this sequence. No skipping.
|
|
1. PHP Enum(s) for status/type fields (`app/Enums/`)
|
|
2. Migration(s) — verify against SCHEMA.md
|
|
3. Eloquent Model with: HasUlids, HasFactory, SoftDeletes (if documented),
|
|
OrganisationScope (if event-related), relationships, scopes, accessors
|
|
4. Factory with realistic Dutch test data
|
|
5. Service class for business logic (`app/Services/`)
|
|
6. Policy for authorisation
|
|
7. Form Request(s) for validation
|
|
8. API Resource for response transformation
|
|
9. Resource Controller (thin — delegates to Service)
|
|
10. Routes in `api/routes/api.php`
|
|
11. Feature tests — run them, fix failures
|
|
12. Activity log integration in Service methods
|
|
13. Update `/dev-docs/API.md` with new routes
|
|
|
|
25. **GIT.** Auto-commit after each completed module:
|
|
`feat(module-name): add backend scaffold with tests`
|
|
|
|
---
|
|
|
|
## VERIFICATION CHECKLIST (run before reporting "done")
|
|
|
|
```bash
|
|
# All tests pass
|
|
php artisan test
|
|
|
|
# Database rebuilds cleanly
|
|
php artisan migrate:fresh --seed
|
|
|
|
# New routes are visible
|
|
php artisan route:list --path=api/v1
|
|
|
|
# No forbidden patterns
|
|
grep -rn "TODO\|FIXME\|HACK\|dd(\|dump(\|var_dump\|Model::all()" \
|
|
api/app/ api/tests/ --include="*.php"
|
|
|
|
# No UUID v4 in migrations
|
|
grep -rn "uuid(" api/database/migrations/ --include="*.php"
|
|
|
|
# Static analysis (if configured)
|
|
./vendor/bin/phpstan analyse
|
|
```
|
|
|
|
Manual verification:
|
|
- [ ] Every new model has: HasUlids, HasFactory, correct SoftDeletes, complete
|
|
$fillable, all relationships from SCHEMA.md
|
|
- [ ] Every new model with event data has OrganisationScope
|
|
- [ ] Every controller action is covered by a Policy method
|
|
- [ ] Every Service method logs activity via spatie/laravel-activitylog
|
|
- [ ] Every Form Request references PHP Enums (not string literals) for
|
|
enum validation
|
|
- [ ] Business logic is in Service classes, not in Controllers
|
|
- [ ] API Resource includes computed fields where applicable
|
|
- [ ] No N+1: index actions use `with()` for all accessed relationships
|
|
- [ ] `/dev-docs/API.md` updated with new routes
|
|
|
|
---
|
|
|
|
## TASK
|
|
|
|
[INSERT SPECIFIC TASK HERE]
|