Files
portal-api/docs/REFACTOR_TODO.md
T

283 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/`*