refactor: align codebase with EventCrew domain and trim legacy band stack

- Update API: events, users, policies, routes, resources, migrations
- Remove deprecated models/resources (customers, setlists, invitations, etc.)
- Refresh admin app and docs; remove apps/band

Made-with: Cursor
This commit is contained in:
2026-03-29 23:19:06 +02:00
parent 34e12e00b3
commit 1cb7674d52
1034 changed files with 7453 additions and 8743 deletions

View File

@@ -1,223 +1,168 @@
---
description: Core workspace rules for Laravel + Vue/TypeScript full-stack application
description: Core workspace rules for EventCrew multi-tenant SaaS platform
globs: ["**/*"]
alwaysApply: true
---
# Workspace Rules
You are an expert full-stack developer working on a Laravel API backend with a Vue 3/TypeScript frontend using Vuexy admin template. This is an API-first architecture where the backend and frontend are completely separated.
You are an expert full-stack developer working on EventCrew, a multi-tenant SaaS platform for event and festival management. The backend is a Laravel 12 REST API (JSON only, no Blade), and three Vue 3 SPA frontends communicate via CORS + Sanctum tokens.
## Tech Stack
### Backend (Laravel)
- PHP 8.3+
- Laravel 12+
- Laravel Sanctum for SPA authentication (token-based)
- MySQL 8.0 database
- Redis for cache and queues
- Pest for testing
- PHP 8.2+
- Laravel 12
- Laravel Sanctum (SPA token auth)
- Spatie laravel-permission (three-level roles)
- Spatie laravel-activitylog (audit log)
- Spatie laravel-medialibrary (file management)
- MySQL 8 (primary), Redis (cache, queues, sessions)
- Laravel Horizon (queue monitoring)
- PHPUnit for testing
### Frontend (Vue)
- Vue 3 with TypeScript (strict mode)
- Vite as build tool
- Vuexy Admin Template
- TanStack Query (Vue Query) for server state
- Pinia for client state
- Vue Router for routing
- Axios for HTTP client
- TypeScript 5.9+
- Vue 3.5+ (Composition API, `<script setup>` only)
- Vite 7+
- Vuexy 9.5 + Vuetify 3.10
- Pinia 3 (client state)
- TanStack Query / Vue Query (server state)
- Axios (HTTP client)
- VeeValidate + Zod (form validation)
- VueDraggable (drag-and-drop)
- Vue I18n (internationalization)
## Project Structure
```
band-management/
├── api/ # Laravel 12 API
event-crew/
├── api/ # Laravel 12 REST API (JSON only)
│ ├── app/
│ │ ├── Actions/ # Single-purpose business logic
│ │ ├── Enums/ # PHP enums
│ │ ├── Http/
│ │ │ ├── Controllers/Api/V1/
│ │ │ ├── Middleware/
│ │ │ ├── Requests/ # Form Request validation
│ │ │ └── Resources/ # API Resources
│ │ ├── Models/ # Eloquent models
│ │ ├── Policies/ # Authorization
│ │ ├── Services/ # Complex business logic
│ │ ── Traits/ # Shared traits
│ │ │ ├── Middleware/ # OrganisationRoleMiddleware, EventRoleMiddleware, PortalTokenMiddleware
│ │ │ ├── Requests/Api/V1/ # Form Request validation
│ │ │ └── Resources/Api/V1/ # API Resources
│ │ ├── Models/ # Eloquent models with HasUlids
│ │ ├── Policies/ # Authorization (never hardcode roles)
│ │ ├── Services/ # Complex business logic
│ │ ── Events/ + Listeners/
│ │ └── Jobs/ # Queue jobs (briefings, PDF, notifications)
│ ├── database/
│ │ ├── factories/
│ │ ├── migrations/
│ │ ├── factories/
│ │ └── seeders/
── routes/
│ │ └── api.php # API routes
│ └── tests/
│ ├── Feature/Api/
│ └── Unit/
── tests/Feature/Api/V1/
├── apps/
│ ├── admin/ # Admin Dashboard (Vuexy full)
│ │ ── src/
│ │ ├── @core/ # Vuexy core (don't modify)
│ │ ├── @layouts/ # Vuexy layouts (don't modify)
│ │ ├── components/ # Custom components
│ │ ├── composables/ # Vue composables
│ │ ├── layouts/ # App layouts
│ │ ├── lib/ # Utilities (api-client, etc.)
│ │ ├── navigation/ # Menu configuration
│ │ ├── pages/ # Page components
│ │ ── plugins/ # Vue plugins
│ │ │ ├── router/ # Vue Router
│ │ │ ├── stores/ # Pinia stores
│ │ │ └── types/ # TypeScript types
│ │ └── ...
│ ├── admin/ # Super Admin SPA (Vuexy full)
│ │ ── src/
│ │ ├── @core/ # Vuexy core (NEVER modify)
│ │ ├── @layouts/ # Vuexy layouts (NEVER modify)
│ │ ├── components/
│ │ ├── composables/ # useModule.ts composables
│ │ ├── lib/ # axios.ts (single instance)
│ │ ├── pages/
│ │ ├── plugins/ # vue-query, casl, vuetify
│ │ ├── stores/ # Pinia stores
│ │ ── types/ # TypeScript interfaces
│ │
│ ├── band/ # Band Portal (Vuexy starter)
│ └── customers/ # Customer Portal (Vuexy starter)
│ ├── app/ # Organizer SPA (Vuexy full) - MAIN APP
│ └── src/ # Same structure as admin/
│ │
│ └── portal/ # External Portal SPA (Vuexy stripped)
│ └── src/ # No sidebar, no customizer, top-bar only
├── docker/ # Docker configurations
── docs/ # Documentation
└── .cursor/ # Cursor AI configuration
├── resources/design/ # Design documents (source of truth)
── .cursor/ # Cursor AI configuration
```
## Multi-Tenancy Rules (CRITICAL)
1. **EVERY query on event-data MUST scope on `organisation_id`** via `OrganisationScope` Eloquent Global Scope.
2. **Never use direct id-checks in controllers** - always use Policies.
3. **Never use `Model::all()` without a where-clause** - always scope.
4. **Never hardcode role strings** like `$user->role === 'admin'` - use `$user->hasRole()` and Policies.
## Naming Conventions
### PHP (Laravel)
| Type | Convention | Example |
|------|------------|---------|
| Models | Singular PascalCase | `Event`, `MusicNumber` |
| Controllers | PascalCase + Controller | `EventController` |
| Form Requests | Action + Resource + Request | `StoreEventRequest` |
| Resources | Resource + Resource | `EventResource` |
| Actions | Verb + Resource + Action | `CreateEventAction` |
| Models | Singular PascalCase | `Event`, `FestivalSection`, `ShiftAssignment` |
| Controllers | PascalCase + Controller | `EventController`, `ShiftController` |
| Form Requests | Action + Resource + Request | `StoreEventRequest`, `UpdateShiftRequest` |
| Resources | Resource + Resource | `EventResource`, `PersonResource` |
| Services | PascalCase + Service | `ZenderService`, `BriefingService` |
| Migrations | snake_case with timestamp | `create_events_table` |
| Tables | Plural snake_case | `events`, `music_numbers` |
| Columns | snake_case | `event_date`, `created_at` |
| Enums | Singular PascalCase | `EventStatus` |
| Tables | Plural snake_case | `events`, `festival_sections`, `shift_assignments` |
| Columns | snake_case | `organisation_id`, `slots_total`, `created_at` |
| Enums | Singular PascalCase | `EventStatus`, `BookingStatus` |
### TypeScript (Vue)
| Type | Convention | Example |
|------|------------|---------|
| Components | PascalCase | `EventCard.vue` |
| Pages | PascalCase + Page | `EventsPage.vue` |
| Composables | camelCase with "use" | `useEvents.ts` |
| Stores | camelCase | `authStore.ts` |
| Types/Interfaces | PascalCase | `Event`, `ApiResponse` |
| Files | kebab-case or camelCase | `api-client.ts` |
| Components | PascalCase | `ShiftAssignPanel.vue`, `PersonCard.vue` |
| Composables | use-prefix camelCase | `useShifts.ts`, `usePersons.ts` |
| Pinia Stores | use-prefix + Store suffix | `useEventStore.ts`, `useAuthStore.ts` |
| Types/Interfaces | PascalCase | `Event`, `Person`, `ShiftAssignment` |
| Variables | camelCase | `slotsFilled`, `fillRate` |
## Code Style
### General Principles
1. **Explicit over implicit** - Be clear about types, returns, and intentions
2. **Small, focused units** - Each file/function does one thing well
3. **Consistent formatting** - Use automated formatters
4. **Descriptive names** - Names should explain purpose
5. **No magic** - Avoid hidden behavior
### PHP
- Use `declare(strict_types=1);` in all files
- Use `final` for classes that shouldn't be extended
- Use readonly properties where applicable
- Prefer named arguments for clarity
- Use enums instead of string constants
### TypeScript
- Enable strict mode in tsconfig
- No `any` types - use `unknown` if truly unknown
- Use interface for objects, type for unions/primitives
- Prefer `const` over `let`
- Use optional chaining and nullish coalescing
### Database
- Primary keys: ULID via `HasUlids` trait (NOT UUID v4, NOT auto-increment for business tables)
- Pure pivot tables: auto-increment integer PK for join performance
- DB columns: `snake_case`
## Environment Configuration
### Development URLs
| Service | URL |
|---------|-----|
| API | http://localhost:8000/api/v1 |
| Admin SPA | http://localhost:5173 |
| Band SPA | http://localhost:5174 |
| Customer SPA | http://localhost:5175 |
| MySQL | localhost:3306 |
| Redis | localhost:6379 |
| Mailpit | http://localhost:8025 |
| Service | URL | Env Variable |
|---------|-----|--------------|
| API | `http://localhost:8000/api/v1` | - |
| Admin SPA | `http://localhost:5173` | `FRONTEND_ADMIN_URL` |
| Organizer SPA | `http://localhost:5174` | `FRONTEND_APP_URL` |
| Portal SPA | `http://localhost:5175` | `FRONTEND_PORTAL_URL` |
| MySQL | `localhost:3306` | - |
| Redis | `localhost:6379` | - |
| Mailpit | `http://localhost:8025` | - |
### Database Credentials (Development)
```
Host: 127.0.0.1
Port: 3306
Database: band_management
Username: band_management
Password: secret
```
### Production URLs
| Service | URL |
|---------|-----|
| API | https://api.bandmanagement.nl |
| Admin | https://admin.bandmanagement.nl |
| Band | https://band.bandmanagement.nl |
| Customers | https://customers.bandmanagement.nl |
### CORS
Three frontend origins configured in `config/cors.php` via env variables. Each Vite dev server gets its own port for CORS isolation.
## Git Conventions
### Branch Names
- `feature/event-management`
- `fix/rsvp-validation`
- `refactor/auth-system`
- `feature/shift-planning`
- `fix/organisation-scoping`
- `refactor/accreditation-engine`
### Commit Messages
```
feat: add event RSVP functionality
fix: correct date validation in events
refactor: extract event creation to action class
docs: update API documentation
test: add event controller tests
feat: add shift claiming with approval flow
fix: enforce organisation scope on persons query
refactor: extract briefing logic to BriefingService
test: add accreditation assignment tests
```
## Dependencies
## Forbidden Patterns
### PHP (api/composer.json)
- PHP 8.3+
- Laravel 12
- Laravel Sanctum
- Laravel Pint (formatting)
- Pest PHP (testing)
### Node (apps/*/package.json)
- Vue 3.4+
- TypeScript 5.3+
- Vite 5+
- Pinia
- @tanstack/vue-query
- axios
- NEVER: `$user->role === 'admin'` (use Policies + Spatie roles)
- NEVER: `Model::all()` without where-clause (always scope)
- NEVER: `dd()` or `var_dump()` left in code
- NEVER: Hardcode `.env` values in code
- NEVER: JSON columns for queryable/filterable data
- NEVER: UUID v4 as primary key (use HasUlids for ULID)
- NEVER: Blade views or Inertia (API-only backend)
- NEVER: Business logic in controllers without Policy authorization
## Code Style Principles
1. **Readability over cleverness** - Write code that is easy to understand
1. **Explicit over implicit** - Be clear about types, returns, and intentions
2. **Single Responsibility** - Each class/function does one thing well
3. **Type Safety** - Leverage TypeScript and PHP type hints everywhere
4. **Testability** - Write code that is easy to test
5. **API Consistency** - Follow RESTful conventions
## Response Format
When generating code:
1. Always include proper type hints/annotations
2. Add brief comments for complex logic only
3. Follow the established patterns in the codebase
4. Consider error handling and edge cases
5. Suggest tests for new functionality
## Communication Style
- Be concise and direct
- Provide working code examples
- Explain architectural decisions briefly
- Ask clarifying questions only when truly ambiguous
3. **Type Safety** - PHP type hints and TypeScript strict mode everywhere
4. **Multi-tenant first** - Every feature must respect organisation boundaries
5. **Mobile-first** - Responsive design, minimum 375px width