mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 09:51:10 -05:00
feat: Implement rides management with CRUD functionality
- Added rides index view with search and filter options. - Created rides show view to display ride details. - Implemented API routes for rides. - Developed authentication routes for user registration, login, and email verification. - Created tests for authentication, email verification, password reset, and user profile management. - Added feature tests for rides and operators, including creation, updating, deletion, and searching. - Implemented soft deletes and caching for rides and operators. - Enhanced manufacturer and operator model tests for various functionalities.
This commit is contained in:
@@ -6,10 +6,11 @@ use App\Traits\HasSlugHistory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Manufacturer extends Model
|
||||
{
|
||||
use HasFactory, HasSlugHistory;
|
||||
use HasFactory, HasSlugHistory, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -24,15 +25,29 @@ class Manufacturer extends Model
|
||||
'description',
|
||||
'total_rides',
|
||||
'total_roller_coasters',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'total_rides' => 'integer',
|
||||
'total_roller_coasters' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the rides manufactured by this company.
|
||||
* Note: This relationship will be properly set up when we implement the Rides system.
|
||||
*/
|
||||
public function rides(): HasMany
|
||||
{
|
||||
return $this->hasMany(Ride::class);
|
||||
return $this->hasMany(Ride::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,7 +57,7 @@ class Manufacturer extends Model
|
||||
{
|
||||
$this->total_rides = $this->rides()->count();
|
||||
$this->total_roller_coasters = $this->rides()
|
||||
->where('type', 'roller_coaster')
|
||||
->where('category', 'RC')
|
||||
->count();
|
||||
$this->save();
|
||||
}
|
||||
@@ -88,6 +103,22 @@ class Manufacturer extends Model
|
||||
return $query->where('total_roller_coasters', '>', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include active manufacturers.
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query for optimized loading with statistics.
|
||||
*/
|
||||
public function scopeOptimized($query)
|
||||
{
|
||||
return $query->select(['id', 'name', 'slug', 'total_rides', 'total_roller_coasters', 'is_active']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*/
|
||||
|
||||
@@ -4,26 +4,34 @@ namespace App\Models;
|
||||
|
||||
use App\Enums\RideCategory;
|
||||
use App\Enums\RideStatus;
|
||||
use App\Traits\HasSlugHistory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class Ride extends Model
|
||||
{
|
||||
use SoftDeletes, HasSlugHistory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'status',
|
||||
'category',
|
||||
'opening_date',
|
||||
'closing_date',
|
||||
'park_id',
|
||||
'park_area_id',
|
||||
'manufacturer_id',
|
||||
'designer_id',
|
||||
'ride_model_id',
|
||||
'category',
|
||||
'status',
|
||||
'post_closing_status',
|
||||
'status_since',
|
||||
'opening_date',
|
||||
'closing_date',
|
||||
'min_height_in',
|
||||
'max_height_in',
|
||||
'capacity_per_hour',
|
||||
@@ -35,13 +43,14 @@ class Ride extends Model
|
||||
'category' => RideCategory::class,
|
||||
'opening_date' => 'date',
|
||||
'closing_date' => 'date',
|
||||
'status_since' => 'date',
|
||||
'min_height_in' => 'integer',
|
||||
'max_height_in' => 'integer',
|
||||
'capacity_per_hour' => 'integer',
|
||||
'ride_duration_seconds' => 'integer',
|
||||
];
|
||||
|
||||
// Base Relationships
|
||||
// Core Relationships (Django Parity)
|
||||
public function park(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Park::class);
|
||||
@@ -54,7 +63,7 @@ class Ride extends Model
|
||||
|
||||
public function manufacturer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Manufacturer::class);
|
||||
return $this->belongsTo(Manufacturer::class, 'manufacturer_id');
|
||||
}
|
||||
|
||||
public function designer(): BelongsTo
|
||||
@@ -67,23 +76,51 @@ class Ride extends Model
|
||||
return $this->belongsTo(RideModel::class);
|
||||
}
|
||||
|
||||
// Extended Relationships
|
||||
public function coasterStats(): HasOne
|
||||
{
|
||||
return $this->hasOne(RollerCoasterStats::class);
|
||||
}
|
||||
|
||||
// Photo Relationships (Polymorphic)
|
||||
public function photos(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Photo::class, 'photosable');
|
||||
}
|
||||
|
||||
// Review Relationships
|
||||
public function reviews(): HasMany
|
||||
public function reviews(): MorphMany
|
||||
{
|
||||
return $this->hasMany(Review::class);
|
||||
return $this->morphMany(Review::class, 'reviewable');
|
||||
}
|
||||
|
||||
public function approvedReviews(): HasMany
|
||||
public function approvedReviews(): MorphMany
|
||||
{
|
||||
return $this->reviews()->approved();
|
||||
return $this->reviews()->where('status', 'approved');
|
||||
}
|
||||
|
||||
// Review Methods
|
||||
// Query Scopes
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('status', 'operating');
|
||||
}
|
||||
|
||||
public function scopeByCategory($query, $category)
|
||||
{
|
||||
return $query->where('category', $category);
|
||||
}
|
||||
|
||||
public function scopeInPark($query, $parkId)
|
||||
{
|
||||
return $query->where('park_id', $parkId);
|
||||
}
|
||||
|
||||
public function scopeByManufacturer($query, $manufacturerId)
|
||||
{
|
||||
return $query->where('manufacturer_id', $manufacturerId);
|
||||
}
|
||||
|
||||
// Attributes & Helper Methods
|
||||
public function getAverageRatingAttribute(): ?float
|
||||
{
|
||||
return $this->approvedReviews()->avg('rating');
|
||||
@@ -94,6 +131,32 @@ class Ride extends Model
|
||||
return $this->approvedReviews()->count();
|
||||
}
|
||||
|
||||
public function getDisplayNameAttribute(): string
|
||||
{
|
||||
return $this->name . ' at ' . $this->park->name;
|
||||
}
|
||||
|
||||
public function getIsOperatingAttribute(): bool
|
||||
{
|
||||
return $this->status === RideStatus::OPERATING;
|
||||
}
|
||||
|
||||
public function getHeightRequirementTextAttribute(): ?string
|
||||
{
|
||||
if (!$this->min_height_in) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = "Must be at least {$this->min_height_in}\" tall";
|
||||
|
||||
if ($this->max_height_in) {
|
||||
$text .= " and no taller than {$this->max_height_in}\" tall";
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Review Management Methods
|
||||
public function canBeReviewedBy(?int $userId): bool
|
||||
{
|
||||
if (!$userId) {
|
||||
@@ -112,7 +175,32 @@ class Ride extends Model
|
||||
'rating' => $data['rating'],
|
||||
'title' => $data['title'] ?? null,
|
||||
'content' => $data['content'],
|
||||
'status' => ReviewStatus::PENDING,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
}
|
||||
|
||||
// Cache Management (Future: will use HasCaching trait)
|
||||
public function getCacheKey(string $suffix = ''): string
|
||||
{
|
||||
return "ride:{$this->id}" . ($suffix ? ":{$suffix}" : '');
|
||||
}
|
||||
|
||||
public function clearRelatedCache(): void
|
||||
{
|
||||
cache()->forget($this->getCacheKey('reviews'));
|
||||
cache()->forget($this->getCacheKey('statistics'));
|
||||
cache()->forget($this->getCacheKey('photos'));
|
||||
}
|
||||
|
||||
// Statistics Management (Future: will use HasStatistics trait)
|
||||
public function updateStatistics(): void
|
||||
{
|
||||
// Placeholder for future HasStatistics trait integration
|
||||
// For now, manually manage statistics
|
||||
$totalReviews = $this->reviews()->count();
|
||||
$averageRating = $this->reviews()->avg('rating');
|
||||
|
||||
// Update any related statistics tracking
|
||||
$this->clearRelatedCache();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user