Clean up documentation and infrastructure configuration files, and refine API resources and controllers for products and services.

This commit is contained in:
pbao9
2026-03-09 14:18:04 +07:00
parent 7b3c955d38
commit 80a5e0ca3d
13 changed files with 390 additions and 2656 deletions
-174
View File
@@ -1,174 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
Laravel 10 backend for a VPS/hosting SaaS platform. Provides an admin panel (Livewire), a customer REST API (JWT), and an admin REST API (JWT + Passport), with deep VMware vCenter integration for VM provisioning.
---
## Common Commands
```bash
# Dev server
php artisan serve
# Migrations & seeding
php artisan migrate
php artisan db:seed --class=SomeSeeder
# Clear all caches
php artisan cache:clear && php artisan config:clear && php artisan route:clear && php artisan view:clear
# Queue
php artisan queue:work
php artisan horizon
# Tests
php artisan test
php artisan test tests/Feature/SomeTest.php
php artisan test --filter testMethodName
# Swagger docs regeneration
php artisan l5-swagger:generate
```
---
## Routing
Four route files, each serving a different audience:
| File | Domain/Prefix | Auth | Audience |
|------|---------------|------|----------|
| `routes/admin.php` | `admin.portal.test` | Session (`auth` + `Staff`) | Admin panel web UI |
| `routes/api.php` | `/api/v1/administrator/` | JWT (`admins-api` guard) | Admin REST API |
| `routes/client.php` | `/api/v1/client/` | JWT (`client` guard) | Customer REST API |
| `routes/web.php` | `/` | None | Public pages |
**Middleware aliases:**
- `Staff` → `StaffCheck` — enforces admin session, updates `last_visited`
- `api.auth` → `ApiAuthCheck` — JWT validation, returns JSON 401 on failure
- `LoginCheck` — redirects authenticated users away from login page
---
## Architecture
### Controller Layers
```
app/Http/Controllers/
├── admin/ — Admin panel web controllers (Blade/Livewire views)
├── Api/Admin/ — Admin REST API controllers
├── Api/Client/ — Customer REST API (ClientController)
├── Api/Service/ — Product/order/service endpoints (used by both)
├── Api/Transaction/— Payment and transaction endpoints
└── rest/ — Legacy internal REST endpoints
```
### Repository Pattern
All DB access goes through repositories. Never query models directly in controllers.
```
app/Repositories/{Domain}/
├── {Domain}Repository.php — Concrete (extends BaseRepository)
├── {Domain}RepositoryInterface.php — Interface
└── {Domain}CachedRepository.php — Redis-cached variant (optional)
```
**BaseRepository** provides: `create`, `update`, `delete`, `find`, `getAll`, `getCount`, `insert`.
Repositories are bound in `AppServiceProvider` → `RepositoryProvider` (40+ bindings). Always inject via interface.
### Service Layer
Business logic is split across two locations:
- `app/Services/` — Domain services: `VMDataService`, `VMHelperService`, `WhoisService`
- `app/Http/Services/` — HTTP-integration services:
- `vCenter.php` — VMware vCenter REST API wrapper (Guzzle, SSL disabled for self-signed certs). Key methods: `loginServer()`, `GetConsoleTickets()`, `getAllVirtualMachine()`, `checkApiExits()`. Token stored in model, refreshed on 'Login_Again' response.
- `WinRMClient.php` — Windows Remote Management for VM operations
- `VNPayService.php` — Payment gateway
- `Invoice/` — Invoice generation/processing
- `PusherNotification.php`, `Telegram.php`, `Discord.php` — Notifications
Dedicated admin service layer: `app/Services/Api/Admin/` (`ProductService`, `VCenterService`), `app/Services/Api/` (`ClientAuthService`, `ServiceService`).
### Global Helpers
`app/Http/Helpers/function_helper.php` (auto-loaded via Composer):
- `setting('group.key')` — Read cached settings with dot notation
- `format_price(amount)` — Format as ₫ currency
- `format_point(points)` — Format loyalty points
- `format_day(date, format)` — Date formatting
### Enums
Type-safe PHP enums in `app/Enums/{Domain}/`. Support `label()` for Vietnamese display text. Key enums: `InvoiceStatus`, `ProductTypeEnums`, `TransactionStatus`, `Status`, `AddonType`.
### Admin UI
Livewire 3 components in `app/Http/Livewire/`. Modal pattern uses Alpine.js events:
- Open: `$dispatch('open-{modal-name}')` / listen with `@open-{modal-name}.window`
- Close: `$dispatch('close-{modal-name}')` / listen with `@close-{modal-name}.window`
Server-side tables use Yajra DataTables.
---
## Auth Guards
| Guard | Driver | Used By |
|-------|--------|---------|
| `web` / `admins` | session | Admin panel |
| `admins-api` | JWT (tymon/jwt-auth) | Admin REST API |
| `client` | JWT (tymon/jwt-auth) | Customer API (portal-client) |
| `users-api` | Passport OAuth2 | — |
| `client-sanctum` | Sanctum | Alternative customer auth |
---
## Key Models
**Core business:** `Customer` (UUID PK, JWT subject), `Order`, `Invoice` (InvoiceStatus enum), `InvoiceItem`, `Product` (slug, cpu/ram/disk/bandwidth/gpu specs), `ProductCategories` (hierarchical via kalnoy/nestedset).
**VMware:** `vCenter` (credentials + token), `ModuleVmware` (VM instances → orders), `ModuleHost`, `ModuleDatacenter`, `ModuleNetwork`, `ModuleIpAddress`, `ModuleDatastore`, `ModuleSnapshot`.
**Finance:** `Transaction`, `Payment`, `Cart`, `CartAddon`.
**Support:** `Ticket`, `TicketReply`, `Mail`.
---
## Queue Jobs
Async operations in `app/Jobs/`:
- `VirtualMachine/ProcessOrderPaymentJob` — Payment processing + VM provisioning
- `Hosting/`, `Mail/`, `Ticket/`, `Payment/` — Domain-specific async tasks
---
## API Response Convention
```json
{ "error": false, "data": {...} }
{ "error": true, "message": "Reason here" }
```
API Resources in `app/Http/Resources/Api/` transform models before response.
---
## Key Integrations
- **VMware vCenter** — `app/Http/Services/vCenter.php`, passwords decrypted via `SignRSA` facade
- **VNPay** — `config/vnpay.php`, `VNPayService.php`
- **Redis** — Caching (cached repositories), sessions, queues
- **Pusher** — Real-time events
- **CKFinder** — Admin file browser (custom auth: `CustomCKFinderAuth` middleware)
- **L5-Swagger** — API docs at `/api/documentation`
- **Spatie Permission** — RBAC for admin roles
- **Horizon** — Queue monitoring at `/horizon`
@@ -53,27 +53,36 @@ class ProductController extends Controller
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'product_type' => 'nullable|string',
'category' => 'nullable|string|max:255',
'is_active' => 'boolean',
'setup_fee' => 'nullable|numeric|min:0',
'cpu' => 'nullable|numeric|min:0',
'ram' => 'nullable|numeric|min:0',
'disk' => 'nullable|numeric|min:0',
'bandwidth' => 'nullable|numeric|min:0',
'name' => 'required|string|max:255',
'product_categories_id' => 'nullable|integer|exists:product_categories,id',
'product_type' => 'nullable|string',
'category' => 'nullable|string|max:255',
'is_active' => 'boolean',
'setup_fee' => 'nullable|numeric|min:0',
'cpu' => 'nullable|numeric|min:0',
'ram' => 'nullable|numeric|min:0',
'disk' => 'nullable|numeric|min:0',
'bandwidth' => 'nullable|numeric|min:0',
]);
$productType = $validated['product_type'] ?? null;
if (!$productType && !empty($validated['product_categories_id'])) {
$category = \App\Models\ProductCategories::find($validated['product_categories_id']);
$productType = $category?->type;
}
$product = Product::create([
'name' => $validated['name'],
'slug' => Str::slug($validated['name']),
'product_type' => $validated['product_type'] ?? null,
'is_active' => $validated['is_active'] ?? true,
'setup_fee' => $validated['setup_fee'] ?? 0,
'cpu' => $validated['cpu'] ?? null,
'ram' => $validated['ram'] ?? null,
'disk' => $validated['disk'] ?? null,
'bandwidth' => $validated['bandwidth'] ?? null,
'name' => $validated['name'],
'slug' => Str::slug($validated['name']),
'product_categories_id' => $validated['product_categories_id'] ?? null,
'product_type' => $productType,
'is_active' => $validated['is_active'] ?? true,
'setup_fee' => $validated['setup_fee'] ?? 0,
'cpu' => $validated['cpu'] ?? null,
'ram' => $validated['ram'] ?? null,
'disk' => $validated['disk'] ?? null,
'bandwidth' => $validated['bandwidth'] ?? null,
]);
return response()->json([
@@ -92,20 +101,26 @@ class ProductController extends Controller
}
$validated = $request->validate([
'name' => 'sometimes|required|string|max:255',
'product_type' => 'nullable|string',
'is_active' => 'boolean',
'setup_fee' => 'nullable|numeric|min:0',
'cpu' => 'nullable|numeric|min:0',
'ram' => 'nullable|numeric|min:0',
'disk' => 'nullable|numeric|min:0',
'bandwidth' => 'nullable|numeric|min:0',
'name' => 'sometimes|required|string|max:255',
'product_categories_id' => 'nullable|integer|exists:product_categories,id',
'product_type' => 'nullable|string',
'is_active' => 'boolean',
'setup_fee' => 'nullable|numeric|min:0',
'cpu' => 'nullable|numeric|min:0',
'ram' => 'nullable|numeric|min:0',
'disk' => 'nullable|numeric|min:0',
'bandwidth' => 'nullable|numeric|min:0',
]);
if (isset($validated['name']) && $validated['name'] !== $product->name) {
$validated['slug'] = Str::slug($validated['name']);
}
if (empty($validated['product_type']) && !empty($validated['product_categories_id'])) {
$category = \App\Models\ProductCategories::find($validated['product_categories_id']);
$validated['product_type'] = $category?->type;
}
$product->update($validated);
return response()->json([
@@ -18,6 +18,7 @@ class ProductCategoryResource extends JsonResource
'sort_order' => $this->sort_order,
'type' => $this->type,
'type_label' => $this->type_label,
'page_type' => $this->resolvePageType($this->type),
'created_at' => $this->created_at?->toISOString(),
'children' => $this->whenLoaded('children', fn () =>
self::collection($this->children)
@@ -27,4 +28,15 @@ class ProductCategoryResource extends JsonResource
),
];
}
private function resolvePageType(?string $type): string
{
return match ($type) {
'vps', 'cloud' => 'service_vps',
'hosting', 'email', 'ssl' => 'service_hosting',
'domain' => 'service_domain',
'blog' => 'blog',
default => 'service_vps',
};
}
}
@@ -19,6 +19,7 @@ class ProductResource extends JsonResource
'name' => $this->name,
'slug' => $this->slug,
'product_type' => $this->product_type,
'page_type' => $this->resolvePageType($this->product_type),
'category' => $this->whenLoaded('category', fn() => $this->category?->name),
'cpu' => $this->cpu,
'ram' => $this->ram,
@@ -30,4 +31,15 @@ class ProductResource extends JsonResource
'created_at' => $this->created_at,
];
}
private function resolvePageType(?string $productType): string
{
return match ($productType) {
'vps', 'cloud' => 'service_vps',
'hosting', 'email', 'ssl' => 'service_hosting',
'domain' => 'service_domain',
'blog' => 'blog',
default => 'service_vps',
};
}
}
@@ -18,9 +18,21 @@ class ProductCategoryResource extends JsonResource
'sort_order' => $this->sort_order,
'type' => $this->type,
'type_label' => $this->type_label,
'page_type' => $this->resolvePageType($this->type),
'children' => $this->when($this->children, function () {
return self::collection($this->children);
}),
];
}
private function resolvePageType(?string $type): string
{
return match ($type) {
'vps', 'cloud' => 'service_vps',
'hosting', 'email', 'ssl' => 'service_hosting',
'domain' => 'service_domain',
'blog' => 'blog',
default => 'service_vps',
};
}
}
@@ -22,6 +22,7 @@ class ServiceListResource extends JsonResource
'description' => $this->description,
'thumbnail' => $this->thumbnail ? asset($this->thumbnail) : null,
'product_type' => $this->product_type,
'page_type' => $this->resolvePageType($this->product_type),
'specs' => [
'cpu' => $this->cpu,
'ram' => $this->ram,
@@ -46,4 +47,15 @@ class ServiceListResource extends JsonResource
})->unique('group')->values(),
];
}
private function resolvePageType(?string $productType): string
{
return match ($productType) {
'vps', 'cloud' => 'service_vps',
'hosting', 'email', 'ssl' => 'service_hosting',
'domain' => 'service_domain',
'blog' => 'blog',
default => 'service_vps',
};
}
}
@@ -38,6 +38,7 @@ class ServiceResource extends JsonResource
'description' => $this->description,
'thumbnail' => $this->thumbnail ? asset($this->thumbnail) : null,
'product_type' => $this->product_type,
'page_type' => $this->resolvePageType($this->product_type),
'specs' => [
'cpu' => (int) $this->cpu,
'ram' => (int) $this->ram,
@@ -77,4 +78,15 @@ class ServiceResource extends JsonResource
]),
];
}
private function resolvePageType(?string $productType): string
{
return match ($productType) {
'vps', 'cloud' => 'service_vps',
'hosting', 'email', 'ssl' => 'service_hosting',
'domain' => 'service_domain',
'blog' => 'blog',
default => 'service_vps',
};
}
}
+2 -7
View File
@@ -11,7 +11,6 @@
"php": "^8.1 || ^8.2",
"ext-openssl": "*",
"ext-ssh2": "*",
"ckfinder/ckfinder-laravel-package": "^5.0",
"darkaonline/l5-swagger": "^8.6",
"developermithu/tallcraftui": "^2.0",
"guzzlehttp/guzzle": "^7.2",
@@ -21,7 +20,6 @@
"laravel/passport": "^12.2",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.7",
"livewire/livewire": "^3.6",
"maatwebsite/excel": "^3.1",
"opcodesio/log-viewer": "^3.18",
"phpseclib/phpseclib": "*",
@@ -29,12 +27,9 @@
"pusher/pusher-php-server": "^7.2",
"spatie/laravel-permission": "^6.3",
"spatie/laravel-sluggable": "^3.7",
"tymon/jwt-auth": "^2.0",
"yajra/laravel-datatables": "^10.0",
"yajra/laravel-datatables-oracle": "^10.3.1"
"tymon/jwt-auth": "^2.0"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.9",
"fakerphp/faker": "^1.9.1",
"laravel/sail": "^1.0.1",
"mockery/mockery": "^1.4.4",
@@ -84,4 +79,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
Generated
+287 -1985
View File
File diff suppressed because it is too large Load Diff
-73
View File
@@ -1,73 +0,0 @@
version: '3.8'
services:
app:
build:
context: .
dockerfile: docker/Dockerfile
image: portal
container_name: portal
restart: unless-stopped
working_dir: /var/www
volumes:
- .:/var/www
networks:
- laravel_network
depends_on:
- redis
mysql:
image: mysql:8.0
container_name: mysql_portal
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: portal
MYSQL_USER: admin
MYSQL_PASSWORD: root123
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
networks:
- laravel_network
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin_portal
environment:
PMA_HOST: mysql
MYSQL_ROOT_PASSWORD: root
ports:
- "8080:80"
networks:
- laravel_network
nginx:
image: nginx:alpine
container_name: nginx_portal
ports:
- "80:80"
volumes:
- .:/var/www
- ./docker/nginx.conf:/etc/nginx/nginx.conf
depends_on:
- app
networks:
- laravel_network
redis:
image: redis:alpine
container_name: laravel_redis_portal
restart: unless-stopped
ports:
- "6379:6379"
networks:
- laravel_network
networks:
laravel_network:
driver: bridge
volumes:
mysql_data:
driver: local
-32
View File
@@ -1,32 +0,0 @@
{
"authHost": "http://client.test",
"authEndpoint": "/broadcasting/auth",
"clients": [],
"database": "redis",
"databaseConfig": {
"redis": {},
"sqlite": {
"databasePath": "/database/laravel-echo-server.sqlite"
}
},
"devMode": true,
"host": null,
"port": "6001",
"protocol": "http",
"socketio": {},
"secureOptions": 67108864,
"sslCertPath": "",
"sslKeyPath": "",
"sslCertChainPath": "",
"sslPassphrase": "",
"subscribers": {
"http": true,
"redis": true
},
"apiOriginAllow": {
"allowCors": false,
"allowOrigin": "",
"allowMethods": "",
"allowHeaders": ""
}
}
-125
View File
@@ -1,125 +0,0 @@
# Performance & Production Readiness Audit
**Date:** 2026-01-27
**Auditor:** Antigravity (AI)
**Target:** Laravel 10 / PHP 8.1+ / MySQL / Redis
---
## 1. CRITICAL PERFORMANCE RISKS (Must fix before production)
### 🔴 Synchronous vCenter Calls in Livewire
**File:** `app/Http/Livewire/VPSReinstallModal.php`
**Description:** The `getIPFromVCenter()` method calls `vCenterService->loginServer()` and `GetIPAddressVirtualMachine` **synchronously** during component initialization (`loadOsData`).
**Risk:** If vCenter is slow (common) or down, the user's browser will hang indefinitely/timeout when opening the modal. This blocks the PHP-FPM worker.
**Fix:**
- Move vCenter lookup to a background Queue Job.
- Use `wire:poll` or Echo to check for the IP status after the modal opens.
- **Immediate:** Wrap the call in a short timeout or remove it from the read path if not strictly necessary for the initial view.
### 🔴 Self-Calling HTTP Requests (Loopback)
**File:** `app/Http/Livewire/VPSReinstallModal.php`
**Description:** `confirmReinstall` uses `Http::withoutVerifying()->post(route('service.reinstall.vm')...)` to trigger logic.
**Risk:**
1. **Ineufficiency:** It consumes an extra PHP-FPM worker for the loopback request (Request A waits for Request B).
2. **Security:** `withoutVerifying()` bypasses SSL checks.
**Fix:** Refactor the logic inside `ServiceController::reinstallVM` into a `ReinstallService` and call it directly from the Livewire component.
### 🔴 Job-within-Job Synchronous Execution
**File:** `app/Jobs/VirtualMachine/ProcessOrderPaymentJob.php`
**Description:** `ProcessOrderPaymentJob` instantiates `CreateVirtualMachineJob` and calls `$vmJob->handle(...)` directly.
**Risk:** If `CreateVirtualMachineJob` takes long (e.g., 2 minutes for vCenter), `ProcessOrderPaymentJob` might timeout (default 60s/90s). If creating 5 VMs, it will surely fail.
**Fix:**
- Dispatch `CreateVirtualMachineJob::dispatch(...)` to the queue.
- Let `ProcessOrderPaymentJob` only *initiate* the process or use a `Batch` to monitor progress.
---
## 2. DATABASE PERFORMANCE REVIEW
### ⚠️ `Select *` in Repositories
**File:** `app/Repositories/BaseCachedRepository.php`
**Description:** `getAll()` uses `parent::getAll()` which likely does `$model->all()`.
**Risk:** Tables like `vps` (with `text` description) or `user_logs` will eventually destroy memory when `getAll()` is called and cached.
**Fix:**
- Remove `getAll()` from `BaseCachedRepository`.
- Implement `paginate()` by default.
- Use `select(['id', 'name', ...])` for lists.
### ⚠️ Missing Explicit Indices
**File:** `database/migrations/2026_01_27_151851_update_forigin_key_module_ip_table.php`
**Description:** FKs added to `module_host_id` and `module_network_id`. While MySQL often indexes FKs, explicit indexing ensures query performance for `where('module_host_id', $id)`.
**Fix:** Verify indices exist or add `$table->index('module_host_id')`.
---
## 3. LIVEWIRE & FRONTEND PERFORMANCE
### ⚠️ Heavy Mount Logic
**File:** `app/Http/Livewire/VPSReinstallModal.php`
**Description:** `loadOsData` performs multiple DB queries (`VPS::with`, `OsGroup::with` with closures) and checking Invoice items.
**Fix:** Use **Lazy Loading** (`wire:init` or `lazy` attribute in Livewire 3) to load heavy data *after* the modal opens.
---
## 4. QUEUE, REDIS & BACKGROUND JOBS
### ⚠️ Queue Blocking
**Description:** `ProcessOrderPaymentJob` combines "Payment Logic" (fast) with "VM Creation" (slow).
**Fix:**
- Separate queues: `queue:work --queue=high,default,low`.
- Put `CreateVirtualMachineJob` on `low` or `provisioning` queue.
- Keep `ProcessOrderPaymentJob` on `high` to update payment status quickly.
---
## 5. CACHING STRATEGY REVIEW
### 🔴 Dangerous 'getAll' Cache
**File:** `app/Repositories/BaseCachedRepository.php`
**Description:** Caches the *entire table result* of `getAll()`.
**Risk:** Redis memory exhaustion.
**Fix:**
- **Delete** `getAll` caching logic.
- Cache *individual* items by ID (`find($id)` is fine).
- Cache configuration/settings, not transactional data lists.
---
## 6. SECURITY & PRODUCTION CONFIG CHECK
### ⚠️ SSL Verification Disabled
**File:** `app/Http/Livewire/VPSReinstallModal.php`
**Description:** `Http::withoutVerifying()` is used.
**Fix:** Fix the root cause (self-signed certs?) or use internal service calls instead of HTTP loopbacks.
### ⚠️ Sensitive Logging
**File:** `app/Jobs/VirtualMachine/ProcessOrderPaymentJob.php`
**Description:** Sends detailed error logs to Telegram.
**Risk:** Ensure `trace` or `message` doesn't contain API keys / credit card info (unlikely in exception message, but possible).
---
## 7. SCALABILITY BOTTLENECKS
### At 1k Users
- `BaseCachedRepository::getAll()` will start causing 500 errors (OOM).
### At 10k Users
- Synchronous vCenter calls will cause "No child processes" in PHP-FPM during traffic spikes.
- Redis will fill up if `getAll` isn't removed.
---
## 8. FINAL PRODUCTION CHECKLIST
### Must Fix Before Launch
- [ ] **Refactor**: Remove `Http::withoutVerifying()` loopback in `VPSReinstallModal`.
- [ ] **Async**: Move `getIPFromVCenter` to background or make it lazy/optional.
- [ ] **Cache**: Disable `getAll()` caching in `BaseCachedRepository`.
- [ ] **Queue**: Decouple `CreateVirtualMachineJob` from `ProcessOrderPaymentJob` (dispatch don't call handle).
### Should Fix Soon
- [ ] **Database**: Add explicit indices for searching fields in `vps` and `orders`.
- [ ] **Refactor**: Move logic from Controllers/Livewire to Services to avoid loopback HTTP calls.
-234
View File
@@ -1,234 +0,0 @@
# REST API Rules Documentation
## Overview
Chuyển đổi từ Laravel Views sang REST API sử dụng JWT Authentication.
## Base URL
```
/api/v1
```
## Authentication
- **Admin API**: Sử dụng guard `admins-api` với JWT
- **Client API**: Sử dụng guard `client` với JWT (đã có sẵn)
## Headers
```
Authorization: Bearer <token>
Accept: application/json
Content-Type: application/json
```
## Response Format
### Success
```json
{
"error": false,
"data": { ... }
}
```
### Error
```json
{
"error": true,
"message": "Error message"
}
```
---
## API Endpoints
### 1. Authentication (Admin)
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/administrator/login` | Đăng nhập admin |
| POST | `/administrator/logout` | Đăng xuất |
| POST | `/administrator/refresh` | Refresh token |
| GET | `/administrator/me` | Lấy thông tin admin hiện tại |
### 2. Customers
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/customers` | Danh sách khách hàng |
| POST | `/administrator/customers` | Tạo khách hàng mới |
| GET | `/administrator/customers/{id}` | Chi tiết khách hàng |
| PUT | `/administrator/customers/{id}` | Cập nhật khách hàng |
| DELETE | `/administrator/customers/{id}` | Xóa khách hàng |
| POST | `/administrator/customers/{id}/reset-password` | Reset password |
| POST | `/administrator/customers/{id}/add-credit` | Nạp tiền |
| GET | `/administrator/customers/{id}/products` | Danh sách sản phẩm |
| GET | `/administrator/customers/{id}/invoices` | Danh sách hóa đơn |
| GET | `/administrator/customers/{id}/transactions` | Danh sách giao dịch |
| GET | `/administrator/customers/{id}/services` | Danh sách dịch vụ (VPS/Hosting) |
| GET | `/administrator/customers/trash` | Danh sách đã xóa |
| POST | `/administrator/customers/{id}/restore` | Khôi phục |
| DELETE | `/administrator/customers/{id}/force` | Xóa vĩnh viễn |
### 3. Orders
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/orders` | Danh sách đơn hàng |
| POST | `/administrator/orders` | Tạo đơn hàng mới |
| GET | `/administrator/orders/{id}` | Chi tiết đơn hàng |
| PUT | `/administrator/orders/{id}` | Cập nhật đơn hàng |
| DELETE | `/administrator/orders/{id}` | Xóa đơn hàng |
| POST | `/administrator/orders/{id}/accept` | Chấp nhận đơn hàng |
| POST | `/administrator/orders/{id}/cancel` | Hủy đơn hàng |
### 4. Invoices
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/invoices` | Danh sách hóa đơn |
| POST | `/administrator/invoices` | Tạo hóa đơn mới |
| GET | `/administrator/invoices/{id}` | Chi tiết hóa đơn |
| PUT | `/administrator/invoices/{id}` | Cập nhật hóa đơn |
| DELETE | `/administrator/invoices/{id}` | Xóa hóa đơn |
| POST | `/administrator/invoices/{id}/paid` | Đánh dấu đã thanh toán |
| POST | `/administrator/invoices/{id}/cancel` | Hủy hóa đơn |
### 5. Products
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/products` | Danh sách sản phẩm |
| POST | `/administrator/products` | Tạo sản phẩm mới |
| GET | `/administrator/products/{id}` | Chi tiết sản phẩm |
| PUT | `/administrator/products/{id}` | Cập nhật sản phẩm |
| DELETE | `/administrator/products/{id}` | Xóa sản phẩm |
| POST | `/administrator/products/{id}/duplicate` | Nhân bản sản phẩm |
| POST | `/administrator/products/import` | Import sản phẩm từ Excel |
| GET | `/administrator/products/export` | Export sản phẩm ra Excel |
### 6. Product Categories
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/product-categories` | Danh sách danh mục |
| POST | `/administrator/product-categories` | Tạo danh mục mới |
| GET | `/administrator/product-categories/{id}` | Chi tiết danh mục |
| PUT | `/administrator/product-categories/{id}` | Cập nhật danh mục |
| DELETE | `/administrator/product-categories/{id}` | Xóa danh mục |
### 7. Payments
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/payments` | Danh sách phương thức thanh toán |
| POST | `/administrator/payments` | Tạo phương thức thanh toán |
| GET | `/administrator/payments/{id}` | Chi tiết phương thức thanh toán |
| PUT | `/administrator/payments/{id}` | Cập nhật phương thức thanh toán |
| DELETE | `/administrator/payments/{id}` | Xóa phương thức thanh toán |
### 8. Modules - VPS/VMware
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/vps` | Danh sách VPS |
| GET | `/administrator/vps/{id}` | Chi tiết VPS |
| PUT | `/administrator/vps/{id}` | Cập nhật VPS |
| POST | `/administrator/vps/{id}/power-on` | Bật VPS |
| POST | `/administrator/vps/{id}/power-off` | Tắt VPS |
| POST | `/administrator/vps/{id}/restart` | Restart VPS |
| POST | `/administrator/vps/{id}/reinstall` | Cài lại VPS |
### 9. Modules - Hosting
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/hosting` | Danh sách Hosting |
| GET | `/administrator/hosting/{id}` | Chi tiết Hosting |
| PUT | `/administrator/hosting/{id}` | Cập nhật Hosting |
| POST | `/administrator/hosting` | Tạo Hosting mới |
| DELETE | `/administrator/hosting/{id}` | Xóa Hosting |
### 10. Modules - Domain
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/domains` | Danh sách Domain |
| GET | `/administrator/domains/{id}` | Chi tiết Domain |
| PUT | `/administrator/domains/{id}` | Cập nhật Domain |
### 11. Modules - DNS
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/dns` | Danh sách DNS |
| GET | `/administrator/dns/{id}` | Chi tiết DNS |
| PUT | `/administrator/dns/{id}` | Cập nhật DNS |
| POST | `/administrator/dns` | Tạo DNS mới |
| DELETE | `/administrator/dns/{id}` | Xóa DNS |
### 12. Modules - IP Pool
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/ip-pools` | Danh sách IP Pool |
| GET | `/administrator/ip-pools/{id}` | Chi tiết IP Pool |
| PUT | `/administrator/ip-pools/{id}` | Cập nhật IP Pool |
| POST | `/administrator/ip-pools` | Tạo IP Pool mới |
| DELETE | `/administrator/ip-pools/{id}` | Xóa IP Pool |
### 13. vCenter Management
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/vcenter` | Danh sách vCenter |
| POST | `/administrator/vcenter` | Tạo vCenter mới |
| GET | `/administrator/vcenter/{id}` | Chi tiết vCenter |
| PUT | `/administrator/vcenter/{id}` | Cập nhật vCenter |
| DELETE | `/administrator/vcenter/{id}` | Xóa vCenter |
| POST | `/administrator/vcenter/sync` | Đồng bộ vCenter |
| GET | `/administrator/vcenter/tree` | Lấy cây thư mục |
### 14. Settings
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/settings` | Danh sách cài đặt |
| PUT | `/administrator/settings` | Cập nhật cài đặt |
### 15. Reports/Logs
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/logs` | Danh sách logs |
| GET | `/administrator/logs/customer/{id}` | Logs của khách hàng |
### 16. Media
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/administrator/media` | Danh sách media |
| POST | `/administrator/media` | Upload media |
| DELETE | `/administrator/media/{id}` | Xóa media |
---
## DataTable Support
Các endpoint danh sách hỗ trợ tham số DataTable:
- `draw`: Draw counter
- `start`: Paging start
- `length`: Page length
- `search[value]`: Search value
- `order[0][column]`: Sort column
- `order[0][dir]`: Sort direction
Response DataTable:
```json
{
"draw": 1,
"recordsTotal": 100,
"recordsFiltered": 100,
"data": [...]
}
```
---
## Pagination
Các endpoint danh sách hỗ trợ phân trang:
- `page`: Số trang (mặc định: 1)
- `limit`: Số item trên trang (mặc định: 15)
---
## Error Codes
| Code | Description |
|------|-------------|
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 422 | Validation Error |
| 500 | Server Error |