mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 07:31:09 -05:00
Add models, enums, and services for user roles, theme preferences, slug history, and ID generation
This commit is contained in:
261
app/Models/ParkArea.php
Normal file
261
app/Models/ParkArea.php
Normal file
@@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasSlugHistory;
|
||||
use App\Traits\HasAreaStatistics;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class ParkArea extends Model
|
||||
{
|
||||
use HasFactory, HasSlugHistory, HasAreaStatistics;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'opening_date',
|
||||
'closing_date',
|
||||
'position',
|
||||
'parent_id',
|
||||
'ride_count',
|
||||
'coaster_count',
|
||||
'flat_ride_count',
|
||||
'water_ride_count',
|
||||
'daily_capacity',
|
||||
'peak_wait_time',
|
||||
'average_rating',
|
||||
'total_rides_operated',
|
||||
'retired_rides_count',
|
||||
'last_new_ride_added',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'opening_date' => 'date',
|
||||
'closing_date' => 'date',
|
||||
'position' => 'integer',
|
||||
'ride_count' => 'integer',
|
||||
'coaster_count' => 'integer',
|
||||
'flat_ride_count' => 'integer',
|
||||
'water_ride_count' => 'integer',
|
||||
'daily_capacity' => 'integer',
|
||||
'peak_wait_time' => 'integer',
|
||||
'average_rating' => 'decimal:2',
|
||||
'total_rides_operated' => 'integer',
|
||||
'retired_rides_count' => 'integer',
|
||||
'last_new_ride_added' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the park that owns the area.
|
||||
*/
|
||||
public function park(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Park::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent area if this is a sub-area.
|
||||
*/
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ParkArea::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sub-areas of this area.
|
||||
*/
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(ParkArea::class, 'parent_id')
|
||||
->orderBy('position');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a brief description suitable for cards and previews.
|
||||
*/
|
||||
public function getBriefDescriptionAttribute(): string
|
||||
{
|
||||
$description = $this->description ?? '';
|
||||
return strlen($description) > 150 ? substr($description, 0, 150) . '...' : $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opening year of the area.
|
||||
*/
|
||||
public function getOpeningYearAttribute(): ?string
|
||||
{
|
||||
return $this->opening_date?->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the area is currently operating.
|
||||
*/
|
||||
public function isOperating(): bool
|
||||
{
|
||||
if ($this->closing_date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this area has sub-areas.
|
||||
*/
|
||||
public function hasChildren(): bool
|
||||
{
|
||||
return $this->children()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a top-level area.
|
||||
*/
|
||||
public function isTopLevel(): bool
|
||||
{
|
||||
return is_null($this->parent_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next available position for a new area.
|
||||
*/
|
||||
public function getNextPosition(): int
|
||||
{
|
||||
$maxPosition = static::where('park_id', $this->park_id)
|
||||
->where('parent_id', $this->parent_id)
|
||||
->max('position');
|
||||
|
||||
return ($maxPosition ?? -1) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this area to a new position.
|
||||
*/
|
||||
public function moveToPosition(int $newPosition): void
|
||||
{
|
||||
if ($newPosition === $this->position) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oldPosition = $this->position;
|
||||
|
||||
if ($newPosition > $oldPosition) {
|
||||
// Moving down: decrement positions of items in between
|
||||
static::where('park_id', $this->park_id)
|
||||
->where('parent_id', $this->parent_id)
|
||||
->whereBetween('position', [$oldPosition + 1, $newPosition])
|
||||
->decrement('position');
|
||||
} else {
|
||||
// Moving up: increment positions of items in between
|
||||
static::where('park_id', $this->park_id)
|
||||
->where('parent_id', $this->parent_id)
|
||||
->whereBetween('position', [$newPosition, $oldPosition - 1])
|
||||
->increment('position');
|
||||
}
|
||||
|
||||
$this->update(['position' => $newPosition]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope query to only include operating areas.
|
||||
*/
|
||||
public function scopeOperating($query)
|
||||
{
|
||||
return $query->whereNull('closing_date');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope query to only include top-level areas.
|
||||
*/
|
||||
public function scopeTopLevel($query)
|
||||
{
|
||||
return $query->whereNull('parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override parent method to ensure unique slugs within a park.
|
||||
*/
|
||||
protected function generateSlug(): string
|
||||
{
|
||||
$slug = \Str::slug($this->name);
|
||||
$count = 2;
|
||||
|
||||
while (
|
||||
static::where('park_id', $this->park_id)
|
||||
->where('slug', $slug)
|
||||
->where('id', '!=', $this->id)
|
||||
->exists() ||
|
||||
static::whereHas('slugHistories', function ($query) use ($slug) {
|
||||
$query->where('slug', $slug);
|
||||
})
|
||||
->where('park_id', $this->park_id)
|
||||
->where('id', '!=', $this->id)
|
||||
->exists()
|
||||
) {
|
||||
$slug = \Str::slug($this->name) . '-' . $count++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*/
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an area by its slug within a specific park.
|
||||
*/
|
||||
public static function findByParkAndSlug(Park $park, string $slug): ?self
|
||||
{
|
||||
// Try current slug
|
||||
$area = static::where('park_id', $park->id)
|
||||
->where('slug', $slug)
|
||||
->first();
|
||||
|
||||
if ($area) {
|
||||
return $area;
|
||||
}
|
||||
|
||||
// Try historical slug
|
||||
$slugHistory = SlugHistory::where('slug', $slug)
|
||||
->where('sluggable_type', static::class)
|
||||
->whereHas('sluggable', function ($query) use ($park) {
|
||||
$query->where('park_id', $park->id);
|
||||
})
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
return $slugHistory ? static::find($slugHistory->sluggable_id) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the model.
|
||||
*/
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function (ParkArea $area) {
|
||||
if (is_null($area->position)) {
|
||||
$area->position = $area->getNextPosition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user