feat: Enhance service management and transaction handling
- Implemented client access token verification middleware to secure API endpoints. - Updated ServiceController to fetch active hosting services for authenticated users. - Refactored OrderController to ensure order retrieval is customer-specific. - Improved error logging in VmController for unexpected vCenter responses. - Disabled VNPay auto-deposit in TransactionController, switching to manual deposit flow. - Enhanced ServiceClientDetailResource to include Plesk credentials for hosting accounts. - Adjusted VpsServiceResource to dynamically set page type based on product type. - Added logging for PowerShellService to capture errors in RDP port changes. - Provisioning job now resolves service plans and logs relevant information for hosting accounts. - Updated database migrations to add necessary fields for service plans and DNS servers. - Refactored Service and ServicePlan models to establish relationships with hosting accounts and DNS servers.
This commit is contained in:
@@ -47,7 +47,9 @@
|
||||
"Bash(node -e \"console.log\\(require.resolve\\('ag-grid-react/package.json'\\)\\)\")",
|
||||
"Bash(awk 'NR==1,NR==900' routes/api.php)",
|
||||
"Bash(sed -i '' -e 's/#c93131/#F16723/g' -e 's/#FFF6F6/#FFF1E6/g' -e 's/#ea4544/#F16723/g' -e 's/#EA4544/#F16723/g' -e 's/#e0cccc/#e6e6e6/g' portal/index.html)",
|
||||
"Bash(sed -i '' -e 's/#c93131/#F16723/g' -e 's/#FFF6F6/#FFF1E6/g' -e 's/#ea4544/#F16723/g' -e 's/#EA4544/#F16723/g' -e 's/#e0cccc/#e6e6e6/g' index.html)"
|
||||
"Bash(sed -i '' -e 's/#c93131/#F16723/g' -e 's/#FFF6F6/#FFF1E6/g' -e 's/#ea4544/#F16723/g' -e 's/#EA4544/#F16723/g' -e 's/#e0cccc/#e6e6e6/g' index.html)",
|
||||
"Bash(xargs -I{} basename {})",
|
||||
"Bash(uniq -c -w7)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ VNPAY_API_URL=http://sandbox.vnpayment.vn/merchant_webapi/merchant.html
|
||||
VNPAY_TRANSACTION_API=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
|
||||
FRONTEND_URL=https://portal.test
|
||||
|
||||
# openssl rand -hex 32
|
||||
CLIENT_API_ACCESS_KEY=
|
||||
CLIENT_API_ACCESS_SECRET="tgs-client-api"
|
||||
|
||||
|
||||
|
||||
WMKS_HOST=10.10.10.120
|
||||
@@ -22,6 +22,7 @@ class ServerController extends Controller
|
||||
$perPage = min((int) $request->get('per_page', 15), 500);
|
||||
$filter = $request->get('filter');
|
||||
$status = $request->get('status');
|
||||
$type = $request->get('type');
|
||||
|
||||
$query = $this->serverRepository->model->newQuery();
|
||||
|
||||
@@ -37,6 +38,14 @@ class ServerController extends Controller
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
if ($type === 'hosting') {
|
||||
$query->whereIn('type', ['hosting', 'both']);
|
||||
} elseif ($type === 'domain') {
|
||||
$query->whereIn('type', ['domain', 'both']);
|
||||
} elseif ($type !== null && $type !== '') {
|
||||
$query->where('type', $type);
|
||||
}
|
||||
|
||||
$paginator = $query->orderByDesc('id')->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
@@ -70,6 +79,7 @@ class ServerController extends Controller
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'ip_address' => 'required|string|max:255',
|
||||
'panel_port' => 'nullable|integer|min:1|max:65535',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'required|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
@@ -83,6 +93,7 @@ class ServerController extends Controller
|
||||
$server = $this->serverRepository->create([
|
||||
'name' => $validated['name'],
|
||||
'ip_address' => $validated['ip_address'],
|
||||
'panel_port' => $validated['panel_port'] ?? 8880,
|
||||
'username' => $validated['username'],
|
||||
'password' => Crypt::encryptString($validated['password']),
|
||||
'api_key' => $validated['api_key'] ?? null,
|
||||
@@ -114,6 +125,7 @@ class ServerController extends Controller
|
||||
$validated = $request->validate([
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
'ip_address' => 'sometimes|required|string|max:255',
|
||||
'panel_port' => 'nullable|integer|min:1|max:65535',
|
||||
'username' => 'sometimes|required|string|max:255',
|
||||
'password' => 'nullable|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
@@ -182,6 +194,7 @@ class ServerController extends Controller
|
||||
'id' => $server->id,
|
||||
'name' => $server->name,
|
||||
'ip_address' => $server->ip_address,
|
||||
'panel_port' => $server->panel_port,
|
||||
'username' => $server->username,
|
||||
'api_key' => $server->api_key,
|
||||
'status' => $server->status,
|
||||
|
||||
@@ -11,14 +11,14 @@ class ServicePlanController extends Controller
|
||||
{
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$plans = ServicePlan::with('plesk')->orderBy('title')->get();
|
||||
$plans = ServicePlan::with('plesk', 'dnsServer')->orderBy('title')->get();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $plans]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
$plan = ServicePlan::with('plesk')->find($id);
|
||||
$plan = ServicePlan::with('plesk', 'dnsServer')->find($id);
|
||||
|
||||
if (!$plan) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy gói hosting.'], 404);
|
||||
@@ -30,21 +30,23 @@ class ServicePlanController extends Controller
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'vcpu' => 'required|integer|min:0',
|
||||
'ram' => 'required|integer|min:0',
|
||||
'disk' => 'required|integer|min:0',
|
||||
'domain' => 'required|integer|min:0',
|
||||
'plesk_plan_name' => 'nullable|string|max:255',
|
||||
'note' => 'nullable|string',
|
||||
'title' => 'required|string|max:255',
|
||||
'vcpu' => 'required|integer|min:0',
|
||||
'ram' => 'required|integer|min:0',
|
||||
'disk' => 'required|integer|min:0',
|
||||
'domain' => 'required|integer|min:0',
|
||||
'dns_manager_server_id' => 'nullable|integer|exists:dns_manager_servers,id',
|
||||
'plesk_plan_name' => 'nullable|string|max:255',
|
||||
'note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$plan = ServicePlan::create([
|
||||
'title' => $validated['title'],
|
||||
'vcpu' => $validated['vcpu'],
|
||||
'ram' => $validated['ram'],
|
||||
'disk' => $validated['disk'],
|
||||
'domain' => $validated['domain'],
|
||||
'title' => $validated['title'],
|
||||
'vcpu' => $validated['vcpu'],
|
||||
'ram' => $validated['ram'],
|
||||
'disk' => $validated['disk'],
|
||||
'domain' => $validated['domain'],
|
||||
'dns_manager_server_id' => $validated['dns_manager_server_id'] ?? null,
|
||||
]);
|
||||
|
||||
$this->savePlesk($plan, $validated);
|
||||
@@ -52,7 +54,7 @@ class ServicePlanController extends Controller
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Tạo gói hosting thành công.',
|
||||
'data' => $plan->load('plesk'),
|
||||
'data' => $plan->load('plesk', 'dnsServer'),
|
||||
], 201);
|
||||
}
|
||||
|
||||
@@ -65,21 +67,23 @@ class ServicePlanController extends Controller
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'vcpu' => 'required|integer|min:0',
|
||||
'ram' => 'required|integer|min:0',
|
||||
'disk' => 'required|integer|min:0',
|
||||
'domain' => 'required|integer|min:0',
|
||||
'plesk_plan_name' => 'nullable|string|max:255',
|
||||
'note' => 'nullable|string',
|
||||
'title' => 'required|string|max:255',
|
||||
'vcpu' => 'required|integer|min:0',
|
||||
'ram' => 'required|integer|min:0',
|
||||
'disk' => 'required|integer|min:0',
|
||||
'domain' => 'required|integer|min:0',
|
||||
'dns_manager_server_id' => 'nullable|integer|exists:dns_manager_servers,id',
|
||||
'plesk_plan_name' => 'nullable|string|max:255',
|
||||
'note' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$plan->update([
|
||||
'title' => $validated['title'],
|
||||
'vcpu' => $validated['vcpu'],
|
||||
'ram' => $validated['ram'],
|
||||
'disk' => $validated['disk'],
|
||||
'domain' => $validated['domain'],
|
||||
'title' => $validated['title'],
|
||||
'vcpu' => $validated['vcpu'],
|
||||
'ram' => $validated['ram'],
|
||||
'disk' => $validated['disk'],
|
||||
'domain' => $validated['domain'],
|
||||
'dns_manager_server_id' => $validated['dns_manager_server_id'] ?? null,
|
||||
]);
|
||||
|
||||
$this->savePlesk($plan, $validated);
|
||||
@@ -87,7 +91,7 @@ class ServicePlanController extends Controller
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Cập nhật gói hosting thành công.',
|
||||
'data' => $plan->fresh('plesk'),
|
||||
'data' => $plan->fresh(['plesk', 'dnsServer']),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,21 +53,17 @@ class ServiceController extends Controller
|
||||
public function indexHosting(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
$customer = $request->user();
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_hosting',
|
||||
'data' => [
|
||||
'items' => [],
|
||||
'pagination' => [
|
||||
'total' => 0,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
],
|
||||
],
|
||||
]);
|
||||
$services = $this->serviceClientService->getActiveServices($customer->id, 'hosting', $perPage);
|
||||
|
||||
return (new VpsServiceCollection($services))
|
||||
->additional([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_hosting',
|
||||
])
|
||||
->response();
|
||||
}
|
||||
|
||||
public function indexWebDesign(Request $request): JsonResponse
|
||||
|
||||
@@ -128,13 +128,14 @@ class OrderControllerRefactor extends Controller
|
||||
|
||||
public function retryOrderProvisioning(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$order = $this->orderRepository->find($id);
|
||||
$customerId = (int) $request->user()->id;
|
||||
$order = $this->orderRepository->findForCustomer($id, $customerId);
|
||||
|
||||
if (! $order) {
|
||||
return response()->json(['success' => false, 'message' => 'Đơn hàng không tồn tại.'], 404);
|
||||
}
|
||||
|
||||
$result = $this->orderQueryService->retryProvisioning($order, (int) $request->user()->id);
|
||||
$result = $this->orderQueryService->retryProvisioning($order, $customerId);
|
||||
|
||||
if (! $result['ok']) {
|
||||
return response()->json(['success' => false, 'message' => $result['message']], $result['code']);
|
||||
|
||||
@@ -262,10 +262,14 @@ class VmController extends Controller
|
||||
], $ok ? 200 : 400);
|
||||
}
|
||||
|
||||
\Illuminate\Support\Facades\Log::error('changePort: unexpected vCenter response', [
|
||||
'vm_id' => $vm->id,
|
||||
'result' => $result,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Phản hồi không hợp lệ từ vCenter.',
|
||||
'data' => ['raw' => $result],
|
||||
], 400);
|
||||
}
|
||||
|
||||
@@ -280,9 +284,9 @@ class VmController extends Controller
|
||||
return response()->json(['error' => true, 'message' => 'Please choose product']);
|
||||
}
|
||||
|
||||
$vm = ModuleVmware::with('vCenter')->find((int) $productId);
|
||||
if (!$vm) {
|
||||
return response()->json(['error' => true, 'message' => "Can't find product"]);
|
||||
$vm = $this->resolveVmById($productId, $request);
|
||||
if ($vm instanceof JsonResponse) {
|
||||
return $vm;
|
||||
}
|
||||
|
||||
$result = $this->getConsoleTicket($serviceVCenter, $vm);
|
||||
|
||||
@@ -29,46 +29,52 @@ class TransactionController extends Controller
|
||||
$this->activityLogger = $activityLogger;
|
||||
}
|
||||
|
||||
// ponytail: VNPay auto-deposit disabled — nạp tiền hiện dùng luồng thủ công (ManualDepositController).
|
||||
public function handle(DepositRequest $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$transRef = time();
|
||||
$uuid = Str::uuid();
|
||||
$publicCode = 'DEP-' . now()->format('Ymd') . '-' . strtoupper(Str::random(6));
|
||||
|
||||
$transaction = Transaction::create([
|
||||
'uuid' => $uuid,
|
||||
'public_code' => $publicCode,
|
||||
'trans' => 'DEP' . $transRef,
|
||||
'amount' => $request->amount,
|
||||
'customer_id' => $user->id,
|
||||
'status' => TransactionStatus::PENDING->value,
|
||||
]);
|
||||
|
||||
$paymentUrl = $this->vnpayService->createPaymentUrl([
|
||||
'amount' => $request->amount,
|
||||
'transaction_ref' => $transRef,
|
||||
'transaction_uuid' => $uuid,
|
||||
]);
|
||||
|
||||
$this->activityLogger->log(
|
||||
event: 'deposit_vnpay',
|
||||
logName: 'Transaction',
|
||||
description: "Customer {$user->email} tạo yêu cầu nạp tiền VNPay: " . number_format($request->amount) . ' VNĐ - Mã: ' . $publicCode,
|
||||
subject: $transaction,
|
||||
newValues: ['public_code' => $publicCode, 'amount' => $request->amount, 'type' => 'vnpay'],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Tạo yêu cầu nạp tiền thành công',
|
||||
'data' => [
|
||||
'transaction_id' => $transaction->trans,
|
||||
'transaction_uuid' => $uuid,
|
||||
'transaction_code' => $publicCode,
|
||||
'payment_url' => $paymentUrl,
|
||||
],
|
||||
]);
|
||||
'error' => true,
|
||||
'message' => 'Nạp tiền qua VNPay hiện không khả dụng. Vui lòng sử dụng nạp tiền thủ công.',
|
||||
], 410);
|
||||
|
||||
// $user = $request->user();
|
||||
// $transRef = time();
|
||||
// $uuid = Str::uuid();
|
||||
// $publicCode = 'DEP-' . now()->format('Ymd') . '-' . strtoupper(Str::random(6));
|
||||
//
|
||||
// $transaction = Transaction::create([
|
||||
// 'uuid' => $uuid,
|
||||
// 'public_code' => $publicCode,
|
||||
// 'trans' => 'DEP' . $transRef,
|
||||
// 'amount' => $request->amount,
|
||||
// 'customer_id' => $user->id,
|
||||
// 'status' => TransactionStatus::PENDING->value,
|
||||
// ]);
|
||||
//
|
||||
// $paymentUrl = $this->vnpayService->createPaymentUrl([
|
||||
// 'amount' => $request->amount,
|
||||
// 'transaction_ref' => $transRef,
|
||||
// 'transaction_uuid' => $uuid,
|
||||
// ]);
|
||||
//
|
||||
// $this->activityLogger->log(
|
||||
// event: 'deposit_vnpay',
|
||||
// logName: 'Transaction',
|
||||
// description: "Customer {$user->email} tạo yêu cầu nạp tiền VNPay: " . number_format($request->amount) . ' VNĐ - Mã: ' . $publicCode,
|
||||
// subject: $transaction,
|
||||
// newValues: ['public_code' => $publicCode, 'amount' => $request->amount, 'type' => 'vnpay'],
|
||||
// );
|
||||
//
|
||||
// return response()->json([
|
||||
// 'error' => false,
|
||||
// 'message' => 'Tạo yêu cầu nạp tiền thành công',
|
||||
// 'data' => [
|
||||
// 'transaction_id' => $transaction->trans,
|
||||
// 'transaction_uuid' => $uuid,
|
||||
// 'transaction_code' => $publicCode,
|
||||
// 'payment_url' => $paymentUrl,
|
||||
// ],
|
||||
// ]);
|
||||
}
|
||||
|
||||
protected function redirectToFrontend(string $status = 'failed', ?string $transactionCode = null): \Illuminate\Http\RedirectResponse
|
||||
@@ -85,110 +91,113 @@ class TransactionController extends Controller
|
||||
return redirect()->away($url . '?' . http_build_query($params));
|
||||
}
|
||||
|
||||
// ponytail: VNPay auto-deposit disabled — nạp tiền hiện dùng luồng thủ công (ManualDepositController).
|
||||
public function returnCallback(Request $request)
|
||||
{
|
||||
$data = $request->all();
|
||||
|
||||
$vnp_TxnRef = $data['vnp_TxnRef'] ?? null;
|
||||
$vnp_Amount = $data['vnp_Amount'] ?? 0;
|
||||
|
||||
FacadesLog::info('VNPay callback received', [
|
||||
'vnp_TxnRef' => $vnp_TxnRef,
|
||||
'vnp_Amount' => $vnp_Amount,
|
||||
'user' => $request->user() ? $request->user()->id : 'not authenticated',
|
||||
]);
|
||||
|
||||
$transaction = Transaction::where('trans', 'DEP' . $vnp_TxnRef)->first();
|
||||
return $this->redirectToFrontend('failed');
|
||||
|
||||
if (!$transaction) {
|
||||
return $this->redirectToFrontend('failed');
|
||||
}
|
||||
|
||||
if ($transaction->status == TransactionStatus::COMPLETED->value) {
|
||||
return $this->redirectToFrontend('success', $transaction->public_code);
|
||||
}
|
||||
|
||||
$verify = $this->vnpayService->verifyPayment($data);
|
||||
|
||||
FacadesLog::info('VNPay callback debug', [
|
||||
'isValid' => $verify['isValid'],
|
||||
'isSuccess' => $verify['isSuccess'] ?? false,
|
||||
'responseCode' => $data['vnp_ResponseCode'] ?? 'N/A',
|
||||
]);
|
||||
|
||||
if (!$verify['isValid']) {
|
||||
return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
}
|
||||
|
||||
if (!$verify['isSuccess']) {
|
||||
$transaction->update(['status' => TransactionStatus::FAILED->value]);
|
||||
return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
}
|
||||
|
||||
$originalAmount = $transaction->amount * 100;
|
||||
if ($vnp_Amount != $originalAmount) {
|
||||
$transaction->update(['status' => TransactionStatus::FAILED->value]);
|
||||
return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
}
|
||||
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$transaction->update(['status' => TransactionStatus::COMPLETED->value]);
|
||||
|
||||
$customer = Customer::find($transaction->customer_id);
|
||||
|
||||
FacadesLog::info('Customer update debug', [
|
||||
'customer_id' => $transaction->customer_id,
|
||||
'customer_found' => $customer ? true : false,
|
||||
'transaction_amount' => $transaction->amount,
|
||||
'current_total' => $customer->total ?? 'N/A',
|
||||
'current_credit' => $customer->credit ?? 'N/A',
|
||||
]);
|
||||
|
||||
$customer->total += $transaction->amount;
|
||||
$customer->credit += $transaction->amount / 100;
|
||||
$customer->save();
|
||||
|
||||
FacadesLog::info('Customer after update', [
|
||||
'new_total' => $customer->total,
|
||||
'new_credit' => $customer->credit,
|
||||
]);
|
||||
|
||||
Log::create([
|
||||
'agent' => 'api',
|
||||
'client_ip' => $request->ip(),
|
||||
'description' => 'Nạp tiền vào tài khoản: ' . number_format($transaction->amount) . ' VNĐ (+' . number_format($transaction->amount / 100) . ' credit)',
|
||||
'customer_id' => $transaction->customer_id,
|
||||
]);
|
||||
|
||||
$this->activityLogger->log(
|
||||
event: 'deposit_success',
|
||||
logName: 'Transaction',
|
||||
description: "Customer {$customer->email} nạp tiền VNPay thành công: " . number_format($transaction->amount) . ' VNĐ - Mã: ' . $transaction->public_code,
|
||||
subject: $transaction,
|
||||
newValues: ['amount' => $transaction->amount, 'new_total' => $customer->total, 'public_code' => $transaction->public_code],
|
||||
);
|
||||
|
||||
DepositSuccessJob::dispatch([
|
||||
'customer_email' => $customer->email,
|
||||
'customer_name' => $customer->fullname,
|
||||
'transaction_code' => $transaction->public_code,
|
||||
'amount' => $transaction->amount,
|
||||
'credit' => $transaction->amount / 100,
|
||||
'current_total' => $customer->total,
|
||||
'current_credit' => $customer->credit,
|
||||
'deposit_date' => now()->format('d/m/Y H:i:s'),
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
FacadesLog::error('Payment processing error: ' . $e->getMessage());
|
||||
return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
}
|
||||
|
||||
return $this->redirectToFrontend('success', $transaction->public_code);
|
||||
// $data = $request->all();
|
||||
//
|
||||
// $vnp_TxnRef = $data['vnp_TxnRef'] ?? null;
|
||||
// $vnp_Amount = $data['vnp_Amount'] ?? 0;
|
||||
//
|
||||
// FacadesLog::info('VNPay callback received', [
|
||||
// 'vnp_TxnRef' => $vnp_TxnRef,
|
||||
// 'vnp_Amount' => $vnp_Amount,
|
||||
// 'user' => $request->user() ? $request->user()->id : 'not authenticated',
|
||||
// ]);
|
||||
//
|
||||
// $transaction = Transaction::where('trans', 'DEP' . $vnp_TxnRef)->first();
|
||||
//
|
||||
// if (!$transaction) {
|
||||
// return $this->redirectToFrontend('failed');
|
||||
// }
|
||||
//
|
||||
// if ($transaction->status == TransactionStatus::COMPLETED->value) {
|
||||
// return $this->redirectToFrontend('success', $transaction->public_code);
|
||||
// }
|
||||
//
|
||||
// $verify = $this->vnpayService->verifyPayment($data);
|
||||
//
|
||||
// FacadesLog::info('VNPay callback debug', [
|
||||
// 'isValid' => $verify['isValid'],
|
||||
// 'isSuccess' => $verify['isSuccess'] ?? false,
|
||||
// 'responseCode' => $data['vnp_ResponseCode'] ?? 'N/A',
|
||||
// ]);
|
||||
//
|
||||
// if (!$verify['isValid']) {
|
||||
// return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
// }
|
||||
//
|
||||
// if (!$verify['isSuccess']) {
|
||||
// $transaction->update(['status' => TransactionStatus::FAILED->value]);
|
||||
// return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
// }
|
||||
//
|
||||
// $originalAmount = $transaction->amount * 100;
|
||||
// if ($vnp_Amount != $originalAmount) {
|
||||
// $transaction->update(['status' => TransactionStatus::FAILED->value]);
|
||||
// return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// DB::beginTransaction();
|
||||
//
|
||||
// $transaction->update(['status' => TransactionStatus::COMPLETED->value]);
|
||||
//
|
||||
// $customer = Customer::find($transaction->customer_id);
|
||||
//
|
||||
// FacadesLog::info('Customer update debug', [
|
||||
// 'customer_id' => $transaction->customer_id,
|
||||
// 'customer_found' => $customer ? true : false,
|
||||
// 'transaction_amount' => $transaction->amount,
|
||||
// 'current_total' => $customer->total ?? 'N/A',
|
||||
// 'current_credit' => $customer->credit ?? 'N/A',
|
||||
// ]);
|
||||
//
|
||||
// $customer->total += $transaction->amount;
|
||||
// $customer->credit += $transaction->amount / 100;
|
||||
// $customer->save();
|
||||
//
|
||||
// FacadesLog::info('Customer after update', [
|
||||
// 'new_total' => $customer->total,
|
||||
// 'new_credit' => $customer->credit,
|
||||
// ]);
|
||||
//
|
||||
// Log::create([
|
||||
// 'agent' => 'api',
|
||||
// 'client_ip' => $request->ip(),
|
||||
// 'description' => 'Nạp tiền vào tài khoản: ' . number_format($transaction->amount) . ' VNĐ (+' . number_format($transaction->amount / 100) . ' credit)',
|
||||
// 'customer_id' => $transaction->customer_id,
|
||||
// ]);
|
||||
//
|
||||
// $this->activityLogger->log(
|
||||
// event: 'deposit_success',
|
||||
// logName: 'Transaction',
|
||||
// description: "Customer {$customer->email} nạp tiền VNPay thành công: " . number_format($transaction->amount) . ' VNĐ - Mã: ' . $transaction->public_code,
|
||||
// subject: $transaction,
|
||||
// newValues: ['amount' => $transaction->amount, 'new_total' => $customer->total, 'public_code' => $transaction->public_code],
|
||||
// );
|
||||
//
|
||||
// DepositSuccessJob::dispatch([
|
||||
// 'customer_email' => $customer->email,
|
||||
// 'customer_name' => $customer->fullname,
|
||||
// 'transaction_code' => $transaction->public_code,
|
||||
// 'amount' => $transaction->amount,
|
||||
// 'credit' => $transaction->amount / 100,
|
||||
// 'current_total' => $customer->total,
|
||||
// 'current_credit' => $customer->credit,
|
||||
// 'deposit_date' => now()->format('d/m/Y H:i:s'),
|
||||
// ]);
|
||||
//
|
||||
// DB::commit();
|
||||
// } catch (\Exception $e) {
|
||||
// DB::rollBack();
|
||||
// FacadesLog::error('Payment processing error: ' . $e->getMessage());
|
||||
// return $this->redirectToFrontend('failed', $transaction->public_code);
|
||||
// }
|
||||
//
|
||||
// return $this->redirectToFrontend('success', $transaction->public_code);
|
||||
}
|
||||
|
||||
public function getTransactions(Request $request)
|
||||
@@ -231,7 +240,7 @@ class TransactionController extends Controller
|
||||
public function verifyPayment(Request $request)
|
||||
{
|
||||
$transRef = $request->query('vnp_TxnRef');
|
||||
|
||||
|
||||
if (!$transRef) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
@@ -239,7 +248,9 @@ class TransactionController extends Controller
|
||||
], 400);
|
||||
}
|
||||
|
||||
$transaction = Transaction::where('trans', 'DEP' . $transRef)->first();
|
||||
$transaction = Transaction::where('trans', 'DEP' . $transRef)
|
||||
->where('customer_id', $request->user()->id)
|
||||
->first();
|
||||
|
||||
if (!$transaction) {
|
||||
return response()->json([
|
||||
@@ -325,7 +336,9 @@ class TransactionController extends Controller
|
||||
], 400);
|
||||
}
|
||||
|
||||
$transaction = Transaction::where('public_code', $code)->first();
|
||||
$transaction = Transaction::where('public_code', $code)
|
||||
->where('customer_id', $request->user()->id)
|
||||
->first();
|
||||
|
||||
if (!$transaction) {
|
||||
return response()->json([
|
||||
@@ -338,27 +351,13 @@ class TransactionController extends Controller
|
||||
'error' => false,
|
||||
'message' => 'Lấy thông tin giao dịch thành công',
|
||||
'data' => [
|
||||
'transaction' => [
|
||||
'public_code' => $transaction->public_code,
|
||||
'amount' => $transaction->amount,
|
||||
'amount_formatted' => number_format($transaction->amount) . ' VNĐ',
|
||||
'status' => $transaction->status,
|
||||
'status_label' => TransactionStatus::from($transaction->status)->label(),
|
||||
'created_at' => $transaction->created_at->format('Y-m-d H:i:s'),
|
||||
'paid_at' => $transaction->updated_at->format('Y-m-d H:i:s'),
|
||||
],
|
||||
'customer' => [
|
||||
'fullname' => $transaction->customer->fullname,
|
||||
'email' => $transaction->customer->email,
|
||||
'phone' => $transaction->customer->phone,
|
||||
'address' => $transaction->customer->address,
|
||||
],
|
||||
'balance' => [
|
||||
'total' => $transaction->customer->total,
|
||||
'credit' => $transaction->customer->credit,
|
||||
'total_formatted' => number_format($transaction->customer->total) . ' VNĐ',
|
||||
'credit_formatted' => number_format($transaction->customer->credit) . ' điểm',
|
||||
],
|
||||
'public_code' => $transaction->public_code,
|
||||
'amount' => $transaction->amount,
|
||||
'amount_formatted' => number_format($transaction->amount) . ' VNĐ',
|
||||
'status' => $transaction->status,
|
||||
'status_label' => TransactionStatus::from($transaction->status)->label(),
|
||||
'created_at' => $transaction->created_at->format('Y-m-d H:i:s'),
|
||||
'paid_at' => $transaction->updated_at->format('Y-m-d H:i:s'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ class Kernel extends HttpKernel
|
||||
'Staff' => \App\Http\Middleware\StaffCheck::class,
|
||||
'json.response' => \App\Http\Middleware\ForceJsonResponse::class,
|
||||
'api.auth' => \App\Http\Middleware\ApiAuthCheck::class,
|
||||
'client.access-token' => \App\Http\Middleware\ClientAccessTokenCheck::class,
|
||||
'perm' => \App\Http\Middleware\CheckPermission::class,
|
||||
'compression' => \App\Http\Middleware\CompressionMiddleware::class,
|
||||
'compression.advanced' => \App\Http\Middleware\AdvancedCompressionMiddleware::class,
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientAccessTokenCheck
|
||||
{
|
||||
/**
|
||||
* Verify the `x-access-token` header contains an AES-256-CBC ciphertext
|
||||
* (base64(iv . ciphertext)) of the shared secret, encrypted with the
|
||||
* openssl key configured in services.client_api.key. Blocks callers
|
||||
* that don't hold the shared key (external scraping/direct API calls).
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$token = $request->header('x-access-token', '');
|
||||
$key = config('services.client_api.key');
|
||||
|
||||
if (!$key || !$this->isValid($token, $key)) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Invalid access token',
|
||||
'code' => 403,
|
||||
], 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private function isValid(string $token, string $key): bool
|
||||
{
|
||||
$raw = base64_decode($token, true);
|
||||
if ($raw === false || strlen($raw) <= 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$iv = substr($raw, 0, 16);
|
||||
$ciphertext = substr($raw, 16);
|
||||
|
||||
$plaintext = openssl_decrypt($ciphertext, 'aes-256-cbc', hex2bin($key), OPENSSL_RAW_DATA, $iv);
|
||||
|
||||
if ($plaintext === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hash_equals(config('services.client_api.secret'), $plaintext);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,31 @@
|
||||
|
||||
namespace App\Http\Resources\Api\Client\Service;
|
||||
|
||||
use App\Models\ModuleHostingPleskClient;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class ServiceClientDetailResource extends JsonResource
|
||||
{
|
||||
/** Plesk panel login (persisted, reusable) — separate from the one-time FTP password, which is never stored. */
|
||||
private function resolvePleskCredentials($hosting): ?array
|
||||
{
|
||||
$client = ModuleHostingPleskClient::query()
|
||||
->where('customer_id', $hosting->customer_id)
|
||||
->where('dns_manager_server_id', $hosting->dns_manager_server_id)
|
||||
->first();
|
||||
|
||||
if (!$client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'login' => $client->login,
|
||||
'password' => Crypt::decryptString($client->password),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
@@ -50,6 +70,8 @@ class ServiceClientDetailResource extends JsonResource
|
||||
$order = $orderItem?->order;
|
||||
$vm = $this->resource->vmware;
|
||||
$product = $orderItem?->product;
|
||||
$hosting = $this->resource->hostingAccount;
|
||||
$pleskCredentials = $hosting ? $this->resolvePleskCredentials($hosting) : null;
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
@@ -81,6 +103,17 @@ class ServiceClientDetailResource extends JsonResource
|
||||
'ip' => $vm->ip ?? null,
|
||||
'dns' => $vm->dns ?? null,
|
||||
] : null,
|
||||
'hosting' => $hosting ? [
|
||||
'ip_address' => $hosting->server?->ip_address,
|
||||
'plesk_url' => $hosting->server?->ip_address
|
||||
? "http://{$hosting->server->ip_address}:{$hosting->server->panel_port}/"
|
||||
: null,
|
||||
'hosting_plan' => $hosting->hosting_plan,
|
||||
'ftp_username' => $hosting->ftp_username,
|
||||
'username' => $pleskCredentials['login'] ?? null,
|
||||
'password' => $pleskCredentials['password'] ?? null,
|
||||
'status' => $hosting->status,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class VpsServiceResource extends JsonResource
|
||||
'name' => $product->name,
|
||||
'slug' => $product->slug,
|
||||
'product_type' => $product->product_type,
|
||||
'page_type' => 'service_vps',
|
||||
'page_type' => 'service_' . str_replace(' ', '_', $product->product_type),
|
||||
] : null,
|
||||
'specs' => $this->resolveSpecs($product),
|
||||
];
|
||||
|
||||
@@ -256,11 +256,15 @@ PS;
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
\Illuminate\Support\Facades\Log::error('changeRDPPortWindows: failed to parse PowerCLI output', [
|
||||
'vm_name' => $vmName,
|
||||
'raw_output' => $raw,
|
||||
'json_error' => json_last_error_msg(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'JSON parsing failed',
|
||||
'raw_output' => $raw,
|
||||
'json_error' => json_last_error_msg()
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Jobs\Hosting;
|
||||
|
||||
use App\Enums\Server\ServerType;
|
||||
use App\Models\DnsServer;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\ServicePlan;
|
||||
use App\Services\Plesk\PleskHostingService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@@ -49,14 +48,50 @@ class ProvisionHostingAccountJob implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
$server = DnsServer::query()
|
||||
->where('status', 'activated')
|
||||
->whereIn('type', [ServerType::HOSTING->value, ServerType::BOTH->value])
|
||||
->first();
|
||||
$servicePlanId = $this->item->product?->service_plan_id;
|
||||
$servicePlan = $servicePlanId
|
||||
? ServicePlan::with(['plesk', 'dnsServer'])->find($servicePlanId)
|
||||
: null;
|
||||
|
||||
$hostingPlan = $servicePlan?->plesk?->plesk_plan_name;
|
||||
$server = $servicePlan?->dnsServer;
|
||||
|
||||
Log::channel('Custom')->info('ProvisionHostingAccountJob resolved service plan', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'product_name' => $this->item->product?->name,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
'plesk_plan_name' => $hostingPlan,
|
||||
'dns_manager_server_id' => $server?->id,
|
||||
'dns_manager_server_ip' => $server?->ip_address,
|
||||
]);
|
||||
|
||||
if (! $hostingPlan) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: no service_plan_plesk mapping for this product', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $server) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: no active hosting server available', [
|
||||
'order_item_id' => $this->item->id,
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: service plan has no dns_manager_server_id configured', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'product_id' => $this->item->product_id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $server->servesHosting()) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed: configured server does not serve hosting', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'service_plan_id' => $servicePlanId,
|
||||
'dns_manager_server_id' => $server->id,
|
||||
'server_type' => $server->type,
|
||||
]);
|
||||
|
||||
return;
|
||||
@@ -67,12 +102,13 @@ class ProvisionHostingAccountJob implements ShouldQueue
|
||||
'service_id' => $service->id,
|
||||
'dns_manager_server_id' => $server->id,
|
||||
'domain_name' => $domainName,
|
||||
'hosting_plan' => $this->item->product?->name ?? 'Default',
|
||||
'hosting_plan' => $hostingPlan,
|
||||
]);
|
||||
|
||||
if ($result['error']) {
|
||||
Log::channel('Custom')->error('ProvisionHostingAccountJob failed', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'hosting_plan' => $hostingPlan,
|
||||
'message' => $result['message'] ?? 'Unknown error',
|
||||
]);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class DnsServer extends Model
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'ip_address',
|
||||
'panel_port',
|
||||
'password',
|
||||
'username',
|
||||
'api_key',
|
||||
|
||||
@@ -70,6 +70,12 @@ class Service extends Model
|
||||
return $this->belongsTo(ModuleVmware::class, 'vm_id');
|
||||
}
|
||||
|
||||
/** The Plesk hosting account associated with this service, if type = hosting. */
|
||||
public function hostingAccount()
|
||||
{
|
||||
return $this->hasOne(ModuleHostingAccount::class, 'service_id');
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -12,10 +12,16 @@ class ServicePlan extends Model
|
||||
'ram',
|
||||
'disk',
|
||||
'domain',
|
||||
'dns_manager_server_id',
|
||||
];
|
||||
|
||||
public function plesk()
|
||||
{
|
||||
return $this->hasOne(ServicePlanPlesk::class);
|
||||
}
|
||||
|
||||
public function dnsServer()
|
||||
{
|
||||
return $this->belongsTo(DnsServer::class, 'dns_manager_server_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,15 @@ class OrderRepository extends BaseApiRepository implements OrderRepositoryInterf
|
||||
return $this->model->where('order_num', $orderNum)->where('customer_id', $customerId)->first();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// findForCustomer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public function findForCustomer(int $id, int $customerId): ?Order
|
||||
{
|
||||
return $this->model->where('id', $id)->where('customer_id', $customerId)->first();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// getOrders
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -29,9 +29,7 @@ class ServiceClientRepository extends BaseApiRepository implements ServiceClient
|
||||
->orderBy('started_at', 'desc');
|
||||
|
||||
if ($productType) {
|
||||
$query->whereHas('product', function ($builder) use ($productType) {
|
||||
$builder->where('product_type', $productType);
|
||||
});
|
||||
$query->where('type', $productType);
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
@@ -66,6 +64,7 @@ class ServiceClientRepository extends BaseApiRepository implements ServiceClient
|
||||
'orderItem.product',
|
||||
'orderItem.order',
|
||||
'vmware.ipAddress',
|
||||
'hostingAccount.server',
|
||||
])
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $customerId)
|
||||
|
||||
@@ -138,6 +138,7 @@ class DomainOrderService
|
||||
$service = Service::create([
|
||||
'customer_id' => $lockedCustomer->id,
|
||||
'product_id' => $product->id,
|
||||
'type' => ProductTypeEnums::DOMAIN->value,
|
||||
'order_item_id' => $orderItem->id,
|
||||
'invoice_id' => $invoice->id,
|
||||
'server_name' => $domainName,
|
||||
|
||||
@@ -157,6 +157,7 @@ class OrderService
|
||||
$service = Service::create([
|
||||
'customer_id' => $customer->id,
|
||||
'product_id' => $item->product_id,
|
||||
'type' => $item->product?->product_type,
|
||||
'order_item_id' => $item->id,
|
||||
'invoice_id' => $invoice?->id,
|
||||
'server_name' => $item->server_name,
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Models\DnsServer;
|
||||
use App\Models\ModuleHostingAccount;
|
||||
use App\Repositories\DnsManager\Domain\DnsDomainRepositoryInterface;
|
||||
use App\Repositories\Module\HostingAccount\HostingAccountRepositoryInterface;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Phase 1 orchestration: Create Customer -> Create Hosting Subscription ->
|
||||
@@ -54,7 +55,8 @@ class PleskHostingService
|
||||
$pleskLogin = $client['data']['login'];
|
||||
$pleskPassword = $client['data']['password'];
|
||||
|
||||
$ftpLogin = str_replace('.', '_', $data['domain_name']);
|
||||
// Suffix avoids a collision if the same domain is re-provisioned (e.g. after cancellation).
|
||||
$ftpLogin = str_replace('.', '_', $data['domain_name']) . '_' . Str::lower(Str::random(4));
|
||||
// Not persisted anywhere — the only record of it is this response, once.
|
||||
$ftpPassword = $data['ftp_password'] ?? Helper::general_password();
|
||||
|
||||
|
||||
@@ -39,4 +39,10 @@ return [
|
||||
'wordpress_posts_url' => env('DOCS_WORDPRESS_POSTS_URL', 'https://tailieu.tgs.com.vn/wp-json/wp/v2/posts'),
|
||||
],
|
||||
|
||||
'client_api' => [
|
||||
// openssl rand -hex 32
|
||||
'key' => env('CLIENT_API_ACCESS_KEY'),
|
||||
'secret' => env('CLIENT_API_ACCESS_SECRET', 'tgs-client-api'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->string('type', 50)->nullable()->after('product_id')->index();
|
||||
});
|
||||
|
||||
// Backfill existing rows from products.product_type (vps / hosting / domain / ...).
|
||||
DB::statement(<<<'SQL'
|
||||
UPDATE services
|
||||
JOIN products ON products.id = services.product_id
|
||||
SET services.type = products.product_type
|
||||
WHERE services.type IS NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->dropColumn('type');
|
||||
});
|
||||
}
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('service_plans', function (Blueprint $table) {
|
||||
$table->foreignId('dns_manager_server_id')->nullable()->after('domain')
|
||||
->constrained('dns_manager_servers')->nullOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('service_plans', function (Blueprint $table) {
|
||||
$table->dropConstrainedForeignId('dns_manager_server_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('dns_manager_servers', function (Blueprint $table) {
|
||||
$table->unsignedInteger('panel_port')->default(8880)->after('ip_address');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('dns_manager_servers', function (Blueprint $table) {
|
||||
$table->dropColumn('panel_port');
|
||||
});
|
||||
}
|
||||
};
|
||||
+2
-1
@@ -24,6 +24,7 @@ use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::prefix('v1')
|
||||
->as('api.')
|
||||
->middleware(['client.access-token'])
|
||||
->group(function () {
|
||||
// Auth
|
||||
Route::prefix('client')
|
||||
@@ -263,7 +264,7 @@ Route::prefix('v1')
|
||||
Route::get('v1/payment/vnpay/return', [TransactionController::class, 'returnCallback']);
|
||||
|
||||
Route::prefix('v1/system')
|
||||
->middleware(['api.auth'])
|
||||
->middleware(['client.access-token', 'api.auth'])
|
||||
->group(function () {
|
||||
// VM power & network operations
|
||||
Route::controller(VmController::class)->group(function () {
|
||||
|
||||
@@ -4597,11 +4597,16 @@ jquery.flot@^0.8.3:
|
||||
resolved "https://registry.npmjs.org/jquery.flot/-/jquery.flot-0.8.3.tgz"
|
||||
integrity sha512-/tEE8J5NjwvStHDaCHkvTJpD7wDS4hE1OEL8xEmhgQfUe0gLUem923PIceNez1mz4yBNx6Hjv7pJcowLNd+nbg==
|
||||
|
||||
jquery@*, jquery@>=1.2.6, jquery@>=1.7, jquery@>=1.8.3, jquery@^4.0.0:
|
||||
jquery@*, jquery@>=1.2.6, jquery@>=1.7, jquery@>=1.8.3:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz"
|
||||
integrity sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==
|
||||
|
||||
jquery@3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
|
||||
integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
|
||||
|
||||
jquery@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz"
|
||||
|
||||
Reference in New Issue
Block a user