# Band Management - Architecture > This document describes the system architecture, design decisions, and patterns used in this application. ## System Overview ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ INTERNET │ └─────────────────────────────────────────────────────────────────────────┘ │ ┌───────────────────────────┼───────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Admin SPA │ │ Band SPA │ │ Customer SPA │ │ (Vuexy Full) │ │ (Vuexy Lite) │ │ (Vuexy Lite) │ │ :5173 │ │ :5174 │ │ :5175 │ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │ │ │ └───────────────────────────┼───────────────────────────┘ │ ▼ ┌───────────────────────┐ │ Laravel API │ │ (Sanctum) │ │ :8000 │ └───────────┬───────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ MySQL │ │ Redis │ │ Mailpit │ │ :3306 │ │ :6379 │ │ :8025 │ └───────────┘ └───────────┘ └───────────┘ ``` --- ## Applications ### Admin Dashboard (`apps/admin/`) **Purpose**: Full management interface for band administrators. **Users**: Band leaders, managers, booking agents **Features**: - Member management (CRUD, roles, invitations) - Event/gig management (calendar, list, RSVP tracking) - Music catalog (songs, attachments, metadata) - Setlist builder (drag-drop, templates) - Location management (venues, contacts) - Customer CRM (companies, individuals, history) - Booking request management - Reports and analytics **Vuexy Version**: `typescript-version/full-version` --- ### Band Portal (`apps/band/`) **Purpose**: Member-facing interface for band members. **Users**: Musicians, performers, crew **Features**: - Personal dashboard (upcoming gigs) - Event calendar with RSVP - View setlists and music - Download attachments (lyrics, charts) - Profile settings - Notifications **Vuexy Version**: `typescript-version/starter-kit` --- ### Customer Portal (`apps/customers/`) **Purpose**: Client-facing interface for customers. **Users**: Event organizers, venue managers, clients **Features**: - View booked events - Submit booking requests - Track request status - View assigned setlists (if permitted) - Profile settings **Vuexy Version**: `typescript-version/starter-kit` --- ## API Structure ### Base URL - Development: `http://localhost:8000/api/v1` - Production: `https://api.bandmanagement.nl/api/v1` ### Authentication Endpoints ``` POST /auth/register Register new user POST /auth/login Login, returns token POST /auth/logout Logout, revokes token GET /auth/user Get authenticated user POST /auth/forgot-password Request password reset POST /auth/reset-password Reset password with token ``` ### Resource Endpoints ``` # Events GET /events List events (paginated, filterable) POST /events Create event GET /events/{id} Get event details PUT /events/{id} Update event DELETE /events/{id} Delete event POST /events/{id}/invite Invite members to event GET /events/{id}/invitations Get event invitations POST /events/{id}/rsvp Submit RSVP response POST /events/{id}/duplicate Duplicate event # Members GET /members List members POST /members Create member GET /members/{id} Get member details PUT /members/{id} Update member DELETE /members/{id} Delete/deactivate member POST /members/invite Send invitation email # Music GET /music List music numbers POST /music Create music number GET /music/{id} Get music number PUT /music/{id} Update music number DELETE /music/{id} Delete music number POST /music/{id}/attachments Upload attachment DELETE /music/{id}/attachments/{aid} Delete attachment # Setlists GET /setlists List setlists POST /setlists Create setlist GET /setlists/{id} Get setlist with items PUT /setlists/{id} Update setlist DELETE /setlists/{id} Delete setlist POST /setlists/{id}/clone Clone setlist PUT /setlists/{id}/items Reorder/update items # Locations GET /locations List locations POST /locations Create location GET /locations/{id} Get location PUT /locations/{id} Update location DELETE /locations/{id} Delete location # Customers GET /customers List customers POST /customers Create customer GET /customers/{id} Get customer PUT /customers/{id} Update customer DELETE /customers/{id} Delete customer # Booking Requests (Customer Portal) GET /booking-requests List user's requests POST /booking-requests Submit booking request GET /booking-requests/{id} Get request details # Notifications GET /notifications List notifications PUT /notifications/{id}/read Mark as read POST /notifications/read-all Mark all as read ``` --- ## Database Schema ### Entity Relationship Diagram ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ users │ │ customers │ │ locations │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ id (ULID) │──┐ │ id (ULID) │ │ id (ULID) │ │ name │ │ │ user_id (FK) │───────│ name │ │ email │ │ │ name │ │ address │ │ type │ │ │ company_name │ │ city │ │ role │ │ │ type │ │ capacity │ │ status │ │ │ email │ │ contact_* │ └──────────────┘ │ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ ▼ │ ▼ ▼ ┌──────────────┐ │ ┌──────────────┐ ┌──────────────┐ │event_invites │ │ │ events │───────│ setlists │ ├──────────────┤ │ ├──────────────┤ ├──────────────┤ │ id (ULID) │ │ │ id (ULID) │ │ id (ULID) │ │ event_id(FK) │──┼───▶│ title │ │ name │ │ user_id (FK) │──┘ │ location_id │ │ description │ │ rsvp_status │ │ customer_id │ │ is_template │ │ rsvp_note │ │ setlist_id │◀──────│ is_archived │ └──────────────┘ │ event_date │ └──────────────┘ │ status │ │ │ created_by │ │ └──────────────┘ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │setlist_items │ │music_numbers │───────│music_attach │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ id (ULID) │ │ id (ULID) │ │ id (ULID) │ │ setlist_id │ │ title │ │ music_num_id │ │ music_num_id │ │ artist │ │ file_name │ │ position │ │ duration │ │ file_type │ │ set_number │ │ key, tempo │ │ file_path │ │ is_break │ │ tags (JSON) │ └──────────────┘ └──────────────┘ └──────────────┘ ``` ### Table Definitions #### users | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | name | string | Full name | | email | string | Unique email | | email_verified_at | timestamp | Email verification | | password | string | Hashed password | | phone | string? | Phone number | | bio | text? | Biography | | instruments | json? | Array of instruments | | avatar_path | string? | Avatar file path | | type | enum | `member`, `customer` | | role | enum? | `admin`, `booking_agent`, `music_manager`, `member` | | status | enum | `active`, `inactive` | | invited_at | timestamp? | When invited | | last_login_at | timestamp? | Last login | | remember_token | string? | Remember me token | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### customers | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | user_id | ULID? | FK to users (for portal access) | | name | string | Contact name | | company_name | string? | Company name | | type | enum | `individual`, `company` | | email | string? | Email | | phone | string? | Phone | | address | string? | Street address | | city | string? | City | | postal_code | string? | Postal code | | country | string | Country (default: NL) | | notes | text? | Internal notes | | is_portal_enabled | boolean | Can access portal | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### locations | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | name | string | Venue name | | address | string | Street address | | city | string | City | | postal_code | string? | Postal code | | country | string | Country (default: NL) | | latitude | decimal? | GPS latitude | | longitude | decimal? | GPS longitude | | capacity | integer? | Max capacity | | contact_name | string? | Contact person | | contact_email | string? | Contact email | | contact_phone | string? | Contact phone | | stage_specs | text? | Stage specifications | | technical_notes | text? | Technical requirements | | parking_info | text? | Parking information | | notes | text? | General notes | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### events | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | title | string | Event title | | description | text? | Description | | location_id | ULID? | FK to locations | | customer_id | ULID? | FK to customers | | setlist_id | ULID? | FK to setlists | | event_date | date | Date of event | | start_time | time | Start time | | end_time | time? | End time | | load_in_time | time? | Load-in time | | soundcheck_time | time? | Soundcheck time | | fee | decimal(10,2)? | Payment amount | | currency | string | Currency (default: EUR) | | status | enum | `draft`, `pending`, `confirmed`, `completed`, `cancelled` | | visibility | enum | `private`, `members`, `public` | | rsvp_deadline | datetime? | RSVP deadline | | notes | text? | Public notes | | internal_notes | text? | Admin-only notes | | is_public_setlist | boolean | Show setlist to customer | | created_by | ULID | FK to users | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### event_invitations | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | event_id | ULID | FK to events | | user_id | ULID | FK to users | | rsvp_status | enum | `pending`, `available`, `unavailable`, `tentative` | | rsvp_note | text? | Response note | | rsvp_responded_at | timestamp? | When responded | | invited_at | timestamp | When invited | | reminder_sent_at | timestamp? | Last reminder | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### music_numbers | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | title | string | Song title | | artist | string? | Original artist | | genre | string? | Genre/style | | duration_seconds | integer? | Duration in seconds | | key | string? | Musical key (e.g., "Am", "G") | | tempo_bpm | integer? | Tempo in BPM | | time_signature | string? | Time signature (e.g., "4/4") | | lyrics | text? | Full lyrics | | notes | text? | Performance notes | | tags | json? | Array of tags | | play_count | integer | Times played (default: 0) | | last_played_at | timestamp? | Last performed | | is_active | boolean | Active in catalog | | created_by | ULID? | FK to users | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### music_attachments | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | music_number_id | ULID | FK to music_numbers | | file_name | string | Stored filename | | original_name | string | Original filename | | file_path | string | Storage path | | file_type | enum | `lyrics`, `chords`, `sheet_music`, `audio`, `other` | | file_size | integer | Size in bytes | | mime_type | string | MIME type | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### setlists | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | name | string | Setlist name | | description | text? | Description | | total_duration_seconds | integer? | Calculated total | | is_template | boolean | Is a template | | is_archived | boolean | Archived | | created_by | ULID? | FK to users | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### setlist_items | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | setlist_id | ULID | FK to setlists | | music_number_id | ULID? | FK to music_numbers | | position | integer | Order position | | set_number | integer | Set number (1, 2, 3) | | is_break | boolean | Is a break | | break_duration_seconds | integer? | Break length | | notes | string? | Item notes | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### booking_requests | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | customer_id | ULID | FK to customers | | event_date | date | Requested date | | start_time | time? | Requested start | | end_time | time? | Requested end | | location_name | string? | Venue name | | location_address | string? | Venue address | | event_type | string? | Type of event | | expected_guests | integer? | Guest count | | message | text? | Request message | | status | enum | `pending`, `reviewed`, `accepted`, `declined` | | admin_notes | text? | Admin notes | | event_id | ULID? | FK to created event | | reviewed_by | ULID? | FK to users | | reviewed_at | timestamp? | When reviewed | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### notifications | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | user_id | ULID | FK to users | | type | string | Notification type | | title | string | Title | | message | text | Message body | | data | json? | Additional data | | action_url | string? | Link URL | | read_at | timestamp? | When read | | created_at | timestamp | Created | | updated_at | timestamp | Updated | #### activity_logs | Column | Type | Description | |--------|------|-------------| | id | ULID | Primary key | | user_id | ULID? | FK to users | | loggable_type | string | Model class | | loggable_id | ULID | Model ID | | action | string | Action performed | | description | text? | Description | | changes | json? | Before/after data | | ip_address | string? | Client IP | | user_agent | string? | Browser info | | created_at | timestamp | Created | --- ## API Response Format ### Success Response ```json { "success": true, "data": { ... }, "message": "Event created successfully", "meta": { "pagination": { "current_page": 1, "per_page": 15, "total": 100, "last_page": 7, "from": 1, "to": 15 } } } ``` ### Error Response ```json { "success": false, "message": "Validation failed", "errors": { "title": ["The title field is required."], "event_date": ["The event date must be a future date."] } } ``` ### Single Resource ```json { "success": true, "data": { "id": "01HQ3K5P7X...", "title": "Summer Concert", "event_date": "2025-07-15", "status": "confirmed", "location": { "id": "01HQ3K5P7X...", "name": "City Park Amphitheater" }, "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z" } } ``` ### Collection (Paginated) ```json { "success": true, "data": [ { "id": "...", "title": "Event 1" }, { "id": "...", "title": "Event 2" } ], "meta": { "pagination": { "current_page": 1, "per_page": 15, "total": 45, "last_page": 3 } } } ``` --- ## User Roles & Permissions ### Roles | Role | Description | |------|-------------| | `admin` | Full access to everything | | `booking_agent` | Manage events, locations, customers | | `music_manager` | Manage music catalog and setlists | | `member` | View events, RSVP, view music | ### Permissions Matrix | Resource | Admin | Booking Agent | Music Manager | Member | |----------|-------|---------------|---------------|--------| | Members | CRUD | Read | Read | Read | | Events | CRUD | CRUD | Read | Read | | Locations | CRUD | CRUD | Read | Read | | Customers | CRUD | CRUD | Read | - | | Music | CRUD | Read | CRUD | Read | | Setlists | CRUD | Read | CRUD | Read | | Booking Requests | CRUD | CRUD | - | - | | RSVP | All | All | Own | Own | --- ## File Storage ### Structure ``` storage/app/ ├── public/ │ ├── avatars/ # User avatars │ └── music/ # Music attachments │ ├── lyrics/ │ ├── chords/ │ ├── sheet_music/ │ └── audio/ └── private/ └── exports/ # Generated reports ``` ### File Types Allowed | Type | Extensions | Max Size | |------|------------|----------| | Avatar | jpg, png, webp | 2 MB | | Lyrics | txt, pdf, docx | 5 MB | | Chords | pdf, png, jpg | 10 MB | | Sheet Music | pdf, png, jpg | 10 MB | | Audio | mp3, wav, m4a | 50 MB | --- ## Architectural Decisions ### ADR-001: API-First Architecture **Status**: Accepted **Date**: 2025-01-01 **Context**: We need to build a web application with three separate SPAs (Admin, Band, Customers) that may have mobile clients in the future. **Decision**: Implement a completely separated frontend and backend communicating via RESTful JSON API. **Consequences**: - ✅ Frontend and backend can be developed/deployed independently - ✅ Easy to add mobile or other clients later - ✅ Clear API contracts - ✅ Better scalability options - ⚠️ More complex initial setup - ⚠️ Requires CORS configuration --- ### ADR-002: Laravel Sanctum for Authentication **Status**: Accepted **Date**: 2025-01-01 **Context**: Need authentication for SPAs that's secure and simple to implement. **Decision**: Use Laravel Sanctum with token-based authentication for the SPAs. **Alternatives Considered**: - **Passport**: Too complex for our needs (OAuth2 overkill for first-party SPA) - **JWT**: Requires token storage in localStorage (XSS vulnerable) **Consequences**: - ✅ Simple token-based auth for multiple SPAs - ✅ Built into Laravel, minimal setup - ✅ Works well with separate domains - ⚠️ Need to handle token storage securely --- ### ADR-003: TanStack Query for Server State **Status**: Accepted **Date**: 2025-01-01 **Context**: Need efficient data fetching with caching, background updates, and optimistic updates. **Decision**: Use TanStack Query (Vue Query) for all server state management. **Consequences**: - ✅ Automatic caching and deduplication - ✅ Built-in loading/error states - ✅ Background refetching - ✅ Optimistic updates for better UX - ✅ DevTools for debugging - ⚠️ Learning curve for query key management --- ### ADR-004: Action Pattern for Business Logic **Status**: Accepted **Date**: 2025-01-01 **Context**: Controllers should be thin, and business logic needs to be reusable and testable. **Decision**: Use single-responsibility Action classes for all business logic. **Pattern**: ```php class CreateEventAction { public function execute(array $data): Event { // Business logic here } } // Usage in controller public function store(Request $request, CreateEventAction $action) { return $action->execute($request->validated()); } ``` **Consequences**: - ✅ Single Responsibility Principle - ✅ Easy to test in isolation - ✅ Reusable across controllers, commands, jobs - ⚠️ More files to manage --- ### ADR-005: Vuexy for All SPAs **Status**: Accepted **Date**: 2025-01-01 **Context**: Need consistent UI across three SPAs with professional admin components. **Decision**: Use Vuexy Vue template for all SPAs (full version for Admin, starter-kit for Band/Customers). **Consequences**: - ✅ Consistent UI/UX across all portals - ✅ Pre-built admin components - ✅ Single learning curve for the team - ✅ Professional look out of the box - ⚠️ License cost - ⚠️ Dependency on third-party template --- ## Security Architecture ### Defense in Depth ``` ┌─────────────────────────────────────────────────────────────┐ │ 1. Network Layer │ │ - HTTPS only │ │ - Rate limiting │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 2. Application Layer │ │ - CORS validation │ │ - Input validation (Form Requests) │ │ - SQL injection prevention (Eloquent) │ │ - XSS prevention (Vue escaping) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 3. Authentication & Authorization │ │ - Sanctum token authentication │ │ - Role-based access control │ │ - Resource authorization (Policies) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 4. Data Layer │ │ - Encrypted connections (TLS) │ │ - Sensitive data hashing (bcrypt) │ │ - Database credentials via environment │ └─────────────────────────────────────────────────────────────┘ ``` ### CORS Configuration ```php // config/cors.php 'allowed_origins' => [ 'http://localhost:5173', // Admin 'http://localhost:5174', // Band 'http://localhost:5175', // Customers ], 'supports_credentials' => true, ``` --- ## Performance Considerations ### Backend Optimizations 1. **Database** - Eager loading relationships (`with()`) - Database indexes on filtered/sorted columns - Query caching for expensive operations 2. **Caching Strategy** ```php // Cache expensive queries Cache::remember('stats:dashboard', 3600, fn() => $this->calculateStats() ); ``` 3. **Queue Heavy Operations** - Email sending - File processing - Report generation ### Frontend Optimizations 1. **Code Splitting** - Lazy load routes - Dynamic imports for heavy components 2. **Query Optimization** ```typescript // Deduplicate requests useQuery({ queryKey: ['events'], staleTime: 5 * 60 * 1000 }) // Prefetch on hover queryClient.prefetchQuery({ queryKey: ['event', id] }) ``` 3. **Bundle Size** - Tree shaking enabled - Dynamic imports for routes --- ## Monitoring & Observability ### Logging Strategy | Level | Use Case | Example | |-------|----------|---------| | DEBUG | Development only | Query details, variable dumps | | INFO | Normal operations | User login, API calls | | WARNING | Unexpected but handled | Rate limit approached | | ERROR | Errors requiring attention | Failed payment | | CRITICAL | System failures | Database down | ### Health Checks ``` GET /api/v1/health { "status": "healthy", "checks": { "database": "ok", "redis": "ok", "queue": "ok" } } ``` --- ## Model Relationships **User** - User has many EventInvitations - User has many Notifications - User has many ActivityLogs - User has many created Events (as creator) - User has many created MusicNumbers (as creator) - User has many created Setlists (as creator) **Event** - Event belongs to Location (nullable) - Event belongs to Customer (nullable) - Event belongs to Setlist (nullable) - Event belongs to User (created_by) - Event has many EventInvitations - Event has many Users through EventInvitations (invited members) **EventInvitation** - EventInvitation belongs to Event - EventInvitation belongs to User **Location** - Location has many Events **Customer** - Customer belongs to User (nullable, for portal access) - Customer has many Events - Customer has many BookingRequests **MusicNumber** - MusicNumber belongs to User (created_by, nullable) - MusicNumber has many MusicAttachments - MusicNumber has many SetlistItems - MusicNumber has many Setlists through SetlistItems **MusicAttachment** - MusicAttachment belongs to MusicNumber **Setlist** - Setlist belongs to User (created_by, nullable) - Setlist has many SetlistItems - Setlist has many MusicNumbers through SetlistItems - Setlist has many Events **SetlistItem** - SetlistItem belongs to Setlist - SetlistItem belongs to MusicNumber (nullable, null when is_break = true) **BookingRequest** - BookingRequest belongs to Customer - BookingRequest belongs to Event (nullable, when accepted) - BookingRequest belongs to User (reviewed_by, nullable) **Notification** - Notification belongs to User **ActivityLog** - ActivityLog belongs to User (nullable) - ActivityLog is polymorphic (loggable_type, loggable_id) --- *Last updated: 2025-01-01*