feat: add detail image media and play list

This commit is contained in:
2026-06-06 19:08:27 +03:30
parent 0b578e15dd
commit fcf9c35697
5 changed files with 91 additions and 10 deletions
+24 -6
View File
@@ -30,6 +30,8 @@ public function store(Request $request)
'subcategory_ids.*' => 'integer|exists:sub_categories,id', 'subcategory_ids.*' => 'integer|exists:sub_categories,id',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192', 'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'detail_image_id' => 'nullable|exists:images,id',
'detail_image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'duration' => 'nullable|integer', 'duration' => 'nullable|integer',
'is_premium' => 'nullable|boolean', 'is_premium' => 'nullable|boolean',
'file' => 'nullable|file|extensions:mp3,wav,ogg,flac,aac,m4a,opus,mpga,wma,mp4,mov,m4v,webm,mkv,avi,3gp|max:512000', 'file' => 'nullable|file|extensions:mp3,wav,ogg,flac,aac,m4a,opus,mpga,wma,mp4,mov,m4v,webm,mkv,avi,3gp|max:512000',
@@ -47,6 +49,7 @@ public function store(Request $request)
// An uploaded image file takes precedence over a provided image_id. // An uploaded image file takes precedence over a provided image_id.
$imageId = $this->uploadedImageId($request) ?? ($data['image_id'] ?? null); $imageId = $this->uploadedImageId($request) ?? ($data['image_id'] ?? null);
$detailImageId = $this->uploadedImageId($request, 'detail_image') ?? ($data['detail_image_id'] ?? null);
// When a file is uploaded, expose its public URL as external_url too // When a file is uploaded, expose its public URL as external_url too
// (older front-end versions read external_url for the playable source). // (older front-end versions read external_url for the playable source).
@@ -60,6 +63,7 @@ public function store(Request $request)
'file_path' => $path, 'file_path' => $path,
'external_url' => $externalUrl, 'external_url' => $externalUrl,
'image_id' => $imageId, 'image_id' => $imageId,
'detail_image_id' => $detailImageId,
'duration' => $data['duration'] ?? null, 'duration' => $data['duration'] ?? null,
'visibility' => $data['visibility'] ?? 'public', 'visibility' => $data['visibility'] ?? 'public',
'is_premium'=> $data['is_premium'] ?? false 'is_premium'=> $data['is_premium'] ?? false
@@ -80,12 +84,12 @@ public function store(Request $request)
} }
return response()->json([ return response()->json([
'message' => 'Media created successfully', 'message' => 'Media created successfully',
'media' => $media->load(['image', 'categories', 'subCategories', 'tags']), 'media' => $media->load(['image', 'detailImage', 'categories', 'subCategories', 'tags']),
]); ]);
} }
public function index(Request $request) public function index(Request $request)
{ {
$query = Media::with(['image', 'categories', 'subCategories', 'myNote', 'tags','comments']) $query = Media::with(['image', 'detailImage', 'categories', 'subCategories', 'myNote', 'tags','comments'])
->where(function ($q) { ->where(function ($q) {
$q->where('visibility', 'public') $q->where('visibility', 'public')
->orWhere('user_id', auth()->id()); ->orWhere('user_id', auth()->id());
@@ -354,6 +358,7 @@ public function show($id)
{ {
$media = Media::with([ $media = Media::with([
'image', 'image',
'detailImage',
'categories', 'categories',
'subCategories', 'subCategories',
'myNote', 'myNote',
@@ -392,6 +397,7 @@ public function show($id)
'updated_at' => $media->updated_at, 'updated_at' => $media->updated_at,
'is_premium' => $media->is_premium, 'is_premium' => $media->is_premium,
'image' => $media->image, 'image' => $media->image,
'detail_image' => $media->detailImage,
'categories' => $media->categories, 'categories' => $media->categories,
'sub_categories' => $media->subCategories, 'sub_categories' => $media->subCategories,
'tags' => $media->tags, 'tags' => $media->tags,
@@ -482,6 +488,8 @@ public function update(Request $request, $id)
'is_premium' => 'nullable|boolean', 'is_premium' => 'nullable|boolean',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192', 'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'detail_image_id' => 'nullable|exists:images,id',
'detail_image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'duration' => 'nullable|integer', 'duration' => 'nullable|integer',
'file' => 'nullable|file|extensions:mp3,wav,ogg,flac,aac,m4a,opus,mpga,wma,mp4,mov,m4v,webm,mkv,avi,3gp|max:512000', 'file' => 'nullable|file|extensions:mp3,wav,ogg,flac,aac,m4a,opus,mpga,wma,mp4,mov,m4v,webm,mkv,avi,3gp|max:512000',
'external_url' => 'nullable|string', 'external_url' => 'nullable|string',
@@ -522,6 +530,16 @@ public function update(Request $request, $id)
$updateData['image_id'] = $media->image_id; $updateData['image_id'] = $media->image_id;
} }
// --- Handle detail image (shown on the show-by-id screen) ---
$uploadedDetailImageId = $this->uploadedImageId($request, 'detail_image');
if ($uploadedDetailImageId !== null) {
$updateData['detail_image_id'] = $uploadedDetailImageId;
} elseif (array_key_exists('detail_image_id', $data)) {
$updateData['detail_image_id'] = $data['detail_image_id'];
} else {
$updateData['detail_image_id'] = $media->detail_image_id;
}
// --- update media --- // --- update media ---
$media->update($updateData); $media->update($updateData);
@@ -546,7 +564,7 @@ public function update(Request $request, $id)
return response()->json([ return response()->json([
'message' => 'Media updated successfully', 'message' => 'Media updated successfully',
'media' => $media->load(['image', 'categories', 'subCategories', 'tags']), 'media' => $media->load(['image', 'detailImage', 'categories', 'subCategories', 'tags']),
]); ]);
} }
@@ -583,7 +601,7 @@ public function toggleSaveMedia($id)
// GET saved // GET saved
public function saved() public function saved()
{ {
return auth()->user()->savedMedia()->with(['image','categories', 'subCategories', 'myNote' , 'tags'])->get(); return auth()->user()->savedMedia()->with(['image','detailImage','categories', 'subCategories', 'myNote' , 'tags'])->get();
} }
// RECORD a play for the current user (feeds popular + recently played). // RECORD a play for the current user (feeds popular + recently played).
@@ -621,7 +639,7 @@ public function popular(Request $request)
}) })
->withCount('plays as listeners_count') // distinct users who played ->withCount('plays as listeners_count') // distinct users who played
->withSum('plays as plays_count', 'play_count') // total plays ->withSum('plays as plays_count', 'play_count') // total plays
->with(['image', 'categories', 'subCategories', 'tags']) ->with(['image', 'detailImage', 'categories', 'subCategories', 'tags'])
->orderByDesc('plays_count') ->orderByDesc('plays_count')
->orderByDesc('listeners_count') ->orderByDesc('listeners_count')
->orderByDesc('created_at') ->orderByDesc('created_at')
@@ -638,7 +656,7 @@ public function recentlyPlayed(Request $request)
$plays = MediaPlay::where('user_id', auth()->id()) $plays = MediaPlay::where('user_id', auth()->id())
->whereNotNull('last_played_at') ->whereNotNull('last_played_at')
->with(['media' => fn ($q) => $q->with(['image', 'categories', 'subCategories', 'tags'])]) ->with(['media' => fn ($q) => $q->with(['image', 'detailImage', 'categories', 'subCategories', 'tags'])])
->orderByDesc('last_played_at') ->orderByDesc('last_played_at')
->limit($limit) ->limit($limit)
->get(); ->get();
@@ -13,7 +13,7 @@ class MusicPlaylistController extends Controller
public function index(Request $request, $categoryId = null) public function index(Request $request, $categoryId = null)
{ {
$query = MusicPlaylist::with(['categories', 'subcategories', 'image']); $query = MusicPlaylist::with(['categories', 'subcategories', 'image', 'detailImage']);
$categoryId = $categoryId ?? $request->input('category_id'); $categoryId = $categoryId ?? $request->input('category_id');
@@ -46,6 +46,8 @@ public function store(Request $request)
'description' => 'nullable|string', 'description' => 'nullable|string',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192', 'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'detail_image_id' => 'nullable|exists:images,id',
'detail_image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'order' => 'nullable|integer', 'order' => 'nullable|integer',
'is_active' => 'nullable|boolean', 'is_active' => 'nullable|boolean',
]); ]);
@@ -61,6 +63,7 @@ public function store(Request $request)
// An uploaded image file takes precedence over a provided image_id. // An uploaded image file takes precedence over a provided image_id.
$data['image_id'] = $this->uploadedImageId($request) ?? ($data['image_id'] ?? null); $data['image_id'] = $this->uploadedImageId($request) ?? ($data['image_id'] ?? null);
$data['detail_image_id'] = $this->uploadedImageId($request, 'detail_image') ?? ($data['detail_image_id'] ?? null);
$playlist = MusicPlaylist::create($data); $playlist = MusicPlaylist::create($data);
@@ -69,7 +72,7 @@ public function store(Request $request)
return response()->json([ return response()->json([
'message' => 'Playlist created successfully', 'message' => 'Playlist created successfully',
'playlist' => $playlist->load(['categories', 'subcategories', 'image']) 'playlist' => $playlist->load(['categories', 'subcategories', 'image', 'detailImage'])
], 201); ], 201);
} }
@@ -79,6 +82,7 @@ public function show($id)
'categories', 'categories',
'subcategories', 'subcategories',
'image', 'image',
'detailImage',
'musics' => function($q) { 'musics' => function($q) {
$q->where('music.is_active', true) $q->where('music.is_active', true)
->with(['image', 'tags']) ->with(['image', 'tags'])
@@ -127,6 +131,8 @@ public function update(Request $request, $id)
'description' => 'nullable|string', 'description' => 'nullable|string',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192', 'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'detail_image_id' => 'nullable|exists:images,id',
'detail_image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
'order' => 'nullable|integer', 'order' => 'nullable|integer',
'is_active' => 'nullable|boolean', 'is_active' => 'nullable|boolean',
]); ]);
@@ -139,6 +145,9 @@ public function update(Request $request, $id)
if (($uploadedImageId = $this->uploadedImageId($request)) !== null) { if (($uploadedImageId = $this->uploadedImageId($request)) !== null) {
$data['image_id'] = $uploadedImageId; $data['image_id'] = $uploadedImageId;
} }
if (($uploadedDetailImageId = $this->uploadedImageId($request, 'detail_image')) !== null) {
$data['detail_image_id'] = $uploadedDetailImageId;
}
$playlist->update($data); $playlist->update($data);
@@ -152,7 +161,7 @@ public function update(Request $request, $id)
return response()->json([ return response()->json([
'message' => 'Playlist updated successfully', 'message' => 'Playlist updated successfully',
'playlist' => $playlist->load(['categories', 'subcategories', 'image']) 'playlist' => $playlist->load(['categories', 'subcategories', 'image', 'detailImage'])
]); ]);
} }
+7
View File
@@ -13,6 +13,7 @@ class Media extends Model
protected $fillable = [ protected $fillable = [
'user_id', 'user_id',
'image_id', 'image_id',
'detail_image_id',
'title', 'title',
'caption', 'caption',
'type', 'type',
@@ -42,6 +43,12 @@ public function image()
return $this->belongsTo(Image::class); return $this->belongsTo(Image::class);
} }
// Image shown on the detail (show-by-id) screen; image() is the list thumbnail.
public function detailImage()
{
return $this->belongsTo(Image::class, 'detail_image_id');
}
public function categories() public function categories()
{ {
return $this->belongsToMany(Category::class, 'category_media'); return $this->belongsToMany(Category::class, 'category_media');
+7 -1
View File
@@ -15,7 +15,7 @@ class MusicPlaylist extends Model
protected $table = 'music_playlists'; protected $table = 'music_playlists';
protected $fillable = [ protected $fillable = [
'name', 'slug', 'description', 'image_id', 'order', 'is_active' 'name', 'slug', 'description', 'image_id', 'detail_image_id', 'order', 'is_active'
]; ];
protected $casts = [ protected $casts = [
@@ -54,6 +54,12 @@ public function image(): BelongsTo
{ {
return $this->belongsTo(Image::class); return $this->belongsTo(Image::class);
} }
// Image shown on the detail (show-by-id) screen; image() is the list thumbnail.
public function detailImage(): BelongsTo
{
return $this->belongsTo(Image::class, 'detail_image_id');
}
public function getActiveMusicsAttribute() public function getActiveMusicsAttribute()
{ {
@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Add a second image reference: image_id = list/thumbnail image,
* detail_image_id = image shown on the detail (show-by-id) screen.
*/
public function up(): void
{
Schema::table('media', function (Blueprint $table) {
$table->unsignedBigInteger('detail_image_id')->nullable()->after('image_id');
$table->foreign('detail_image_id')->references('id')->on('images')->nullOnDelete();
});
Schema::table('music_playlists', function (Blueprint $table) {
$table->unsignedBigInteger('detail_image_id')->nullable()->after('image_id');
$table->foreign('detail_image_id')->references('id')->on('images')->nullOnDelete();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('media', function (Blueprint $table) {
$table->dropForeign(['detail_image_id']);
$table->dropColumn('detail_image_id');
});
Schema::table('music_playlists', function (Blueprint $table) {
$table->dropForeign(['detail_image_id']);
$table->dropColumn('detail_image_id');
});
}
};