input('type') === Category::TYPE_PLAYLIST) { return response()->json($this->playlistCategories($request)); } $query = Category::query()->withCount('subcategories'); if ($request->filled('type')) { $query->where('type', $request->input('type')); } if ($request->boolean('with_subcategories')) { $query->with('subcategories'); } return response()->json( $query->orderBy('name')->get() ); } // 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)], '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, '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)], '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('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']); } }