diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index 82ad96a..3d76e01 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -3,10 +3,14 @@ namespace App\Http\Controllers; use App\Models\Category; +use App\Traits\StoresUploads; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; class CategoryController extends Controller { + use StoresUploads; + public function index(Request $request) { $query = Category::query()->withCount('subcategories'); @@ -24,9 +28,17 @@ public function store(Request $request) { $data = $request->validate([ 'name' => 'required|string|max:255|unique:categories,name', + 'description' => 'nullable|string', + 'icon' => 'nullable|image|max:8192', ]); - $category = Category::create($data); + $category = Category::create([ + 'name' => $data['name'], + 'description' => $data['description'] ?? null, + 'icon' => $request->hasFile('icon') + ? $this->storeUpload($request->file('icon'), 'categories/icons') + : null, + ]); return response()->json([ 'message' => 'Category created successfully', @@ -46,10 +58,25 @@ public function update(Request $request, $id) $category = Category::findOrFail($id); $data = $request->validate([ - 'name' => 'required|string|max:255|unique:categories,name,' . $category->id, + 'name' => 'sometimes|string|max:255|unique:categories,name,' . $category->id, + 'description' => 'nullable|string', + 'icon' => 'nullable|image|max:8192', ]); - $category->update($data); + 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', @@ -60,6 +87,11 @@ public function update(Request $request, $id) 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']); diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php index 40aefbe..c740e00 100644 --- a/app/Http/Controllers/MediaController.php +++ b/app/Http/Controllers/MediaController.php @@ -299,7 +299,7 @@ public function filters(Request $request) $categories = Category::query() ->withCount(['media as media_count' => $visibleMedia]) ->orderByDesc('media_count') - ->get(['id', 'name']); + ->get(['id', 'name', 'description', 'icon']); /* |-------------------------------------------------------------------------- @@ -310,7 +310,7 @@ public function filters(Request $request) $subcategories = SubCategory::query() ->withCount(['media as media_count' => $visibleMedia]) ->orderByDesc('media_count') - ->get(['id', 'category_id', 'name']); + ->get(['id', 'category_id', 'name', 'description']); /* diff --git a/app/Http/Controllers/SubCategoryController.php b/app/Http/Controllers/SubCategoryController.php index 17e2e0d..0d43bcd 100644 --- a/app/Http/Controllers/SubCategoryController.php +++ b/app/Http/Controllers/SubCategoryController.php @@ -27,6 +27,7 @@ public function store(Request $request) $data = $request->validate([ 'category_id' => 'required|exists:categories,id', 'name' => 'required|string|max:255', + 'description' => 'nullable|string', ]); $existing = SubCategory::where('category_id', $data['category_id']) @@ -62,6 +63,7 @@ public function update(Request $request, $id) $data = $request->validate([ 'category_id' => 'sometimes|exists:categories,id', 'name' => 'sometimes|string|max:255', + 'description' => 'nullable|string', ]); $categoryId = $data['category_id'] ?? $subCategory->category_id; diff --git a/app/Models/Category.php b/app/Models/Category.php index 9e7ed7b..7d2e08e 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -6,7 +6,14 @@ class Category extends Model { - protected $fillable = ['name']; + protected $fillable = ['name', 'description', 'icon']; + + protected $appends = ['icon_url']; + + public function getIconUrlAttribute(): ?string + { + return $this->icon ? asset('storage/' . $this->icon) : null; + } public function questions() { diff --git a/app/Models/SubCategory.php b/app/Models/SubCategory.php index 10dc58d..0ee3f16 100644 --- a/app/Models/SubCategory.php +++ b/app/Models/SubCategory.php @@ -10,7 +10,7 @@ class SubCategory extends Model { protected $table = 'sub_categories'; - protected $fillable = ['category_id', 'name']; + protected $fillable = ['category_id', 'name', 'description']; public function category(): BelongsTo { diff --git a/database/migrations/2026_06_03_110000_add_description_and_icon_to_categories_table.php b/database/migrations/2026_06_03_110000_add_description_and_icon_to_categories_table.php new file mode 100644 index 0000000..135bbd8 --- /dev/null +++ b/database/migrations/2026_06_03_110000_add_description_and_icon_to_categories_table.php @@ -0,0 +1,29 @@ +text('description')->nullable()->after('name'); + $table->string('icon')->nullable()->after('description'); // stored icon image path + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('categories', function (Blueprint $table) { + $table->dropColumn(['description', 'icon']); + }); + } +}; diff --git a/database/migrations/2026_06_03_110100_add_description_to_sub_categories_table.php b/database/migrations/2026_06_03_110100_add_description_to_sub_categories_table.php new file mode 100644 index 0000000..ac892c8 --- /dev/null +++ b/database/migrations/2026_06_03_110100_add_description_to_sub_categories_table.php @@ -0,0 +1,28 @@ +text('description')->nullable()->after('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('sub_categories', function (Blueprint $table) { + $table->dropColumn('description'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 57fe068..ae354ed 100644 --- a/routes/api.php +++ b/routes/api.php @@ -222,6 +222,8 @@ Route::post('/media/{id}/note', [MediaController::class, 'storeNote']); // Media categories + // Explicit POST update so an icon image can be uploaded (multipart can't ride a PUT). + Route::post('categories/{id}', [CategoryController::class, 'update']); Route::apiResource('categories', CategoryController::class); // Media sub categories