refactor: implement centralized cache key management and add utility traits for vCenter SSH operations.

This commit is contained in:
pbao9
2026-05-16 16:52:21 +07:00
parent 06dbd54ecd
commit 8a115c5175
36 changed files with 1694 additions and 517 deletions
+282
View File
@@ -0,0 +1,282 @@
# Refactor TODO — Portal API
> Dựa trên kết quả review kiến trúc Laravel. Ưu tiên từ trên xuống.
> **Quy tắc:** Mỗi task nên là 1 PR riêng. Không mix refactor + feature.
---
## 🔴 CRITICAL — Sửa ngay, đang ảnh hưởng production
### [BUG] VPSController: powerOff gọi nhầm restartVM ✅ DONE
- [x] Xác nhận `vCenterService` có method `StartVirtualMachine` và `ShutdownGuestVirtualMachine`
- [x] Sửa `VPSController::powerOn()` — thay `restartVM` → `StartVirtualMachine`
- [x] Sửa `VPSController::powerOff()` — thay `restartVM` → `ShutdownGuestVirtualMachine`
- [x] Fix `StartVirtualMachine` trong vCenter service: dùng null-safe vCenter resolution (như `restartVM`), thêm try/catch
- [x] Fix `ShutdownGuestVirtualMachine` trong vCenter service: tương tự
- [ ] Test manual cả 3 action: powerOn, powerOff, restart
**Files đã sửa:**
- `app/Http/Controllers/Api/Admin/VPS/VPSController.php` — dòng 105, 150
- `app/Http/Services/vCenter.php` — `StartVirtualMachine()`, `ShutdownGuestVirtualMachine()`
---
### [SECURITY] CustomerController: trả plain-text password trong response
- [ ] Xóa `'data' => ['new_password' => $password]` khỏi response của `resetPassword()`
- [ ] Import và dispatch `PasswordRecoverJob` (đã import sẵn nhưng chưa dùng)
- [ ] Thay response thành: `"Password reset. Customer will receive an email."`
- [ ] Kiểm tra frontend có đang render `new_password` không → cập nhật nếu có
**File:** `app/Http/Controllers/Api/Admin/Customer/CustomerController.php` — dòng 314
---
### [CONFIG] ServerService: hardcode IP production trong code
- [ ] Tạo config key: `config('services.plesk.default_ip')` hoặc `env('PLESK_DEFAULT_IP')`
- [ ] Thêm `PLESK_DEFAULT_IP=103.42.58.124` vào `.env` và `.env.example`
- [ ] Thay 2 chỗ hardcode trong `createDomain()` và `updateDomain()` — dòng 216–217, 264–265
- [ ] Verify `.env.example` không chứa IP thật
**File:** `app/Services/ServerService.php`
---
## 🟠 HIGH — Sprint hiện tại
### [REFACTOR] Extract RenewalService từ OrderController
- [ ] Tạo `app/Services/Api/Service/RenewalService.php`
- [ ] Move toàn bộ logic trong `renewService()` (dòng 497–612) vào service
- [ ] Move: validate balance
- [ ] Move: DB::transaction (debit, log, tạo invoice, tạo invoice item, update service)
- [ ] Move: dispatch VMJobs
- [ ] Tạo `app/Http/Requests/Api/Service/RenewServiceRequest.php` (tách `$request->validate()` ra)
- [ ] Controller `renewService()` chỉ còn: resolve service → gọi service → return response
- [ ] Test: gia hạn thành công, số dư không đủ, service đã terminated
**File:** `app/Http/Controllers/Api/Service/OrderController.php` — dòng 497–612
---
### [REFACTOR] Split OrderController thành 3 controller riêng
- [ ] Tạo `app/Http/Controllers/Api/Client/InvoiceController.php`
- [ ] Move: `getClientInvoices()`, `getClientInvoiceDetail()`, `getClientInvoiceDetailByOrderNum()`
- [ ] Tạo `app/Http/Controllers/Api/Client/ServiceController.php`
- [ ] Move: `getClientServices()`, `getClientServiceDetail()`, `updateClientServiceAutoRenew()`, `renewService()`
- [ ] Giữ lại `OrderController` chỉ với: `getList`, `getClientOrders`, `getClientOrderDetail`, `storeOrder`, `payOrder`, `retryOrderProvisioning`
- [ ] Cập nhật routes tương ứng
- [ ] Xóa các private formatter methods khỏi OrderController sau khi tạo Resources (task dưới)
**File:** `app/Http/Controllers/Api/Service/OrderController.php`
---
### [REFACTOR] Tạo API Resources thay thế private formatter methods
- [ ] Tạo `app/Http/Resources/Api/Client/OrderResource.php` (thay `formatOrderSummary`, `formatOrderDetail`)
- [ ] Tạo `app/Http/Resources/Api/Client/InvoiceResource.php` (thay `formatInvoiceSummary`, `formatInvoiceDetail`)
- [ ] Tạo `app/Http/Resources/Api/Client/ServiceResource.php` (thay inline map trong `getClientServices`)
- [ ] Tạo `app/Http/Resources/Api/Client/OrderItemResource.php` (thay `formatOrderItems`)
- [ ] Xóa các private `format*()` và `isoDate()` khỏi OrderController
- [ ] Move `isoDate()` vào trait `HasIsoDate` hoặc dùng Carbon casting trên model
**Files:** `app/Http/Controllers/Api/Service/OrderController.php` — dòng 618–789
---
### [REFACTOR] Tạo ProductResource — xóa duplicate transformation
- [ ] Tạo `app/Http/Resources/Api/Service/ProductResource.php`
- [ ] Include: uuid, name, slug, specs, os grouping, pricing với labels
- [ ] Khai báo `$billingCycleLabels` và `$periodUnits` là constants trong Resource
- [ ] Thay đoạn `$products->map(function ($product) { ... })` trong `getDetailCategory()` bằng Resource
- [ ] Thay đoạn giống hệt trong `getChildCategoryWithParent()` bằng Resource
- [ ] Xóa 2 đoạn transformation ~60 dòng khỏi controller
**File:** `app/Http/Controllers/Api/Service/ProductCategoryController.php`
---
### [REFACTOR] Giải quyết 2 class OrderService trùng tên
- [ ] Rename `app/Services/OrderService.php` → `app/Services/Admin/Order/LegacyOrderService.php`
- [ ] Cập nhật namespace thành `App\Services\Admin\Order`
- [ ] Cập nhật tất cả nơi inject `App\Services\OrderService` → dùng namespace mới
- [ ] Thêm `@deprecated` docblock vào LegacyOrderService
- [ ] Tạo ticket riêng để migrate LegacyOrderService → dùng chung logic với Api\Order\OrderService
**Files:**
- `app/Services/OrderService.php`
- `app/Http/Controllers/Api/Admin/Order/OrderController.php` — dòng 22, 41
---
### [PERFORMANCE] Fix N+1: Addon::find() trong foreach
- [ ] `resolveAddons()` — thay `Addon::find()` trong loop bằng bulk load:
```php
$addons = Addon::whereIn('id', collect($raw)->pluck('addon_id'))->get()->keyBy('id');
```
- [ ] `attachServiceAddons()` — áp dụng tương tự với `$addonsMeta`
- [ ] Kiểm tra xem có chỗ nào khác trong Services dùng pattern tương tự không
**File:** `app/Services/Api/Order/OrderService.php` — dòng 224, 310
---
### [REFACTOR] CustomerController: extract CustomerService
- [ ] Tạo `app/Services/Api/Admin/CustomerService.php`
- [ ] Move: `destroy()` cascade delete logic → `CustomerService::delete(Customer $customer)`
- [ ] Move: `restore()` cascade restore logic → `CustomerService::restore(Customer $customer)`
- [ ] Move: `forceDelete()` cascade force delete → `CustomerService::forceDelete(Customer $customer)`
- [ ] Move: `addCredit()` business logic → `CustomerService::adjustCredit(Customer $customer, int $amount, string $action)`
- [ ] Move: `resetPassword()` → `CustomerService::resetPassword(Customer $customer): string`
- [ ] Bỏ direct Model calls `Invoice::where(...)`, `Order::where(...)` trong controller — dùng repository
- [ ] Giảm constructor từ 9 repository xuống còn tối đa 3–4
**File:** `app/Http/Controllers/Api/Admin/Customer/CustomerController.php`
---
## 🟡 MEDIUM — Sprint tiếp theo
### [PAGINATION] Thêm pagination cho các client list endpoint
- [ ] `getClientOrders()` — thêm `->paginate($request->integer('per_page', 15))`
- [ ] `getClientInvoices()` — thêm `->paginate($request->integer('per_page', 15))`
- [ ] `getClientServices()` — thêm `->paginate($request->integer('per_page', 15))`
- [ ] `CustomerController::trash()` — thêm pagination
- [ ] Cập nhật response format trả về `meta` (total, per_page, current_page, last_page)
- [ ] Verify frontend handle pagination response
**File:** `app/Http/Controllers/Api/Service/OrderController.php` — dòng 64, 244, 344
---
### [CONSISTENCY] Chuẩn hóa response envelope toàn bộ API
- [ ] Tạo `app/Http/Traits/ApiResponse.php` với các method:
- [ ] `ok(mixed $data, string $message = ''): JsonResponse`
- [ ] `fail(string $message, int $status = 400): JsonResponse`
- [ ] `notFound(string $message = ''): JsonResponse`
- [ ] `forbidden(string $message = ''): JsonResponse`
- [ ] Thống nhất dùng `{ "success": true/false, "data": ..., "message": "..." }`
- [ ] Apply trait vào `Controller.php` base class
- [ ] Migrate Admin controllers (đang dùng `"error"`) → dùng `"success"`
- [ ] Migrate Service controllers → dùng trait thay vì inline helpers
**Files:** Toàn bộ `app/Http/Controllers/Api/`
---
### [AUTHORIZATION] Thêm Laravel Policies
- [ ] Tạo `app/Policies/OrderPolicy.php`
- [ ] `pay(Customer $user, Order $order): bool`
- [ ] `retry(Customer $user, Order $order): bool`
- [ ] `view(Customer $user, Order $order): bool`
- [ ] Tạo `app/Policies/ServicePolicy.php`
- [ ] `view(Customer $user, Service $service): bool`
- [ ] `update(Customer $user, Service $service): bool`
- [ ] `renew(Customer $user, Service $service): bool`
- [ ] Tạo `app/Policies/VmPolicy.php`
- [ ] `control(Customer $user, ModuleVmware $vm): bool`
- [ ] Đăng ký Policies trong `AuthServiceProvider`
- [ ] Replace inline `if ($order->customer_id !== $request->user()->id)` bằng `$this->authorize()`
**Files:** `app/Http/Controllers/Api/Service/OrderController.php`, `VmController.php`
---
### [DI] Inject Telegram qua interface thay vì `new Telegram()`
- [ ] Tạo `app/Contracts/NotificationServiceInterface.php` hoặc dùng Laravel Notification
- [ ] Bind trong `AppServiceProvider`
- [ ] Inject vào `VmController` constructor thay vì `new Telegram()`
- [ ] Inject vào `ManualDepositController` constructor
- [ ] Xóa `use App\Http\Services\Telegram` direct instantiation khỏi controllers
**Files:** `app/Http/Controllers/Api/Service/VmController.php` — dòng 153, `ManualDepositController.php` — dòng 179
---
### [REFACTOR] AdminOrderController::update — extract provisioning logic
- [ ] Tạo `app/Services/Admin\Order\OrderProvisioningService.php`
- [ ] Move switch/case logic (type 1=addon, 3=hosting, 4=VPS) ra khỏi controller
- [ ] Move IP assignment logic
- [ ] Move job dispatch logic
- [ ] Controller `update()` chỉ còn: validate → update status → gọi service nếu status=1
**File:** `app/Http/Controllers/Api/Admin/Order/OrderController.php` — dòng 120–178
---
### [REFACTOR] Migrate LegacyOrderService billing codes sang enum
- [ ] Thay magic integers `2, 3, 4, 5` bằng `ProductBillingCycle::fromLegacyCode()`
- [ ] Thay hardcode `$total * (10 / 100)` bằng `config('billing.tax_rate', 0.10)`
- [ ] Thêm `TAX_RATE=0.10` vào `.env` / config
- [ ] Bỏ `Auth::guard('admin')` khỏi service — pass `$userId` từ controller vào
**File:** `app/Services/OrderService.php` (legacy) — dòng 49, 235–241
---
### [REFACTOR] InvoiceController: gộp markAsPaid / cancel vào update
- [ ] Tạo private `changeInvoiceStatus(int $id, InvoiceStatus $status): JsonResponse`
- [ ] `markAsPaid()` → gọi `changeInvoiceStatus($id, InvoiceStatus::PAID)`
- [ ] `cancel()` → gọi `changeInvoiceStatus($id, InvoiceStatus::CANCELLED)`
- [ ] Xóa 2 method trùng lặp, giữ lại private helper
**File:** `app/Http/Controllers/Api/Admin/Invoice/InvoiceController.php` — dòng 198–260
---
## 🟢 LOW — Backlog / tech debt
### [REFACTOR] ServerService: bỏ stateful $server pattern
- [ ] Thay `$this->server` (instance state) thành parameter trong mỗi method
- [ ] Xóa `resetServer()` method
- [ ] Kiểm tra tất cả caller đang dùng `addServerById()` / `findServerByID()`
- [ ] Đảm bảo service vẫn có thể inject server mặc định qua constructor nếu cần
**File:** `app/Services/ServerService.php`
---
### [CONFIG] VMDataService: bỏ default values che giấu lỗi config
- [ ] Thay `'ip' => $primaryIp ?? '0.0.0.0'` → throw exception nếu không có IP
- [ ] Thay `'module_network_id' => ... ?? 1` → config('services.vmware.default_network_id')
- [ ] Thay `'port' => 1` → config hoặc constant có tên rõ ràng
- [ ] Log warning rõ ràng thay vì silent fallback
**File:** `app/Services/VMDataService.php` — dòng 111–117
---
### [CLEANUP] Xóa deprecated method `resolveVm()` trong VmController
- [ ] Xóa `resolveVm()` (dòng 357–361) đã được đánh dấu `@deprecated`
- [ ] Verify không còn caller nào dùng method này
**File:** `app/Http/Controllers/Api/Service/VmController.php` — dòng 357
---
### [NAMING] Chuẩn hóa naming convention controllers
- [ ] Đổi `app/Http/Controllers/admin/` (lowercase) → `app/Http/Controllers/Admin/` (PascalCase) — cần cẩn thận, ảnh hưởng autoload
- [ ] Đổi `app/Http/Controllers/rest/` → đặt vào đúng namespace Api hoặc xóa nếu unused
---
### [CLEANUP] Xóa hoặc di chuyển test controllers khỏi production
- [ ] Kiểm tra `Api/Admin/Invoice/InvoiceTestController.php` — có đang được route tới không?
- [ ] Nếu không cần → xóa
- [ ] Nếu cần → chuyển sang test suite hoặc đặt sau middleware `env('APP_ENV') === 'local'`
**File:** `app/Http/Controllers/Api/Admin/Invoice/InvoiceTestController.php`
---
## 📋 Tracking
| Priority | Tổng tasks | Đã xong |
|---|---|---|
| 🔴 Critical | 9 | 5 |
| 🟠 High | 28 | 0 |
| 🟡 Medium | 28 | 0 |
| 🟢 Low | 9 | 0 |
| **Tổng** | **74** | **0** |
---
*Generated: 2026-05-16 — based on architecture review of `app/Http/Controllers/Api/` and `app/Services/`*