Files
band-management/.cursor/ARCHITECTURE.md

30 KiB

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

{
  "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

{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "title": ["The title field is required."],
    "event_date": ["The event date must be a future date."]
  }
}

Single Resource

{
  "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)

{
  "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:

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

// 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

    // 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

    // 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