208 lines
6.8 KiB
PHP
208 lines
6.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Category;
|
|
use App\Models\MusicCategory;
|
|
use App\Traits\StoresUploads;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class CategoryController extends Controller
|
|
{
|
|
use StoresUploads;
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$type = $request->input('type');
|
|
|
|
// Playlist categories live in their own table (music_categories); serve
|
|
// them here too so the front has one /categories entry point.
|
|
if ($type === Category::TYPE_PLAYLIST) {
|
|
return response()->json($this->playlistCategories($request)->values());
|
|
}
|
|
|
|
$query = Category::query()->withCount('subcategories');
|
|
|
|
if ($request->filled('type')) {
|
|
$query->where('type', $type);
|
|
}
|
|
|
|
if ($request->boolean('with_subcategories')) {
|
|
$query->with('subcategories');
|
|
}
|
|
|
|
$categories = $query->orderBy('order')->orderBy('name')->get();
|
|
|
|
// No type filter → also fold in the playlist (music) categories so the
|
|
// response contains every category across both tables.
|
|
if (!$request->filled('type')) {
|
|
$categories = $categories
|
|
->concat($this->playlistCategories($request))
|
|
->sortBy([['order', 'asc'], ['name', 'asc']])
|
|
->values();
|
|
}
|
|
|
|
return response()->json($categories);
|
|
}
|
|
|
|
// Music categories normalized to the generic category shape.
|
|
private function playlistCategories(Request $request)
|
|
{
|
|
$query = MusicCategory::query()->with('image')->withCount('subcategories');
|
|
|
|
if (!$request->boolean('include_inactive')) {
|
|
$query->where('is_active', true);
|
|
}
|
|
|
|
if ($request->boolean('with_subcategories')) {
|
|
$query->with('subcategories.image');
|
|
}
|
|
|
|
return $query->orderBy('order')->get()->map(function ($c) use ($request) {
|
|
return [
|
|
'id' => $c->id,
|
|
'name' => $c->name,
|
|
'type' => Category::TYPE_PLAYLIST,
|
|
'description' => $c->description,
|
|
'icon' => $c->image?->path,
|
|
'icon_url' => $c->image?->url,
|
|
'order' => $c->order,
|
|
'is_active' => $c->is_active,
|
|
'subcategories_count' => $c->subcategories_count,
|
|
'subcategories' => $request->boolean('with_subcategories') ? $c->subcategories : null,
|
|
'created_at' => $c->created_at,
|
|
'updated_at' => $c->updated_at,
|
|
];
|
|
});
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$data = $request->validate([
|
|
'name' => [
|
|
'required',
|
|
'string',
|
|
'max:255',
|
|
Rule::unique('categories', 'name')->where('type', $request->input('type', Category::TYPE_MEDIA)),
|
|
],
|
|
'type' => ['nullable', Rule::in(Category::TYPES)],
|
|
'order' => 'nullable|integer',
|
|
'description' => 'nullable|string',
|
|
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
|
]);
|
|
|
|
$category = Category::create([
|
|
'name' => $data['name'],
|
|
'type' => $data['type'] ?? Category::TYPE_MEDIA,
|
|
'order' => $data['order'] ?? 0,
|
|
'description' => $data['description'] ?? null,
|
|
'icon' => $request->hasFile('icon')
|
|
? $this->storeUpload($request->file('icon'), 'categories/icons')
|
|
: null,
|
|
]);
|
|
|
|
return response()->json([
|
|
'message' => 'Category created successfully',
|
|
'category' => $category,
|
|
], 201);
|
|
}
|
|
|
|
public function show($id)
|
|
{
|
|
$category = Category::with('subcategories')->withCount('subcategories')->findOrFail($id);
|
|
|
|
return response()->json($category);
|
|
}
|
|
|
|
// Category with each of its sub-categories and that sub-category's medias.
|
|
public function grouped($id)
|
|
{
|
|
$category = Category::with(['subcategories' => function ($q) {
|
|
$q->orderBy('name')->with(['media' => function ($m) {
|
|
$m->with(['image', 'detailImage', 'tags'])->latest();
|
|
}]);
|
|
}])->findOrFail($id);
|
|
|
|
return response()->json([
|
|
'category' => [
|
|
'id' => $category->id,
|
|
'name' => $category->name,
|
|
'description' => $category->description,
|
|
'icon_url' => $category->icon_url,
|
|
'type' => $category->type,
|
|
],
|
|
'sub_categories' => $category->subcategories->map(fn ($sub) => [
|
|
'sub_category' => [
|
|
'id' => $sub->id,
|
|
'name' => $sub->name,
|
|
'description' => $sub->description,
|
|
],
|
|
'medias' => $sub->media,
|
|
])->values(),
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, $id)
|
|
{
|
|
$category = Category::findOrFail($id);
|
|
|
|
$targetType = $request->input('type', $category->type);
|
|
|
|
$data = $request->validate([
|
|
'name' => [
|
|
'sometimes',
|
|
'string',
|
|
'max:255',
|
|
Rule::unique('categories', 'name')
|
|
->where('type', $targetType)
|
|
->ignore($category->id),
|
|
],
|
|
'type' => ['nullable', Rule::in(Category::TYPES)],
|
|
'order' => 'nullable|integer',
|
|
'description' => 'nullable|string',
|
|
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
|
]);
|
|
|
|
if (array_key_exists('type', $data) && $data['type'] !== null) {
|
|
$category->type = $data['type'];
|
|
}
|
|
if (array_key_exists('order', $data) && $data['order'] !== null) {
|
|
$category->order = $data['order'];
|
|
}
|
|
if (array_key_exists('name', $data)) {
|
|
$category->name = $data['name'];
|
|
}
|
|
if (array_key_exists('description', $data)) {
|
|
$category->description = $data['description'];
|
|
}
|
|
if ($request->hasFile('icon')) {
|
|
if ($category->icon) {
|
|
Storage::disk('public')->delete($category->icon);
|
|
}
|
|
$category->icon = $this->storeUpload($request->file('icon'), 'categories/icons');
|
|
}
|
|
|
|
$category->save();
|
|
|
|
return response()->json([
|
|
'message' => 'Category updated successfully',
|
|
'category' => $category,
|
|
]);
|
|
}
|
|
|
|
public function destroy($id)
|
|
{
|
|
$category = Category::findOrFail($id);
|
|
|
|
if ($category->icon) {
|
|
Storage::disk('public')->delete($category->icon);
|
|
}
|
|
|
|
$category->delete();
|
|
|
|
return response()->json(['message' => 'Category deleted successfully']);
|
|
}
|
|
}
|