# Backend Theme API Implementation Guide

## CONTEXT
Frontend has implemented complete theming system for venue canvas (2000+ lines). Themes control colors, typography, geometry, spacing, animations, effects. Frontend can load themes from JSON files, localStorage, or API. Backend needs to provide CRUD API and database storage.

## WHAT EXISTS (FRONTEND)
- Complete Zod validation schemas (types.ts)
- 3 preset themes: system, customer-light, customer-dark
- Theme loader utilities: loadThemeFromAPI(), loadThemeFromJSON(), loadThemeWithFallback()
- VenueThemeProvider React context
- ThemeSwitcher dev tool component
- All canvas components integrated (seats, tables, sections, stage, grid)
- Example JSON themes: broadway-theater.json, modern-minimal.json

## WHAT NEEDS BUILDING (BACKEND)

### DATABASE SCHEMA
Migration: 2025_01_21_000001_create_venue_themes_table.php (CREATED)

**Table: venue_themes**
- id (bigint, primary key)
- name (string, indexed) - "Broadway Theater"
- theme_id (string, unique) - "broadway-theater" (slug for API lookups)
- description (text, nullable)
- config (json) - Complete theme object with colors, typography, geometry, spacing, animations, effects
- is_global (boolean, default false, indexed) - System-wide presets
- is_active (boolean, default true, indexed) - Can deactivate without deletion
- venue_id (bigint, nullable, indexed, foreign key to venues.id, cascade delete)
- event_id (bigint, nullable, indexed, foreign key to events.id, cascade delete)
- author (string, nullable)
- version (string, default '1.0.0')
- mode (string, default 'custom', indexed) - 'system', 'customer', 'custom'
- created_at, updated_at (timestamps)
- deleted_at (timestamp, nullable) - Soft deletes

**Indexes:**
- is_global, is_active (composite)
- venue_id, is_active (composite)
- event_id, is_active (composite)

**Foreign Keys:**
- events.theme_id -> venue_themes.id (set null on delete)
- venues.default_theme_id -> venue_themes.id (set null on delete)

### MODEL
File: app/Models/VenueTheme.php (CREATED)

**Fillable:** name, theme_id, description, config, is_global, is_active, venue_id, event_id, author, version, mode
**Casts:** config => 'array', is_global => 'boolean', is_active => 'boolean', timestamps
**SoftDeletes:** Yes
**Relationships:** belongsTo(Venue), belongsTo(Event), hasMany(Event, 'theme_id'), hasMany(Venue, 'default_theme_id')

**Scopes:**
- global() - where('is_global', true)
- active() - where('is_active', true)
- forVenue($id) - where('venue_id', $id)
- forEvent($id) - where('event_id', $id)
- byMode($mode) - where('mode', $mode)

**Key Methods:**
- getFullConfigAttribute() - Returns complete theme object with metadata (id, name, description, mode, version, author, createdAt, updatedAt merged into config JSON)
- validateConfig() - Basic validation (checks required keys: colors, typography, geometry, spacing, animations, effects)
- duplicate($newName, $newThemeId) - Clone theme with new name/ID
- resolveThemeForEvent($eventId) - Static method returns: Event theme > Venue theme > Global theme (first active)
- isSystemPreset() - is_global && mode === 'system'
- isVenueSpecific() - !is_null(venue_id)
- isEventSpecific() - !is_null(event_id)

### CONTROLLER
File: app/Http/Controllers/ThemeController.php (CREATED)

**Routes Required:**

PUBLIC (no auth):
- GET /api/themes - List themes with filters (is_global, is_active, venue_id, event_id, mode, with_relations)
- GET /api/themes/{id} - Get by database ID
- GET /api/themes/by-id/{theme_id} - Get by theme_id string (e.g., "broadway-theater")
- GET /api/themes/{id}/config - Get full config JSON (frontend-ready, includes metadata)
- GET /api/themes/resolve/event/{event_id} - Resolve theme using hierarchy (event > venue > global)

ADMIN (auth:sanctum middleware):
- POST /api/themes - Create theme (validates: name required, theme_id unique, config required array with structure validation)
- PUT/PATCH /api/themes/{id} - Update theme (partial updates allowed, re-validates config if changed)
- DELETE /api/themes/{id} - Soft delete (prevents deletion of system presets)
- POST /api/themes/{id}/duplicate - Clone theme with new name/theme_id

**Validation Rules:**
```php
// Create/Update
'name' => 'required|string|max:255',
'theme_id' => 'nullable|string|unique:venue_themes,theme_id|max:255', // auto-slug if not provided
'description' => 'nullable|string',
'config' => 'required|array', // Must have: colors, typography, geometry, spacing, animations, effects
'is_global' => 'boolean',
'is_active' => 'boolean',
'venue_id' => 'nullable|exists:venues,id',
'event_id' => 'nullable|exists:events,id',
'author' => 'nullable|string|max:255',
'version' => 'nullable|string|max:50',
'mode' => 'nullable|string|in:system,customer,custom'
```

**Config Structure Validation (Basic - Frontend does full Zod validation):**
Required top-level keys: colors, typography, geometry, spacing, animations, effects
Required colors: available, held, booked, unavailable, shadowSold, selected, hover, sectionBorder, sectionFill, tableBorder, tableFill, background, gridLines

**Response Format:**
```json
{
  "success": true,
  "message": "Theme created successfully",
  "data": { /* VenueTheme model */ },
  "count": 10 // for index endpoint
}
```

### ROUTES REGISTRATION
File: routes/api/themes.php (CREATED)

Add to RouteServiceProvider.php in mapApiRoutes() method:
```php
if (file_exists(base_path('routes/api/themes.php'))) {
    Route::group($apiRouteConfig, function () {
        require base_path('routes/api/themes.php');
    });
}
```

### MIGRATION STEPS
1. Run migration: `php artisan migrate` (creates venue_themes table, adds theme_id to events, adds default_theme_id to venues)
2. Seed system presets (OPTIONAL - frontend has presets built-in):
```php
VenueTheme::create([
    'name' => 'System View',
    'theme_id' => 'system',
    'description' => 'Functional theme for editing and administration',
    'config' => json_decode(file_get_contents(base_path('storage/themes/system.json')), true),
    'is_global' => true,
    'is_active' => true,
    'mode' => 'system',
    'version' => '1.0.0',
    'author' => 'Showprima'
]);
```

### FRONTEND INTEGRATION
Frontend will use these patterns:

**Load theme from API:**
```tsx
import { loadThemeFromAPI } from '@showprima/venue-editor';
const theme = await loadThemeFromAPI('/api/themes/3/config');
```

**Load theme for event:**
```tsx
const theme = await loadThemeFromAPI('/api/themes/resolve/event/5');
```

**Fallback chain (tries API, then JSON file, then preset):**
```tsx
const theme = await loadThemeWithFallback({
  apiUrl: '/api/themes/resolve/event/5',
  jsonPath: '/themes/default.json',
  fallbackPreset: 'system'
});
```

**Create theme (admin):**
```tsx
const response = await fetch('/api/themes', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
  body: JSON.stringify({
    name: 'My Custom Theme',
    theme_id: 'my-custom-theme',
    config: themeObject, // Full VenueTheme object
    mode: 'custom',
    is_active: true
  })
});
```

### THEME OBJECT STRUCTURE (JSON)
```json
{
  "id": "broadway-theater",
  "name": "Broadway Theater",
  "mode": "custom",
  "description": "Elegant gold and burgundy theme",
  "colors": {
    "available": "#C59849",
    "held": "#D4A574",
    "booked": "#8B1538",
    "unavailable": "#6B7280",
    "shadowSold": "#8B1538",
    "selected": "#1C3144",
    "hover": "#2C4154",
    "sectionBorder": "#8B7355",
    "sectionFill": "#C59849",
    "sectionFillOpacity": 0.15,
    "sectionLabel": "#1C1917",
    "tableBorder": "#8B7355",
    "tableFill": "#FEF3C7",
    "tableSelected": "#1C3144",
    "stageFill": "#1C1917",
    "stageBorder": "#8B7355",
    "background": "#1C1917",
    "gridLines": "#44403C",
    "tooltipBg": "#1C1917",
    "tooltipText": "#FEF3C7",
    "pricingSelected": "#C59849",
    "pricingUnselected": "#78716C"
  },
  "typography": {
    "sectionLabelFont": "Playfair Display, Georgia, serif",
    "sectionLabelSize": 24,
    "sectionLabelWeight": "bold",
    "tableLabelFont": "Playfair Display, Georgia, serif",
    "tableLabelSize": 16,
    "tableLabelWeight": "normal",
    "seatLabelFont": "Inter, system-ui, sans-serif",
    "seatLabelSize": 11,
    "seatLabelWeight": "normal",
    "tooltipFont": "Inter, system-ui, sans-serif",
    "tooltipSize": 13,
    "priceLabelFont": "Inter, system-ui, sans-serif",
    "priceLabelSize": 12,
    "priceLabelWeight": "bold"
  },
  "geometry": {
    "seatRadius": 10,
    "seatStrokeWidth": 2,
    "seatSelectedStrokeWidth": 4,
    "tableBorderWidth": 2,
    "tableCornerRadius": 8,
    "tableSelectedBorderWidth": 4,
    "sectionBorderWidth": 3,
    "sectionBorderDash": null,
    "gridLineWidth": 0,
    "gridSpacing": 0,
    "stageBorderWidth": 3
  },
  "spacing": {
    "labelPadding": 16,
    "tooltipPadding": 20,
    "sectionPadding": 40
  },
  "animations": {
    "enabled": true,
    "hoverScale": 1.2,
    "clickScale": 1.5,
    "hoverDuration": 200,
    "clickDuration": 250,
    "transitionEasing": "ease-in-out"
  },
  "effects": {
    "shadowEnabled": true,
    "shadowColor": "#000000",
    "shadowBlur": 15,
    "shadowOpacity": 0.4,
    "glowEnabled": true,
    "glowColor": "#C59849",
    "glowIntensity": 0.7
  },
  "version": "1.0.0",
  "author": "Showprima Design Team",
  "createdAt": "2025-01-21T00:00:00.000Z",
  "updatedAt": "2025-01-21T00:00:00.000Z"
}
```

### TESTING CHECKLIST
1. Create theme via POST /api/themes - verify theme_id auto-slugs if not provided
2. List all themes GET /api/themes - verify pagination, filters work
3. Get theme by ID GET /api/themes/3 - verify returns model
4. Get config GET /api/themes/3/config - verify returns full config with metadata
5. Get by theme_id GET /api/themes/by-id/broadway-theater - verify works
6. Resolve for event GET /api/themes/resolve/event/5 - verify hierarchy (event > venue > global)
7. Update theme PUT /api/themes/3 - verify partial updates work
8. Duplicate theme POST /api/themes/3/duplicate - verify clone created
9. Delete theme DELETE /api/themes/3 - verify soft delete
10. Prevent delete of system presets - verify error when deleting is_global=true && mode=system
11. Foreign key cascade - delete venue/event, verify theme.venue_id/event_id set to null
12. Validation - try creating theme with missing config.colors.available, verify error

### EXAMPLE API REQUESTS

**List all active global themes:**
```bash
curl http://127.0.0.1:8000/api/themes?is_global=true&is_active=true
```

**Get theme config for event 5:**
```bash
curl http://127.0.0.1:8000/api/themes/resolve/event/5
```

**Create new theme (admin):**
```bash
curl -X POST http://127.0.0.1:8000/api/themes \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Custom Theme",
    "theme_id": "my-custom-theme",
    "config": { /* full config object */ },
    "mode": "custom",
    "is_active": true
  }'
```

**Update theme:**
```bash
curl -X PUT http://127.0.0.1:8000/api/themes/3 \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "Updated Name", "is_active": false}'
```

**Duplicate theme:**
```bash
curl -X POST http://127.0.0.1:8000/api/themes/3/duplicate \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "Copied Theme", "theme_id": "copied-theme"}'
```

### ERROR HANDLING
- 404 when theme not found
- 422 when validation fails (invalid config structure, missing required colors)
- 403 when trying to delete system preset
- 401 when unauthorized for admin routes
- Return validation errors in format: {"success": false, "message": "...", "errors": [...]}

### CORS REQUIREMENTS
Frontend runs on localhost:3001 (admin), localhost:3002 (ticketing), localhost:3003 (public). Ensure CORS allows these origins or use wildcard for localhost development.

### PERFORMANCE NOTES
- Config column is JSON - ensure MySQL/PostgreSQL JSON query support if filtering by config properties
- Add indexes on is_global, is_active, venue_id, event_id as these are commonly filtered
- Consider caching resolveThemeForEvent() results (but not critical, this endpoint is called once per page load)

### SECURITY NOTES
- Only authenticated admins can create/update/delete themes
- Public endpoints (list, show, config, resolve) are intentionally public (themes are visual config, not sensitive)
- Validate config structure to prevent injection attacks (though JSON column handles this)
- Prevent deletion of system presets (is_global=true && mode=system)

### FILES CREATED (BACKEND)
- database/migrations/2025_01_21_000001_create_venue_themes_table.php
- app/Models/VenueTheme.php
- app/Http/Controllers/ThemeController.php
- routes/api/themes.php

### FILES TO MODIFY (BACKEND)
- app/Providers/RouteServiceProvider.php - Add themes.php route loading in mapApiRoutes()
- app/Models/Event.php - Add relationship: belongsTo(VenueTheme, 'theme_id')
- app/Models/Venue.php - Add relationship: belongsTo(VenueTheme, 'default_theme_id')

### FRONTEND REFERENCE FILES
- packages/venue-editor/src/theming/types.ts - Complete Zod schemas
- packages/venue-editor/src/theming/examples/broadway-theater.json - Example theme
- packages/venue-editor/src/theming/loaders.ts - API integration patterns
- packages/venue-editor/src/theming/README.md - Complete documentation (530 lines)
- packages/venue-editor/THEMING_CHANGELOG.md - Implementation summary

### NEXT STEPS AFTER API COMPLETE
1. Run migration: `php artisan migrate`
2. (Optional) Seed system presets
3. Test all endpoints with Postman/Insomnia
4. Verify CORS allows frontend origins
5. Deploy to staging
6. Frontend team will test integration with loadThemeFromAPI()
7. Build admin UI for theme management (separate task)

### ADMIN UI FUTURE WORK (NOT PART OF THIS HANDOFF)
- Theme CRUD interface
- Theme preview/comparison
- Color picker for theme editing
- Assign theme to event/venue
- Theme analytics (which themes are most used)

## COMPLETE - READY FOR IMPLEMENTATION
All files created. Only needs: (1) Add route loading to RouteServiceProvider, (2) Run migration, (3) Test endpoints. Estimated time: 2-3 hours including testing.
