Files
portal-api/docs/REFACTOR_TODO.md
T

13 KiB
Raw Blame History

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 vCenterService có method StartVirtualMachine và ShutdownGuestVirtualMachine
  • Sửa VPSController::powerOn() — thay restartVM → StartVirtualMachine
  • Sửa VPSController::powerOff() — thay restartVM → ShutdownGuestVirtualMachine
  • Fix StartVirtualMachine trong vCenter service: dùng null-safe vCenter resolution (như restartVM), thêm try/catch
  • 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:
    $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/