mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 08:31:09 -05:00
Add photo management features, update database configuration, and enhance park model seeding
This commit is contained in:
@@ -8,7 +8,6 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Location extends Model
|
||||
{
|
||||
use \Spatie\Activitylog\LogsActivity;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -41,7 +40,7 @@ class Location extends Model
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'coordinates' => 'point',
|
||||
// 'coordinates' => 'point', // This cast is not available in Laravel by default
|
||||
'latitude' => 'decimal:6',
|
||||
'longitude' => 'decimal:6',
|
||||
'elevation' => 'decimal:2',
|
||||
@@ -51,23 +50,6 @@ class Location extends Model
|
||||
'is_approximate' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be logged.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $logAttributes = [
|
||||
'name',
|
||||
'location_type',
|
||||
'address',
|
||||
'city',
|
||||
'state',
|
||||
'country',
|
||||
'postal_code',
|
||||
'coordinates',
|
||||
'latitude',
|
||||
'longitude',
|
||||
];
|
||||
|
||||
/**
|
||||
* Boot the model.
|
||||
|
||||
@@ -6,8 +6,10 @@ use App\Enums\ParkStatus;
|
||||
use App\Traits\HasLocation;
|
||||
use App\Traits\HasSlugHistory;
|
||||
use App\Traits\HasParkStatistics;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
@@ -95,6 +97,95 @@ class Park extends Model
|
||||
return $this->hasMany(ParkArea::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the photos for the park.
|
||||
*/
|
||||
public function photos(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Photo::class, 'photoable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the featured photo for the park.
|
||||
*/
|
||||
public function featuredPhoto()
|
||||
{
|
||||
return $this->photos()->where('is_featured', true)->first()
|
||||
?? $this->photos()->orderBy('position')->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of the featured photo or a default image.
|
||||
*/
|
||||
public function getFeaturedPhotoUrlAttribute(): string
|
||||
{
|
||||
$photo = $this->featuredPhoto();
|
||||
return $photo ? $photo->url : asset('images/placeholders/default-park.jpg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a photo to the park.
|
||||
*
|
||||
* @param array<string, mixed> $attributes
|
||||
* @return \App\Models\Photo
|
||||
*/
|
||||
public function addPhoto(array $attributes): Photo
|
||||
{
|
||||
// Set position to be the last in the collection if not specified
|
||||
if (!isset($attributes['position'])) {
|
||||
$lastPosition = $this->photos()->max('position') ?? 0;
|
||||
$attributes['position'] = $lastPosition + 1;
|
||||
}
|
||||
|
||||
// If this is the first photo or is_featured is true, make it featured
|
||||
if ($this->photos()->count() === 0 || ($attributes['is_featured'] ?? false)) {
|
||||
$attributes['is_featured'] = true;
|
||||
} else {
|
||||
$attributes['is_featured'] = false;
|
||||
}
|
||||
|
||||
return $this->photos()->create($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a photo as the featured photo.
|
||||
*
|
||||
* @param \App\Models\Photo|int $photo
|
||||
* @return bool
|
||||
*/
|
||||
public function setFeaturedPhoto($photo): bool
|
||||
{
|
||||
if (is_numeric($photo)) {
|
||||
$photo = $this->photos()->findOrFail($photo);
|
||||
}
|
||||
|
||||
return $photo->setAsFeatured();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder photos.
|
||||
*
|
||||
* @param array<int, int> $photoIds Ordered array of photo IDs
|
||||
* @return bool
|
||||
*/
|
||||
public function reorderPhotos(array $photoIds): bool
|
||||
{
|
||||
// Begin transaction
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($photoIds as $position => $photoId) {
|
||||
$this->photos()->where('id', $photoId)->update(['position' => $position + 1]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get formatted website URL (ensures proper URL format).
|
||||
*/
|
||||
@@ -165,6 +256,50 @@ class Park extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted location for the park.
|
||||
*/
|
||||
public function getFormattedLocationAttribute(): string
|
||||
{
|
||||
return $this->formatted_address ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the absolute URL for the park detail page.
|
||||
*/
|
||||
public function getAbsoluteUrl(): string
|
||||
{
|
||||
return route('parks.show', ['slug' => $this->slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a park by its current or historical slug.
|
||||
*
|
||||
* @param string $slug
|
||||
* @return array{0: \App\Models\Park|null, 1: bool} Park and whether a historical slug was used
|
||||
*/
|
||||
public static function getBySlug(string $slug): array
|
||||
{
|
||||
// Try current slug
|
||||
$park = static::where('slug', $slug)->first();
|
||||
if ($park) {
|
||||
return [$park, false];
|
||||
}
|
||||
|
||||
// Try historical slug
|
||||
$slugHistory = SlugHistory::where('slug', $slug)
|
||||
->where('sluggable_type', static::class)
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($slugHistory) {
|
||||
$park = static::find($slugHistory->sluggable_id);
|
||||
return [$park, true];
|
||||
}
|
||||
|
||||
return [null, false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the model.
|
||||
*/
|
||||
|
||||
131
app/Models/Photo.php
Normal file
131
app/Models/Photo.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Photo extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'file_path',
|
||||
'file_name',
|
||||
'file_size',
|
||||
'mime_type',
|
||||
'width',
|
||||
'height',
|
||||
'position',
|
||||
'is_featured',
|
||||
'alt_text',
|
||||
'credit',
|
||||
'source_url',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'width' => 'integer',
|
||||
'height' => 'integer',
|
||||
'file_size' => 'integer',
|
||||
'position' => 'integer',
|
||||
'is_featured' => 'boolean',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent photoable model.
|
||||
*/
|
||||
public function photoable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the photo.
|
||||
*/
|
||||
public function getUrlAttribute(): string
|
||||
{
|
||||
return asset('storage/' . $this->file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thumbnail URL for the photo.
|
||||
*/
|
||||
public function getThumbnailUrlAttribute(): string
|
||||
{
|
||||
$pathInfo = pathinfo($this->file_path);
|
||||
$thumbnailPath = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_thumb.' . $pathInfo['extension'];
|
||||
|
||||
return asset('storage/' . $thumbnailPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include featured photos.
|
||||
*/
|
||||
public function scopeFeatured($query)
|
||||
{
|
||||
return $query->where('is_featured', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to order by position.
|
||||
*/
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('position');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this photo as featured and unset others.
|
||||
*/
|
||||
public function setAsFeatured(): bool
|
||||
{
|
||||
if (!$this->photoable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin transaction
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Unset all other photos as featured
|
||||
$this->photoable->photos()
|
||||
->where('id', '!=', $this->id)
|
||||
->update(['is_featured' => false]);
|
||||
|
||||
// Set this photo as featured
|
||||
$this->is_featured = true;
|
||||
$this->save();
|
||||
|
||||
DB::commit();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the position of this photo.
|
||||
*/
|
||||
public function updatePosition(int $position): bool
|
||||
{
|
||||
$this->position = $position;
|
||||
return $this->save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user