feat: initial commit - Band Management application

This commit is contained in:
2026-01-06 03:11:46 +01:00
commit 34e12e00b3
24543 changed files with 3991790 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
<?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;
}
}

132
api/app/Models/Event.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
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\HasMany;
final class Event extends Model
{
use HasFactory;
use HasUlids;
protected $fillable = [
'title',
'description',
'location_id',
'customer_id',
'setlist_id',
'event_date',
'start_time',
'end_time',
'load_in_time',
'soundcheck_time',
'fee',
'currency',
'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',
];
}
// Relationships
public function location(): BelongsTo
{
return $this->belongsTo(Location::class);
}
public function customer(): BelongsTo
{
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');
}
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;
}
}

View File

@@ -0,0 +1,65 @@
<?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

@@ -0,0 +1,48 @@
<?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

@@ -0,0 +1,41 @@
<?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

@@ -0,0 +1,81 @@
<?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,77 @@
<?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

@@ -0,0 +1,68 @@
<?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);
}
}

78
api/app/Models/User.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
final class User extends Authenticatable
{
use HasApiTokens;
use HasFactory;
use HasUlids;
use Notifiable;
protected $fillable = [
'name',
'email',
'phone',
'bio',
'instruments',
'avatar_path',
'type',
'role',
'status',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'instruments' => 'array',
];
}
// Helper methods
public function isAdmin(): bool
{
return $this->role === 'admin';
}
public function isBookingAgent(): bool
{
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';
}
}