diff --git a/app/Http/Controllers/Api/Service/ProductCategoryController.php b/app/Http/Controllers/Api/Service/ProductCategoryController.php new file mode 100644 index 0000000..7c72020 --- /dev/null +++ b/app/Http/Controllers/Api/Service/ProductCategoryController.php @@ -0,0 +1,333 @@ +whereNull('parent_id') + ->with(['children' => function($query) { + $query->where('is_active', true) + ->orderBy('sort_order', 'asc'); + }]) + ->orderBy('sort_order', 'asc') + ->get(); + + return response()->json([ + 'error' => false, + 'message' => 'Lấy dữ liệu thành công', + 'data' => $categories, + ]); + } + + /** + * @OA\Get( + * path="/product-categories/{slug}", + * operationId="getProductCategoryDetail", + * tags={"Product Categories"}, + * summary="Get product category details", + * description="Returns detailed information about a specific product category with its products", + * @OA\Parameter( + * name="slug", + * in="path", + * description="Category slug", + * required=true, + * @OA\Schema(type="string") + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * @OA\Property(property="error", type="boolean", example=false), + * @OA\Property(property="message", type="string", example="Lấy dữ liệu thành công"), + * @OA\Property(property="data", type="object", + * @OA\Property(property="id", type="integer", example=1), + * @OA\Property(property="name", type="string", example="Cloud Services"), + * @OA\Property(property="slug", type="string", example="cloud-services"), + * @OA\Property(property="parent_id", type="integer", nullable=true, example=null), + * @OA\Property(property="is_active", type="boolean", example=true), + * @OA\Property(property="sort_order", type="integer", example=1), + * @OA\Property(property="children", type="array", @OA\Items()), + * @OA\Property(property="products", type="array", @OA\Items()) + * ) + * ), + * @OA\Response( + * response=404, + * description="Category not found" + * ) + * ) + * ) + */ + public function getDetailCategory($slug) + { + $category = ProductCategories::where('slug', $slug) + ->where('is_active', true) + ->with([ + 'children' => function($query) { + $query->where('is_active', true) + ->orderBy('sort_order', 'asc'); + } + ]) + ->first(); + + if (!$category) { + return response()->json([ + 'error' => true, + 'message' => 'Không tìm thấy danh mục', + ], 404); + } + + // Get products with full details + $products = Product::where('product_categories_id', $category->id) + ->where('is_active', true) + ->with([ + 'osVersions.osGroup', + 'category', + 'pricings' => fn($q) => $q->where('is_active', 1) + ]) + ->get(); + + $productResources = $products->map(function ($product) { + return [ + 'uuid' => $product->uuid, + 'name' => $product->name, + 'slug' => $product->slug, + 'short_description' => $product->short_description, + 'description' => $product->description, + 'thumbnail' => $product->thumbnail ? asset($product->thumbnail) : null, + 'product_type' => $product->product_type, + 'specs' => [ + 'cpu' => $product->cpu, + 'ram' => $product->ram, + 'disk' => $product->disk, + 'bandwidth' => $product->bandwidth, + 'gpu' => $product->gpu + ], + 'stock' => $product->stock, + 'price' => $product->price, + 'price_sale' => $product->price_sale, + 'group' => $product->category->name ?? null, + 'os' => $product->osVersions->groupBy('os_group_id')->map(function ($versions) { + $group = $versions->first()->osGroup; + return [ + 'name' => $group?->name, + 'thumbnail' => $group?->thumbnail, + 'versions' => $versions->map(fn($os) => [ + 'version' => $os->version, + 'name' => $os->name, + ])->values(), + ]; + })->values(), + 'pricing' => $product->pricings->map(fn($p) => [ + 'id' => $p->id, + 'name' => $p->name, + 'price' => $p->price, + 'price_sale' => $p->price_sale, + 'period' => $p->period, + 'period_unit' => $p->period_unit, + ]), + ]; + }); + + $categoryArray = $category->toArray(); + $categoryArray['products'] = $productResources; + + return response()->json([ + 'error' => false, + 'message' => 'Lấy dữ liệu thành công', + 'data' => $categoryArray, + ]); + } + + /** + * @OA\Get( + * path="/product-categories/{parentSlug}/{childSlug}", + * operationId="getChildCategoryWithParent", + * tags={"Product Categories"}, + * summary="Get child category with parent", + * description="Returns child category detail with parent information", + * @OA\Parameter( + * name="parentSlug", + * in="path", + * description="Parent category slug", + * required=true, + * @OA\Schema(type="string") + * ), + * @OA\Parameter( + * name="childSlug", + * in="path", + * description="Child category slug", + * required=true, + * @OA\Schema(type="string") + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * @OA\Property(property="error", type="boolean", example=false), + * @OA\Property(property="message", type="string", example="Lấy dữ liệu thành công"), + * @OA\Property(property="data", type="object", + * @OA\Property(property="id", type="integer", example=3), + * @OA\Property(property="name", type="string", example="VPS Windows VN"), + * @OA\Property(property="slug", type="string", example="vps-windows-vn"), + * @OA\Property(property="parent_id", type="integer", example=1), + * @OA\Property(property="is_active", type="boolean", example=true), + * @OA\Property(property="sort_order", type="integer", example=0), + * @OA\Property(property="parent", type="object", + * @OA\Property(property="id", type="integer", example=1), + * @OA\Property(property="name", type="string", example="VPS Windows"), + * @OA\Property(property="slug", type="string", example="vps-windows") + * ), + * @OA\Property(property="products", type="array", @OA\Items()) + * ) + * ), + * @OA\Response( + * response=404, + * description="Category not found" + * ) + * ) + * ) + */ + public function getChildCategoryWithParent($parentSlug, $childSlug) + { + // Find parent category + $parent = ProductCategories::where('slug', $parentSlug) + ->where('is_active', true) + ->whereNull('parent_id') + ->first(); + + if (!$parent) { + return response()->json([ + 'error' => true, + 'message' => 'Không tìm thấy danh mục cha', + ], 404); + } + + // Find child category + $child = ProductCategories::where('slug', $childSlug) + ->where('is_active', true) + ->where('parent_id', $parent->id) + ->with([ + 'parent' => function($query) { + $query->select('id', 'name', 'slug', 'parent_id', 'is_active', 'sort_order'); + } + ]) + ->first(); + + if (!$child) { + return response()->json([ + 'error' => true, + 'message' => 'Không tìm thấy danh mục con', + ], 404); + } + + // Get products with full details (like ServiceController::getDetailService) + $products = Product::where('product_categories_id', $child->id) + ->where('is_active', true) + ->with([ + 'osVersions.osGroup', + 'category', + 'pricings' => fn($q) => $q->where('is_active', 1) + ]) + ->get(); + + $productResources = $products->map(function ($product) { + return [ + 'uuid' => $product->uuid, + 'name' => $product->name, + 'slug' => $product->slug, + 'short_description' => $product->short_description, + 'description' => $product->description, + 'thumbnail' => $product->thumbnail ? asset($product->thumbnail) : null, + 'product_type' => $product->product_type, + 'specs' => [ + 'cpu' => $product->cpu, + 'ram' => $product->ram, + 'disk' => $product->disk, + 'bandwidth' => $product->bandwidth, + 'gpu' => $product->gpu + ], + 'stock' => $product->stock, + 'price' => $product->price, + 'price_sale' => $product->price_sale, + 'group' => $product->category->name ?? null, + 'os' => $product->osVersions->groupBy('os_group_id')->map(function ($versions) { + $group = $versions->first()->osGroup; + return [ + 'name' => $group?->name, + 'thumbnail' => $group?->thumbnail, + 'versions' => $versions->map(fn($os) => [ + 'version' => $os->version, + 'name' => $os->name, + ])->values(), + ]; + })->values(), + 'pricing' => $product->pricings->map(fn($p) => [ + 'id' => $p->id, + 'name' => $p->name, + 'price' => $p->price, + 'price_sale' => $p->price_sale, + 'period' => $p->period, + 'period_unit' => $p->period_unit, + ]), + ]; + }); + + $childArray = $child->toArray(); + $childArray['parent'] = $child->parent ? $child->parent->toArray() : null; + $childArray['products'] = $productResources; + + return response()->json([ + 'error' => false, + 'message' => 'Lấy dữ liệu thành công', + 'data' => $childArray, + ]); + } +} diff --git a/config/cors.php b/config/cors.php index 8a39e6d..0966399 100644 --- a/config/cors.php +++ b/config/cors.php @@ -19,7 +19,7 @@ return [ 'allowed_methods' => ['*'], - 'allowed_origins' => ['*'], + 'allowed_origins' => ['*', 'http://localhost:5173'], 'allowed_origins_patterns' => [], diff --git a/routes/client.php b/routes/client.php index 786066a..fda0bb1 100644 --- a/routes/client.php +++ b/routes/client.php @@ -3,6 +3,7 @@ use App\Http\Controllers\Api\Setting\SettingController; use App\Http\Controllers\Api\Client\ClientController; use App\Http\Controllers\Api\Service\OrderController; +use App\Http\Controllers\Api\Service\ProductCategoryController; use App\Http\Controllers\Api\Service\ServiceController; use Illuminate\Support\Facades\Route; @@ -42,6 +43,15 @@ Route::prefix('v1')->group(function () { Route::get('/', 'getSetting')->name('get'); }); }); + + // Product Categories - Protected Routes + Route::middleware(['api.auth'])->prefix('group')->as('product-categories.')->group(function () { + Route::controller(ProductCategoryController::class)->group(function () { + Route::get('get-list', 'getCategories')->name('getList'); + Route::get('{product_categories:slug}', 'getDetailCategory')->name('detail'); + Route::get('{parentSlug}/{childSlug}', 'getChildCategoryWithParent')->name('childWithParent'); + }); + }); });