feat: Many category and Many sub-category media

This commit is contained in:
2026-05-31 20:18:53 +03:30
parent 56f0e8dbe2
commit 449a542571
6 changed files with 190 additions and 48 deletions
+63 -39
View File
@@ -4,6 +4,7 @@
use App\Models\Media; use App\Models\Media;
use App\Models\Category; use App\Models\Category;
use App\Models\SubCategory;
use App\Models\Tag; use App\Models\Tag;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -18,8 +19,10 @@ public function store(Request $request)
'title' => 'required|string|max:255', 'title' => 'required|string|max:255',
'caption' => 'nullable|string', 'caption' => 'nullable|string',
'type' => 'required|in:audio,video', 'type' => 'required|in:audio,video',
'category_id' => 'nullable|exists:categories,id', 'category_ids' => 'nullable|array',
'subcategory_id' => 'nullable|exists:sub_categories,id', 'category_ids.*' => 'integer|exists:categories,id',
'subcategory_ids' => 'nullable|array',
'subcategory_ids.*' => 'integer|exists:sub_categories,id',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'duration' => 'nullable|integer', 'duration' => 'nullable|integer',
'is_premium' => 'nullable|boolean', 'is_premium' => 'nullable|boolean',
@@ -31,8 +34,6 @@ public function store(Request $request)
'tags.*' => 'string', 'tags.*' => 'string',
]); ]);
$categoryId = $data['category_id'] ?? null;
$path = null; $path = null;
if ($request->hasFile('file')) { if ($request->hasFile('file')) {
$path = $request->file('file')->store('media', 'public'); $path = $request->file('file')->store('media', 'public');
@@ -46,12 +47,14 @@ public function store(Request $request)
'file_path' => $path, 'file_path' => $path,
'external_url' => $data['external_url'] ?? null, 'external_url' => $data['external_url'] ?? null,
'image_id' => $data['image_id'] ?? null, 'image_id' => $data['image_id'] ?? null,
'category_id' => $categoryId,
'subcategory_id' => $data['subcategory_id'] ?? null,
'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
]); ]);
$media->categories()->sync($data['category_ids'] ?? []);
$media->subCategories()->sync($data['subcategory_ids'] ?? []);
if (!empty($data['tags'])) { if (!empty($data['tags'])) {
$tagIds = []; $tagIds = [];
@@ -64,12 +67,12 @@ public function store(Request $request)
} }
return response()->json([ return response()->json([
'message' => 'Media created successfully', 'message' => 'Media created successfully',
'media' => $media->load(['image', 'category', 'subCategory', 'tags']), 'media' => $media->load(['image', 'categories', 'subCategories', 'tags']),
]); ]);
} }
public function index(Request $request) public function index(Request $request)
{ {
$query = Media::with(['image', 'category', 'subCategory', 'myNote', 'tags','comments']) $query = Media::with(['image', '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());
@@ -83,12 +86,16 @@ public function index(Request $request)
if ($request->filled('categories')) { if ($request->filled('categories')) {
$categories = explode(',', $request->categories); $categories = explode(',', $request->categories);
$query->whereIn('category_id', $categories); $query->whereHas('categories', function ($q) use ($categories) {
$q->whereIn('categories.id', $categories);
});
} }
if ($request->filled('subcategories')) { if ($request->filled('subcategories')) {
$subcategories = explode(',', $request->subcategories); $subcategories = explode(',', $request->subcategories);
$query->whereIn('subcategory_id', $subcategories); $query->whereHas('subCategories', function ($q) use ($subcategories) {
$q->whereIn('sub_categories.id', $subcategories);
});
} }
/* /*
@@ -164,9 +171,12 @@ public function index(Request $request)
$q->where('title', 'LIKE', "%$search%") $q->where('title', 'LIKE', "%$search%")
->orWhere('caption', 'LIKE', "%$search%") ->orWhere('caption', 'LIKE', "%$search%")
->orWhereHas('category', function ($c) use ($search) { ->orWhereHas('categories', function ($c) use ($search) {
$c->where('name', 'LIKE', "%$search%"); $c->where('name', 'LIKE', "%$search%");
}) })
->orWhereHas('subCategories', function ($s) use ($search) {
$s->where('name', 'LIKE', "%$search%");
})
->orWhereHas('tags', function ($t) use ($search) { ->orWhereHas('tags', function ($t) use ($search) {
$t->where('name', 'LIKE', "%$search%"); $t->where('name', 'LIKE', "%$search%");
}); });
@@ -266,21 +276,28 @@ public function filters(Request $request)
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
*/ */
$categories = Category::select( $visibleMedia = function ($q) {
'categories.id', $q->where(function ($inner) {
'categories.name', $inner->where('media.visibility', 'public')
DB::raw('COUNT(media.id) as media_count') ->orWhere('media.user_id', auth()->id());
) });
->leftJoin('media', function ($join) { };
$join->on('categories.id', '=', 'media.category_id')
->where(function ($q) { $categories = Category::query()
$q->where('media.visibility', 'public') ->withCount(['media as media_count' => $visibleMedia])
->orWhere('media.user_id', auth()->id());
});
})
->groupBy('categories.id', 'categories.name')
->orderByDesc('media_count') ->orderByDesc('media_count')
->get(); ->get(['id', 'name']);
/*
|--------------------------------------------------------------------------
| 1️⃣.5 Subcategories with media count
|--------------------------------------------------------------------------
*/
$subcategories = SubCategory::query()
->withCount(['media as media_count' => $visibleMedia])
->orderByDesc('media_count')
->get(['id', 'category_id', 'name']);
/* /*
@@ -314,8 +331,9 @@ public function filters(Request $request)
->get(); ->get();
return response()->json([ return response()->json([
'categories' => $categories, 'categories' => $categories,
'durations' => $durations, 'subcategories' => $subcategories,
'durations' => $durations,
]); ]);
} }
@@ -323,8 +341,8 @@ public function show($id)
{ {
$media = Media::with([ $media = Media::with([
'image', 'image',
'category', 'categories',
'subCategory', 'subCategories',
'myNote', 'myNote',
'tags', 'tags',
'comments' => function($query) { 'comments' => function($query) {
@@ -361,8 +379,8 @@ 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,
'category' => $media->category, 'categories' => $media->categories,
'sub_category' => $media->subCategory, 'sub_categories' => $media->subCategories,
'tags' => $media->tags, 'tags' => $media->tags,
'myNote' => $media->myNote, 'myNote' => $media->myNote,
'is_saved' => $media->is_saved, 'is_saved' => $media->is_saved,
@@ -444,8 +462,10 @@ public function update(Request $request, $id)
'caption' => 'nullable|string', 'caption' => 'nullable|string',
'type' => 'nullable|in:audio,video', 'type' => 'nullable|in:audio,video',
'category_id' => 'nullable|exists:categories,id', 'category_ids' => 'nullable|array',
'subcategory_id' => 'nullable|exists:sub_categories,id', 'category_ids.*' => 'integer|exists:categories,id',
'subcategory_ids' => 'nullable|array',
'subcategory_ids.*' => 'integer|exists:sub_categories,id',
'is_premium' => 'nullable|boolean', 'is_premium' => 'nullable|boolean',
'image_id' => 'nullable|exists:images,id', 'image_id' => 'nullable|exists:images,id',
'duration' => 'nullable|integer', 'duration' => 'nullable|integer',
@@ -456,8 +476,6 @@ public function update(Request $request, $id)
'tags.*' => 'string', 'tags.*' => 'string',
]); ]);
$categoryId = $data['category_id'] ?? $media->category_id;
// --- handle file replace --- // --- handle file replace ---
if ($request->hasFile('file')) { if ($request->hasFile('file')) {
Storage::disk('public')->delete($media->file_path); Storage::disk('public')->delete($media->file_path);
@@ -469,8 +487,6 @@ public function update(Request $request, $id)
'title' => $data['title'] ?? $media->title, 'title' => $data['title'] ?? $media->title,
'caption' => $data['caption'] ?? $media->caption, 'caption' => $data['caption'] ?? $media->caption,
'type' => $data['type'] ?? $media->type, 'type' => $data['type'] ?? $media->type,
'category_id' => $categoryId,
'subcategory_id' => array_key_exists('subcategory_id', $data) ? $data['subcategory_id'] : $media->subcategory_id,
'is_premium' => $data['is_premium'] ?? $media->is_premium, 'is_premium' => $data['is_premium'] ?? $media->is_premium,
'duration' => $data['duration'] ?? $media->duration, 'duration' => $data['duration'] ?? $media->duration,
'external_url' => $data['external_url'] ?? $media->external_url, 'external_url' => $data['external_url'] ?? $media->external_url,
@@ -490,6 +506,14 @@ public function update(Request $request, $id)
// --- update media --- // --- update media ---
$media->update($updateData); $media->update($updateData);
if (array_key_exists('category_ids', $data)) {
$media->categories()->sync($data['category_ids'] ?? []);
}
if (array_key_exists('subcategory_ids', $data)) {
$media->subCategories()->sync($data['subcategory_ids'] ?? []);
}
if (isset($data['tags'])) { if (isset($data['tags'])) {
$tagIds = []; $tagIds = [];
@@ -503,7 +527,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', 'category' , 'tags']), 'media' => $media->load(['image', 'categories', 'subCategories', 'tags']),
]); ]);
} }
@@ -540,7 +564,7 @@ public function toggleSaveMedia($id)
// GET saved // GET saved
public function saved() public function saved()
{ {
return auth()->user()->savedMedia()->with(['image','category', 'subCategory', 'myNote' , 'tags'])->get(); return auth()->user()->savedMedia()->with(['image','categories', 'subCategories', 'myNote' , 'tags'])->get();
} }
public function storeNote(Request $request, $mediaId) public function storeNote(Request $request, $mediaId)
+5
View File
@@ -17,5 +17,10 @@ public function subcategories()
{ {
return $this->hasMany(SubCategory::class); return $this->hasMany(SubCategory::class);
} }
public function media()
{
return $this->belongsToMany(Media::class, 'category_media');
}
} }
+4 -6
View File
@@ -13,8 +13,6 @@ class Media extends Model
protected $fillable = [ protected $fillable = [
'user_id', 'user_id',
'image_id', 'image_id',
'category_id',
'subcategory_id',
'title', 'title',
'caption', 'caption',
'type', 'type',
@@ -45,14 +43,14 @@ public function image()
return $this->belongsTo(Image::class); return $this->belongsTo(Image::class);
} }
public function category() public function categories()
{ {
return $this->belongsTo(Category::class); return $this->belongsToMany(Category::class, 'category_media');
} }
public function subCategory() public function subCategories()
{ {
return $this->belongsTo(SubCategory::class, 'subcategory_id'); return $this->belongsToMany(SubCategory::class, 'media_sub_category');
} }
public function tags() public function tags()
{ {
+3 -3
View File
@@ -4,7 +4,7 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class SubCategory extends Model class SubCategory extends Model
{ {
@@ -17,8 +17,8 @@ public function category(): BelongsTo
return $this->belongsTo(Category::class); return $this->belongsTo(Category::class);
} }
public function media(): HasMany public function media(): BelongsToMany
{ {
return $this->hasMany(Media::class, 'subcategory_id'); return $this->belongsToMany(Media::class, 'media_sub_category');
} }
} }
@@ -0,0 +1,79 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('category_media', function (Blueprint $table) {
$table->id();
$table->foreignId('media_id')->constrained('media')->cascadeOnDelete();
$table->foreignId('category_id')->constrained('categories')->cascadeOnDelete();
$table->timestamps();
$table->unique(['media_id', 'category_id']);
});
Schema::create('media_sub_category', function (Blueprint $table) {
$table->id();
$table->foreignId('media_id')->constrained('media')->cascadeOnDelete();
$table->foreignId('sub_category_id')->constrained('sub_categories')->cascadeOnDelete();
$table->timestamps();
$table->unique(['media_id', 'sub_category_id']);
});
// Backfill the new pivots from the existing single columns.
if (Schema::hasColumn('media', 'category_id')) {
DB::table('media')
->whereNotNull('category_id')
->orderBy('id')
->select('id', 'category_id')
->chunk(200, function ($rows) {
$now = now();
$insert = $rows->map(fn ($row) => [
'media_id' => $row->id,
'category_id' => $row->category_id,
'created_at' => $now,
'updated_at' => $now,
])->all();
DB::table('category_media')->insertOrIgnore($insert);
});
}
if (Schema::hasColumn('media', 'subcategory_id')) {
DB::table('media')
->whereNotNull('subcategory_id')
->orderBy('id')
->select('id', 'subcategory_id')
->chunk(200, function ($rows) {
$now = now();
$insert = $rows->map(fn ($row) => [
'media_id' => $row->id,
'sub_category_id' => $row->subcategory_id,
'created_at' => $now,
'updated_at' => $now,
])->all();
DB::table('media_sub_category')->insertOrIgnore($insert);
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('media_sub_category');
Schema::dropIfExists('category_media');
}
};
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('media', function (Blueprint $table) {
$table->dropForeign(['category_id']);
$table->dropColumn('category_id');
$table->dropForeign(['subcategory_id']);
$table->dropColumn('subcategory_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('media', function (Blueprint $table) {
$table->unsignedBigInteger('category_id')->nullable()->after('image_id');
$table->foreign('category_id')->references('id')->on('categories')->nullOnDelete();
$table->unsignedBigInteger('subcategory_id')->nullable()->after('category_id');
$table->foreign('subcategory_id')->references('id')->on('sub_categories')->nullOnDelete();
});
}
};