- 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
133 lines
5.0 KiB
Markdown
133 lines
5.0 KiB
Markdown
# Crewli — Claude Code Instructions
|
|
|
|
## Project context
|
|
|
|
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
|
|
|
|
- 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 layout
|
|
|
|
- `api/` — Laravel backend
|
|
- `apps/admin/` — Super Admin SPA
|
|
- `apps/app/` — Organizer SPA (main product app)
|
|
- `apps/portal/` — External portal (volunteers, artists, suppliers, etc.)
|
|
|
|
## Apps and portal architecture
|
|
|
|
- `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
|
|
|
|
Configure three frontend origins in both Laravel (`config/cors.php` via env) and the Vite dev server proxy:
|
|
|
|
- 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
|
|
|
|
- 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
|
|
|
|
- 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()`)
|
|
|
|
### Models
|
|
|
|
- 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
|
|
|
|
- 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)
|
|
|
|
### Roles and permissions
|
|
|
|
- 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
|
|
|
|
- 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 rules (strict)
|
|
|
|
### Vue components
|
|
|
|
- Always `<script setup lang="ts">` — never the Options API
|
|
- Type props with `defineProps<{...}>()`
|
|
- Declare emits with `defineEmits<{...}>()`
|
|
|
|
### API calls
|
|
|
|
- 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
|
|
|
|
### Naming
|
|
|
|
- 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
|
|
|
|
- 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
|
|
|
|
## Forbidden patterns
|
|
|
|
- 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`)
|
|
|
|
## Order of work for each new module
|
|
|
|
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
|