Files
back-meditation/app/Http/Controllers/CategoryController.php
T
2026-06-27 10:13:59 +03:30

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']);
}
}