# 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/`*