13 KiB
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ác nhận
vCenterServicecó methodStartVirtualMachinevàShutdownGuestVirtualMachine - Sửa
VPSController::powerOn()— thayrestartVM→StartVirtualMachine - Sửa
VPSController::powerOff()— thayrestartVM→ShutdownGuestVirtualMachine - Fix
StartVirtualMachinetrong vCenter service: dùng null-safe vCenter resolution (nhưrestartVM), thêm try/catch - Fix
ShutdownGuestVirtualMachinetrong 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, 150app/Http/Services/vCenter.php—StartVirtualMachine(),ShutdownGuestVirtualMachine()
[SECURITY] CustomerController: trả plain-text password trong response
- Xóa
'data' => ['new_password' => $password]khỏi response củaresetPassword() - 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_passwordkhô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ặcenv('PLESK_DEFAULT_IP') - Thêm
PLESK_DEFAULT_IP=103.42.58.124vào.envvà.env.example - Thay 2 chỗ hardcode trong
createDomain()vàupdateDomain()— dòng 216–217, 264–265 - Verify
.env.examplekhô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()
- Move:
- Tạo
app/Http/Controllers/Api/Client/ServiceController.php- Move:
getClientServices(),getClientServiceDetail(),updateClientServiceAutoRenew(),renewService()
- Move:
- Giữ lại
OrderControllerchỉ 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(thayformatOrderSummary,formatOrderDetail) - Tạo
app/Http/Resources/Api/Client/InvoiceResource.php(thayformatInvoiceSummary,formatInvoiceDetail) - Tạo
app/Http/Resources/Api/Client/ServiceResource.php(thay inline map tronggetClientServices) - Tạo
app/Http/Resources/Api/Client/OrderItemResource.php(thayformatOrderItems) - Xóa các private
format*()vàisoDate()khỏi OrderController - Move
isoDate()vào traitHasIsoDatehoặ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
$billingCycleLabelsvà$periodUnitslà constants trong Resource
- Thay đoạn
$products->map(function ($product) { ... })tronggetDetailCategory()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
@deprecateddocblock vào LegacyOrderService - Tạo ticket riêng để migrate LegacyOrderService → dùng chung logic với Api\Order\OrderService
Files:
app/Services/OrderService.phpapp/Http/Controllers/Api/Admin/Order/OrderController.php— dòng 22, 41
[PERFORMANCE] Fix N+1: Addon::find() trong foreach
resolveAddons()— thayAddon::find()trong loop bằng bulk load:$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
- Move:
- 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.phpvới các method:ok(mixed $data, string $message = ''): JsonResponsefail(string $message, int $status = 400): JsonResponsenotFound(string $message = ''): JsonResponseforbidden(string $message = ''): JsonResponse
- Thống nhất dùng
{ "success": true/false, "data": ..., "message": "..." } - Apply trait vào
Controller.phpbase 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.phppay(Customer $user, Order $order): boolretry(Customer $user, Order $order): boolview(Customer $user, Order $order): bool
- Tạo
app/Policies/ServicePolicy.phpview(Customer $user, Service $service): boolupdate(Customer $user, Service $service): boolrenew(Customer $user, Service $service): bool
- Tạo
app/Policies/VmPolicy.phpcontrol(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.phphoặc dùng Laravel Notification - Bind trong
AppServiceProvider - Inject vào
VmControllerconstructor thay vìnew Telegram() - Inject vào
ManualDepositControllerconstructor - Xóa
use App\Http\Services\Telegramdirect 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, 5bằngProductBillingCycle::fromLegacyCode() - Thay hardcode
$total * (10 / 100)bằngconfig('billing.tax_rate', 0.10) - Thêm
TAX_RATE=0.10vào.env/ config - Bỏ
Auth::guard('admin')khỏi service — pass$userIdtừ 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ọichangeInvoiceStatus($id, InvoiceStatus::PAID)cancel()→ gọichangeInvoiceStatus($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/