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:
pacnpal
2025-06-19 22:34:10 -04:00
parent 86263db9d9
commit cc33781245
148 changed files with 14026 additions and 2482 deletions

View File

@@ -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();
}
}