867 lines
30 KiB
Markdown
867 lines
30 KiB
Markdown
# 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*
|