Add enums for ReviewStatus, TrackMaterial, LaunchType, RideCategory, and RollerCoasterType; implement Designer and RideModel models; create migrations for ride_models and helpful_votes tables; enhance RideGalleryComponent documentation

This commit is contained in:
pacnpal
2025-02-25 20:37:19 -05:00
parent 8951e59f49
commit 64b0e90a27
35 changed files with 3157 additions and 1 deletions

43
app/Models/Designer.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Designer extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'slug',
'bio',
];
/**
* Boot the model.
*/
protected static function boot()
{
parent::boot();
static::creating(function ($designer) {
if (empty($designer->slug)) {
$designer->slug = Str::slug($designer->name);
}
});
}
/**
* Get the rides designed by this designer.
*/
public function rides(): HasMany
{
return $this->hasMany(Ride::class);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class HelpfulVote extends Model
{
protected $fillable = [
'review_id',
'user_id',
];
// Relationships
public function review(): BelongsTo
{
return $this->belongsTo(Review::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
// Helper method to toggle vote
public static function toggle(int $reviewId, int $userId): bool
{
$vote = static::where([
'review_id' => $reviewId,
'user_id' => $userId,
])->first();
if ($vote) {
$vote->delete();
return false;
}
static::create([
'review_id' => $reviewId,
'user_id' => $userId,
]);
return true;
}
}

113
app/Models/Review.php Normal file
View File

@@ -0,0 +1,113 @@
<?php
namespace App\Models;
use App\Enums\ReviewStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Auth;
class Review extends Model
{
protected $fillable = [
'ride_id',
'user_id',
'rating',
'title',
'content',
'status',
'moderated_at',
'moderated_by',
'helpful_votes_count',
];
protected $casts = [
'status' => ReviewStatus::class,
'moderated_at' => 'datetime',
'rating' => 'integer',
'helpful_votes_count' => 'integer',
];
// Relationships
public function ride(): BelongsTo
{
return $this->belongsTo(Ride::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function moderator(): BelongsTo
{
return $this->belongsTo(User::class, 'moderated_by');
}
public function helpfulVotes(): HasMany
{
return $this->hasMany(HelpfulVote::class);
}
// Scopes
public function scopePending($query)
{
return $query->where('status', ReviewStatus::PENDING);
}
public function scopeApproved($query)
{
return $query->where('status', ReviewStatus::APPROVED);
}
public function scopeRejected($query)
{
return $query->where('status', ReviewStatus::REJECTED);
}
public function scopeByRide($query, $rideId)
{
return $query->where('ride_id', $rideId);
}
public function scopeByUser($query, $userId)
{
return $query->where('user_id', $userId);
}
// Methods
public function approve(): bool
{
return $this->moderate(ReviewStatus::APPROVED);
}
public function reject(): bool
{
return $this->moderate(ReviewStatus::REJECTED);
}
public function moderate(ReviewStatus $status, ?int $moderatorId = null): bool
{
return $this->update([
'status' => $status,
'moderated_at' => now(),
'moderated_by' => $moderatorId ?? Auth::id(),
]);
}
public function toggleHelpfulVote(int $userId): bool
{
$vote = $this->helpfulVotes()->where('user_id', $userId)->first();
if ($vote) {
$vote->delete();
$this->decrement('helpful_votes_count');
return false;
}
$this->helpfulVotes()->create(['user_id' => $userId]);
$this->increment('helpful_votes_count');
return true;
}
}

118
app/Models/Ride.php Normal file
View File

@@ -0,0 +1,118 @@
<?php
namespace App\Models;
use App\Enums\RideCategory;
use App\Enums\RideStatus;
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\Support\Facades\Auth;
class Ride extends Model
{
protected $fillable = [
'name',
'description',
'status',
'category',
'opening_date',
'closing_date',
'park_id',
'park_area_id',
'manufacturer_id',
'designer_id',
'ride_model_id',
'min_height_in',
'max_height_in',
'capacity_per_hour',
'ride_duration_seconds',
];
protected $casts = [
'status' => RideStatus::class,
'category' => RideCategory::class,
'opening_date' => 'date',
'closing_date' => 'date',
'min_height_in' => 'integer',
'max_height_in' => 'integer',
'capacity_per_hour' => 'integer',
'ride_duration_seconds' => 'integer',
];
// Base Relationships
public function park(): BelongsTo
{
return $this->belongsTo(Park::class);
}
public function parkArea(): BelongsTo
{
return $this->belongsTo(ParkArea::class);
}
public function manufacturer(): BelongsTo
{
return $this->belongsTo(Manufacturer::class);
}
public function designer(): BelongsTo
{
return $this->belongsTo(Designer::class);
}
public function rideModel(): BelongsTo
{
return $this->belongsTo(RideModel::class);
}
public function coasterStats(): HasOne
{
return $this->hasOne(RollerCoasterStats::class);
}
// Review Relationships
public function reviews(): HasMany
{
return $this->hasMany(Review::class);
}
public function approvedReviews(): HasMany
{
return $this->reviews()->approved();
}
// Review Methods
public function getAverageRatingAttribute(): ?float
{
return $this->approvedReviews()->avg('rating');
}
public function getReviewCountAttribute(): int
{
return $this->approvedReviews()->count();
}
public function canBeReviewedBy(?int $userId): bool
{
if (!$userId) {
return false;
}
return !$this->reviews()
->where('user_id', $userId)
->exists();
}
public function addReview(array $data): Review
{
return $this->reviews()->create([
'user_id' => Auth::id(),
'rating' => $data['rating'],
'title' => $data['title'] ?? null,
'content' => $data['content'],
'status' => ReviewStatus::PENDING,
]);
}
}

58
app/Models/RideModel.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
namespace App\Models;
use App\Enums\RideCategory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class RideModel extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'name',
'manufacturer_id',
'description',
'category',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'category' => RideCategory::class,
];
/**
* Get the manufacturer that produces this ride model.
*/
public function manufacturer(): BelongsTo
{
return $this->belongsTo(Manufacturer::class);
}
/**
* Get the rides that are instances of this model.
*/
public function rides(): HasMany
{
return $this->hasMany(Ride::class);
}
/**
* Get the full name of the ride model including manufacturer.
*/
public function getFullNameAttribute(): string
{
return $this->manufacturer
? "{$this->manufacturer->name} {$this->name}"
: $this->name;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Models;
use App\Enums\TrackMaterial;
use App\Enums\RollerCoasterType;
use App\Enums\LaunchType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class RollerCoasterStats extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
protected $fillable = [
'ride_id',
'height_ft',
'length_ft',
'speed_mph',
'inversions',
'ride_time_seconds',
'track_type',
'track_material',
'roller_coaster_type',
'max_drop_height_ft',
'launch_type',
'train_style',
'trains_count',
'cars_per_train',
'seats_per_car',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'height_ft' => 'decimal:2',
'length_ft' => 'decimal:2',
'speed_mph' => 'decimal:2',
'max_drop_height_ft' => 'decimal:2',
'track_material' => TrackMaterial::class,
'roller_coaster_type' => RollerCoasterType::class,
'launch_type' => LaunchType::class,
'trains_count' => 'integer',
'cars_per_train' => 'integer',
'seats_per_car' => 'integer',
];
/**
* Get the ride that owns these statistics.
*/
public function ride(): BelongsTo
{
return $this->belongsTo(Ride::class);
}
/**
* Calculate total seating capacity.
*/
public function getTotalSeatsAttribute(): ?int
{
if ($this->trains_count && $this->cars_per_train && $this->seats_per_car) {
return $this->trains_count * $this->cars_per_train * $this->seats_per_car;
}
return null;
}
}