Clean up documentation and infrastructure configuration files, and refine API resources and controllers for products and services.
This commit is contained in:
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
Reference in New Issue
Block a user