Files
crewli/CLAUDE.md
bert.hausmans 0d24506c89 feat: consolidate frontend API layer, add query-client, and harden backend Fase 1
Frontend:
- Consolidate duplicate API layers into single src/lib/axios.ts per app
- Remove src/lib/api-client.ts and src/utils/api.ts (admin)
- Add src/lib/query-client.ts with TanStack Query config per app
- Update all imports and auto-import config

Backend:
- Fix organisations.billing_status default to 'trial'
- Fix user_invitations.invited_by_user_id to nullOnDelete
- Add MeResource with separated app_roles and pivot-based org roles
- Add cross-org check to EventPolicy view() and update()
- Restrict EventPolicy create/update to org_admin/event_manager (not org_member)
- Attach creator as org_admin on organisation store
- Add query scopes to Event and UserInvitation models
- Improve factories with Dutch test data
- Expand test suite from 29 to 41 tests (90 assertions)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:35:34 +02:00

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/design-document.md`
## 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