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,64 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Customer extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'name',
'company_name',
'type',
'email',
'phone',
'address',
'city',
'postal_code',
'country',
'notes',
'is_portal_enabled',
'user_id',
];
protected function casts(): array
{
return [
'is_portal_enabled' => 'boolean',
];
}
// Relationships
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function events(): HasMany
{
return $this->hasMany(Event::class);
}
// Helper methods
public function isCompany(): bool
{
return $this->type === 'company';
}
public function displayName(): string
{
return $this->company_name ?? $this->name;
}
}

View File

@@ -4,129 +4,52 @@ declare(strict_types=1);
namespace App\Models;
use App\Enums\EventStatus;
use App\Enums\EventVisibility;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
final class Event extends Model
{
use HasFactory;
use HasUlids;
use SoftDeletes;
protected $fillable = [
'title',
'description',
'location_id',
'customer_id',
'setlist_id',
'event_date',
'start_time',
'end_time',
'load_in_time',
'soundcheck_time',
'fee',
'currency',
'organisation_id',
'name',
'slug',
'start_date',
'end_date',
'timezone',
'status',
'visibility',
'rsvp_deadline',
'notes',
'internal_notes',
'is_public_setlist',
'created_by',
];
protected function casts(): array
{
return [
'event_date' => 'date',
'start_time' => 'datetime:H:i',
'end_time' => 'datetime:H:i',
'load_in_time' => 'datetime:H:i',
'soundcheck_time' => 'datetime:H:i',
'fee' => 'decimal:2',
'status' => EventStatus::class,
'visibility' => EventVisibility::class,
'rsvp_deadline' => 'datetime',
'is_public_setlist' => 'boolean',
'start_date' => 'date',
'end_date' => 'date',
];
}
// Relationships
public function location(): BelongsTo
public function organisation(): BelongsTo
{
return $this->belongsTo(Location::class);
return $this->belongsTo(Organisation::class);
}
public function customer(): BelongsTo
public function users(): BelongsToMany
{
return $this->belongsTo(Customer::class);
}
public function setlist(): BelongsTo
{
return $this->belongsTo(Setlist::class);
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
return $this->belongsToMany(User::class, 'event_user_roles')
->withPivot('role')
->withTimestamps();
}
public function invitations(): HasMany
{
return $this->hasMany(EventInvitation::class);
}
// Scopes
public function scopeUpcoming(Builder $query): Builder
{
return $query->where('event_date', '>=', now()->toDateString())
->orderBy('event_date');
}
public function scopePast(Builder $query): Builder
{
return $query->where('event_date', '<', now()->toDateString())
->orderByDesc('event_date');
}
public function scopeConfirmed(Builder $query): Builder
{
return $query->where('status', EventStatus::Confirmed);
}
public function scopeForUser(Builder $query, User $user): Builder
{
return $query->whereHas('invitations', fn (Builder $q) => $q->where('user_id', $user->id));
}
// Helper methods
public function isUpcoming(): bool
{
return $this->event_date->isFuture() || $this->event_date->isToday();
}
public function isPast(): bool
{
return $this->event_date->isPast() && !$this->event_date->isToday();
}
public function isConfirmed(): bool
{
return $this->status === EventStatus::Confirmed;
}
public function isCancelled(): bool
{
return $this->status === EventStatus::Cancelled;
return $this->hasMany(UserInvitation::class);
}
}

View File

@@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Enums\RsvpStatus;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class EventInvitation extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'event_id',
'user_id',
'rsvp_status',
'rsvp_note',
'rsvp_responded_at',
'invited_at',
];
protected function casts(): array
{
return [
'rsvp_status' => RsvpStatus::class,
'rsvp_responded_at' => 'datetime',
'invited_at' => 'datetime',
];
}
// Relationships
public function event(): BelongsTo
{
return $this->belongsTo(Event::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// Helper methods
public function isPending(): bool
{
return $this->rsvp_status === RsvpStatus::Pending;
}
public function isAvailable(): bool
{
return $this->rsvp_status === RsvpStatus::Available;
}
public function hasResponded(): bool
{
return $this->rsvp_responded_at !== null;
}
}

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Location extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'name',
'address',
'city',
'postal_code',
'country',
'latitude',
'longitude',
'capacity',
'contact_name',
'contact_email',
'contact_phone',
'notes',
];
protected function casts(): array
{
return [
'latitude' => 'decimal:7',
'longitude' => 'decimal:7',
'capacity' => 'integer',
];
}
// Relationships
public function events(): HasMany
{
return $this->hasMany(Event::class);
}
}

View File

@@ -1,41 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class MusicAttachment extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'music_number_id',
'file_name',
'original_name',
'file_path',
'file_type',
'file_size',
'mime_type',
];
protected function casts(): array
{
return [
'file_size' => 'integer',
];
}
// Relationships
public function musicNumber(): BelongsTo
{
return $this->belongsTo(MusicNumber::class);
}
}

View File

@@ -1,81 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class MusicNumber extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'title',
'artist',
'genre',
'duration_seconds',
'key',
'tempo_bpm',
'time_signature',
'lyrics',
'notes',
'tags',
'play_count',
'is_active',
'created_by',
];
protected function casts(): array
{
return [
'duration_seconds' => 'integer',
'tempo_bpm' => 'integer',
'tags' => 'array',
'play_count' => 'integer',
'is_active' => 'boolean',
];
}
// Relationships
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function attachments(): HasMany
{
return $this->hasMany(MusicAttachment::class);
}
public function setlistItems(): HasMany
{
return $this->hasMany(SetlistItem::class);
}
// Helper methods
public function formattedDuration(): string
{
if (!$this->duration_seconds) {
return '0:00';
}
$minutes = floor($this->duration_seconds / 60);
$seconds = $this->duration_seconds % 60;
return sprintf('%d:%02d', $minutes, $seconds);
}
public function isActive(): bool
{
return $this->is_active;
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
final class Organisation extends Model
{
use HasFactory;
use HasUlids;
use SoftDeletes;
protected $fillable = [
'name',
'slug',
'billing_status',
'settings',
];
protected function casts(): array
{
return [
'settings' => 'array',
];
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'organisation_user')
->withPivot('role')
->withTimestamps();
}
public function events(): HasMany
{
return $this->hasMany(Event::class);
}
public function invitations(): HasMany
{
return $this->hasMany(UserInvitation::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
final class OrganisationScope implements Scope
{
public function __construct(
private readonly string $organisationId,
) {}
public function apply(Builder $builder, Model $model): void
{
$builder->where($model->getTable() . '.organisation_id', $this->organisationId);
}
}

View File

@@ -1,77 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
final class Setlist extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'name',
'description',
'total_duration_seconds',
'is_template',
'is_archived',
'created_by',
];
protected function casts(): array
{
return [
'total_duration_seconds' => 'integer',
'is_template' => 'boolean',
'is_archived' => 'boolean',
];
}
// Relationships
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function items(): HasMany
{
return $this->hasMany(SetlistItem::class)->orderBy('position');
}
public function events(): HasMany
{
return $this->hasMany(Event::class);
}
// Helper methods
public function isTemplate(): bool
{
return $this->is_template;
}
public function isArchived(): bool
{
return $this->is_archived;
}
public function formattedDuration(): string
{
if (!$this->total_duration_seconds) {
return '0:00';
}
$minutes = floor($this->total_duration_seconds / 60);
$seconds = $this->total_duration_seconds % 60;
return sprintf('%d:%02d', $minutes, $seconds);
}
}

View File

@@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class SetlistItem extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'setlist_id',
'music_number_id',
'position',
'set_number',
'is_break',
'break_duration_seconds',
'notes',
];
protected function casts(): array
{
return [
'position' => 'integer',
'set_number' => 'integer',
'is_break' => 'boolean',
'break_duration_seconds' => 'integer',
];
}
// Relationships
public function setlist(): BelongsTo
{
return $this->belongsTo(Setlist::class);
}
public function musicNumber(): BelongsTo
{
return $this->belongsTo(MusicNumber::class);
}
// Helper methods
public function isBreak(): bool
{
return $this->is_break;
}
public function formattedBreakDuration(): string
{
if (!$this->is_break || !$this->break_duration_seconds) {
return '';
}
$minutes = floor($this->break_duration_seconds / 60);
$seconds = $this->break_duration_seconds % 60;
return sprintf('%d:%02d', $minutes, $seconds);
}
}

View File

@@ -6,28 +6,29 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
final class User extends Authenticatable
{
use HasApiTokens;
use HasFactory;
use HasRoles;
use HasUlids;
use Notifiable;
use SoftDeletes;
protected $fillable = [
'name',
'email',
'phone',
'bio',
'instruments',
'avatar_path',
'type',
'role',
'status',
'password',
'timezone',
'locale',
'avatar',
];
protected $hidden = [
@@ -40,39 +41,20 @@ final class User extends Authenticatable
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'instruments' => 'array',
];
}
// Helper methods
public function isAdmin(): bool
public function organisations(): BelongsToMany
{
return $this->role === 'admin';
return $this->belongsToMany(Organisation::class, 'organisation_user')
->withPivot('role')
->withTimestamps();
}
public function isBookingAgent(): bool
public function events(): BelongsToMany
{
return $this->role === 'booking_agent';
}
public function isMusicManager(): bool
{
return $this->role === 'music_manager';
}
public function isMember(): bool
{
return $this->type === 'member';
}
public function isCustomer(): bool
{
return $this->type === 'customer';
}
public function isActive(): bool
{
return $this->status === 'active';
return $this->belongsToMany(Event::class, 'event_user_roles')
->withPivot('role')
->withTimestamps();
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
final class UserInvitation extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'email',
'invited_by_user_id',
'organisation_id',
'event_id',
'role',
'token',
'status',
'expires_at',
];
protected function casts(): array
{
return [
'expires_at' => 'datetime',
];
}
public function invitedBy(): BelongsTo
{
return $this->belongsTo(User::class, 'invited_by_user_id');
}
public function organisation(): BelongsTo
{
return $this->belongsTo(Organisation::class);
}
public function event(): BelongsTo
{
return $this->belongsTo(Event::class);
}
}