feat: Add DNS management resources and importer
- Implemented DomainCollection, DomainDetailResource, DomainNSCollection, DomainNSResource, and DomainResource for handling DNS domain and record data. - Created VpsServiceCollection and VpsServiceResource for VPS service management. - Added DnsManagerNS and DnsManagerUser models to represent DNS records and user associations. - Developed AdminZoneDnsSqlImporter for importing DNS data from SQL dumps, including customer and DNS domain records. - Added migration scripts to update the customers table with new fields and sync foreign key references for DNS tables. - Created a migration preview document outlining the mapping and schema changes for the admin zone DNS import.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Imports\AdminZoneDnsSqlImporter;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportAdminZoneDnsSql extends Command
|
||||
{
|
||||
protected $signature = 'admin-zonedns:import
|
||||
{path? : Path to the admin_zonedns SQL dump}
|
||||
{--preview : Parse and report only, without writing to DB}
|
||||
{--customers-only : Import only tbl.users into customers}
|
||||
{--dns-only : Import only DNS tables and customer-domain links}';
|
||||
|
||||
protected $description = 'Import admin_zonedns SQL dump into portal customers and DNS tables';
|
||||
|
||||
public function handle(AdminZoneDnsSqlImporter $importer): int
|
||||
{
|
||||
$path = $this->argument('path') ?: base_path('admin_zonedns (1).sql');
|
||||
$preview = (bool) $this->option('preview');
|
||||
$customersOnly = (bool) $this->option('customers-only');
|
||||
$dnsOnly = (bool) $this->option('dns-only');
|
||||
|
||||
if ($customersOnly && $dnsOnly) {
|
||||
$this->error('Use either --customers-only or --dns-only, not both.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$summary = $importer->import(
|
||||
path: $path,
|
||||
preview: $preview,
|
||||
importCustomers: ! $dnsOnly,
|
||||
importDns: ! $customersOnly,
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$mode = $preview ? 'PREVIEW' : 'IMPORTED';
|
||||
$this->info("{$mode} from: {$path}");
|
||||
|
||||
$this->table(
|
||||
['Section', 'Created', 'Updated', 'Restored', 'Skipped', 'Source rows'],
|
||||
[
|
||||
[
|
||||
'customers',
|
||||
$summary['customers']['created'],
|
||||
$summary['customers']['updated'],
|
||||
$summary['customers']['restored'],
|
||||
$summary['customers']['skipped'],
|
||||
$summary['source']['users'],
|
||||
],
|
||||
[
|
||||
'dns_domains',
|
||||
$summary['dns_domains']['created'],
|
||||
$summary['dns_domains']['updated'],
|
||||
'-',
|
||||
$summary['dns_domains']['skipped'],
|
||||
$summary['source']['dns_manager_domain'],
|
||||
],
|
||||
[
|
||||
'dns_records',
|
||||
$summary['dns_records']['created'],
|
||||
$summary['dns_records']['updated'],
|
||||
'-',
|
||||
$summary['dns_records']['skipped'],
|
||||
$summary['source']['dns_manager_ns'],
|
||||
],
|
||||
[
|
||||
'dns_links',
|
||||
$summary['dns_links']['created'],
|
||||
$summary['dns_links']['updated'],
|
||||
'-',
|
||||
$summary['dns_links']['skipped'],
|
||||
$summary['source']['dns_domain_user'],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\DnsManager;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Repositories\DnsManager\Record\DnsRecordRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class RecordController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DnsRecordRepositoryInterface $recordRepository,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 500);
|
||||
$filter = trim((string) $request->get('filter', ''));
|
||||
$type = trim((string) $request->get('type', ''));
|
||||
|
||||
$query = $this->recordRepository->model
|
||||
->with(['domain.server'])
|
||||
->newQuery();
|
||||
|
||||
if ($filter !== '') {
|
||||
$query->where(function ($subQuery) use ($filter) {
|
||||
$subQuery->where('name', 'like', "%{$filter}%")
|
||||
->orWhere('value', 'like', "%{$filter}%")
|
||||
->orWhereHas('domain', function ($domainQuery) use ($filter) {
|
||||
$domainQuery->where('domain_name', 'like', "%{$filter}%")
|
||||
->orWhereHas('server', function ($serverQuery) use ($filter) {
|
||||
$serverQuery->where('name', 'like', "%{$filter}%")
|
||||
->orWhere('ip_address', 'like', "%{$filter}%");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($type !== '') {
|
||||
$query->where('type', $type);
|
||||
}
|
||||
|
||||
$paginator = $query->orderByDesc('id')->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => $paginator->getCollection()->map(fn($record) => $this->formatRecord($record)),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
$record = $this->recordRepository->model
|
||||
->with(['domain.server'])
|
||||
->find($id);
|
||||
|
||||
if (! $record) {
|
||||
return response()->json(['error' => true, 'message' => "Không tìm thấy bản ghi DNS #$id"], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $this->formatRecord($record),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$record = $this->recordRepository->model
|
||||
->with(['domain.server'])
|
||||
->find($id);
|
||||
|
||||
if (! $record) {
|
||||
return response()->json(['error' => true, 'message' => "Không tìm thấy bản ghi DNS #$id"], 404);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'type' => 'required|string|in:A,AAAA,CNAME,TXT,MX,NS,SRV',
|
||||
'name' => 'required|string|max:255',
|
||||
'value' => 'required|string',
|
||||
'ttl' => 'nullable|integer|min:60',
|
||||
'priority' => 'nullable|integer|min:0',
|
||||
'weight' => 'nullable|integer|min:0',
|
||||
'port' => 'nullable|integer|min:0',
|
||||
'service_name' => 'nullable|string|max:255',
|
||||
'protocol' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
$payload = [
|
||||
'type' => $validated['type'],
|
||||
'type_valid' => $validated['type'],
|
||||
'name' => $validated['name'],
|
||||
'value' => $validated['value'],
|
||||
'ttl' => $validated['ttl'] ?? 300,
|
||||
'priority' => array_key_exists('priority', $validated) ? (string) ($validated['priority'] ?? 0) : $record->priority,
|
||||
'weight' => array_key_exists('weight', $validated) ? (string) ($validated['weight'] ?? 0) : $record->weight,
|
||||
'port' => array_key_exists('port', $validated) ? (string) ($validated['port'] ?? 0) : $record->port,
|
||||
'service_name' => $validated['service_name'] ?? null,
|
||||
'protocol' => $validated['protocol'] ?? null,
|
||||
];
|
||||
|
||||
$this->recordRepository->update($id, $payload);
|
||||
|
||||
return $this->show($id);
|
||||
}
|
||||
|
||||
private function formatRecord($record): array
|
||||
{
|
||||
return [
|
||||
'id' => $record->id,
|
||||
'dns_manager_domain_id' => $record->dns_manager_domain_id,
|
||||
'domain_name' => $record->domain?->domain_name,
|
||||
'dns_manager_server_id' => $record->domain?->dns_manager_server_id,
|
||||
'server_name' => $record->domain?->server?->name,
|
||||
'server_ip_address' => $record->domain?->server?->ip_address,
|
||||
'type' => $record->type,
|
||||
'type_valid' => $record->type_valid,
|
||||
'name' => $record->name,
|
||||
'value' => $record->value,
|
||||
'priority' => $record->priority !== null ? (int) $record->priority : null,
|
||||
'weight' => $record->weight !== null ? (int) $record->weight : null,
|
||||
'port' => $record->port !== null ? (int) $record->port : null,
|
||||
'service_name' => $record->service_name,
|
||||
'protocol' => $record->protocol,
|
||||
'plesk_id' => $record->plesk_id,
|
||||
'ttl' => (int) $record->ttl,
|
||||
'created_at' => $record->created_at,
|
||||
'updated_at' => $record->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -4,46 +4,48 @@ namespace App\Http\Controllers\Api\Admin\DnsManager;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Repositories\DnsManager\Server\DnsServerRepositoryInterface;
|
||||
use App\Services\ServerService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ServerController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DnsServerRepositoryInterface $serverRepository,
|
||||
private readonly ServerService $serverService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 500);
|
||||
$filter = $request->get('filter');
|
||||
$status = $request->get('status');
|
||||
$filter = $request->get('filter');
|
||||
$status = $request->get('status');
|
||||
|
||||
$query = $this->serverRepository->model->newQuery();
|
||||
|
||||
if ($filter) {
|
||||
$query->where(function ($q) use ($filter) {
|
||||
$q->where('name', 'like', "%{$filter}%")
|
||||
->orWhere('ip_address', 'like', "%{$filter}%");
|
||||
$query->where(function ($subQuery) use ($filter) {
|
||||
$subQuery->where('name', 'like', "%{$filter}%")
|
||||
->orWhere('ip_address', 'like', "%{$filter}%")
|
||||
->orWhere('username', 'like', "%{$filter}%");
|
||||
});
|
||||
}
|
||||
|
||||
if ($status) {
|
||||
if ($status !== null && $status !== '') {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$paginator = $query->paginate($perPage);
|
||||
$paginator = $query->orderByDesc('id')->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'items' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'data' => [
|
||||
'items' => $paginator->getCollection()->map(fn($server) => $this->formatServer($server)),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
@@ -56,30 +58,44 @@ class ServerController extends Controller
|
||||
return response()->json(['error' => true, 'message' => "Không tìm thấy server #$id"], 404);
|
||||
}
|
||||
|
||||
return response()->json(['error' => false, 'data' => $server]);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $this->formatServer($server),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'ip_address' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'required|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:activated,Stopped',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'required|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:activated,Stopped',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$data = $request->all();
|
||||
$data['password'] = Crypt::encryptString($request->password);
|
||||
$server = $this->serverRepository->create($data);
|
||||
$server = $this->serverRepository->create([
|
||||
'name' => $validated['name'],
|
||||
'ip_address' => $validated['ip_address'],
|
||||
'username' => $validated['username'],
|
||||
'password' => encrypt($validated['password']),
|
||||
'api_key' => $validated['api_key'] ?? null,
|
||||
'status' => $validated['status'] ?? 'activated',
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $server], 201);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => $this->formatServer($server),
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json(['error' => true, 'message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
@@ -92,29 +108,34 @@ class ServerController extends Controller
|
||||
return response()->json(['error' => true, 'message' => "Không tìm thấy server #$id"], 404);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'ip_address' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'nullable|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:activated,Stopped',
|
||||
$validated = $request->validate([
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
'ip_address' => 'sometimes|required|string|max:255',
|
||||
'username' => 'sometimes|required|string|max:255',
|
||||
'password' => 'nullable|string|max:255',
|
||||
'api_key' => 'nullable|string',
|
||||
'status' => 'nullable|string|in:activated,Stopped',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$data = $request->except(['password']);
|
||||
|
||||
if (!empty($request->password)) {
|
||||
$data['password'] = Crypt::encryptString($request->password);
|
||||
try {
|
||||
$payload = collect($validated)
|
||||
->except('password')
|
||||
->toArray();
|
||||
|
||||
if ($request->filled('password')) {
|
||||
$payload['password'] = encrypt((string) $request->password);
|
||||
}
|
||||
|
||||
$this->serverRepository->update($id, $data);
|
||||
$this->serverRepository->update($id, $payload);
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json(['error' => false, 'data' => $this->serverRepository->find($id)]);
|
||||
return $this->show($id);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json(['error' => true, 'message' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
@@ -131,4 +152,37 @@ class ServerController extends Controller
|
||||
|
||||
return response()->json(['error' => false, 'message' => 'Đã xoá server thành công']);
|
||||
}
|
||||
|
||||
public function checkConnection(int $id): JsonResponse
|
||||
{
|
||||
$server = $this->serverRepository->find($id);
|
||||
|
||||
if (!$server) {
|
||||
return response()->json(['error' => true, 'message' => "Không tìm thấy server #$id"], 404);
|
||||
}
|
||||
|
||||
$connected = $this->serverService->checkConnectionByServer($server);
|
||||
|
||||
return response()->json([
|
||||
'error' => !$connected,
|
||||
'data' => ['connected' => $connected],
|
||||
'message' => $connected
|
||||
? 'Kết nối server thành công.'
|
||||
: 'Không thể kết nối tới server. Vui lòng kiểm tra lại IP hoặc tài khoản.',
|
||||
]);
|
||||
}
|
||||
|
||||
private function formatServer($server): array
|
||||
{
|
||||
return [
|
||||
'id' => $server->id,
|
||||
'name' => $server->name,
|
||||
'ip_address' => $server->ip_address,
|
||||
'username' => $server->username,
|
||||
'api_key' => $server->api_key,
|
||||
'status' => $server->status,
|
||||
'created_at' => $server->created_at,
|
||||
'updated_at' => $server->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\Client\Service;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\Service\DomainCollection;
|
||||
use App\Http\Resources\Api\Service\DomainDetailResource;
|
||||
use App\Http\Resources\Api\Service\DomainNSResource;
|
||||
use App\Models\DnsDomain;
|
||||
use App\Models\DnsManagerNS;
|
||||
use App\Services\ActivityLogger;
|
||||
use App\Services\ServerService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DomainController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ActivityLogger $activityLogger,
|
||||
protected ServerService $serverService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$customer = $request->user();
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
|
||||
$domains = DnsDomain::query()
|
||||
->with('server')
|
||||
->withCount('record')
|
||||
->whereHas('customer', fn ($query) => $query->where('customers.id', $customer->id))
|
||||
->orderByDesc('id')
|
||||
->paginate($perPage);
|
||||
|
||||
return (new DomainCollection($domains))
|
||||
->additional([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_domain',
|
||||
])
|
||||
->response();
|
||||
}
|
||||
|
||||
public function show(Request $request, int $domainId): JsonResponse
|
||||
{
|
||||
$domain = $this->resolveCustomerDomain($request, $domainId);
|
||||
|
||||
if (! $domain) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy domain'], 404);
|
||||
}
|
||||
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
|
||||
$domain->load('server')->loadCount('record');
|
||||
|
||||
$records = DnsManagerNS::query()
|
||||
->where('dns_manager_domain_id', $domain->id)
|
||||
->orderBy('type')
|
||||
->orderBy('name')
|
||||
->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_domain_detail',
|
||||
'data' => new DomainDetailResource($domain, $records),
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeRecord(Request $request, int $domainId): JsonResponse
|
||||
{
|
||||
$domain = $this->resolveCustomerDomain($request, $domainId);
|
||||
|
||||
if (! $domain) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy domain'], 404);
|
||||
}
|
||||
|
||||
$payload = $this->validateDomainRecordPayload($request);
|
||||
|
||||
$this->serverService->findServerByID($domain->dns_manager_server_id);
|
||||
$serverPayload = $this->serverService->handlePayload([
|
||||
...$payload,
|
||||
'domain' => $domain->domain_name,
|
||||
])->addRecord();
|
||||
|
||||
if (! empty($serverPayload['error'])) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $serverPayload['message'] ?? 'Không thể tạo DNS record trên server',
|
||||
], 422);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
$record = DnsManagerNS::query()->create([
|
||||
'dns_manager_domain_id' => $domain->id,
|
||||
'type' => $payload['type'],
|
||||
'type_valid' => $payload['type'],
|
||||
'name' => $payload['name'],
|
||||
'value' => $payload['value'],
|
||||
'ttl' => $payload['ttl'] ?? 300,
|
||||
'priority' => (string) ($payload['priority'] ?? 0),
|
||||
'weight' => (string) ($payload['weight'] ?? 0),
|
||||
'port' => (string) ($payload['port'] ?? 0),
|
||||
'service_name' => $payload['service_name'] ?? null,
|
||||
'protocol' => $payload['protocol'] ?? null,
|
||||
'plesk_id' => $serverPayload['data']['id'] ?? null,
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
|
||||
return response()->json(['error' => true, 'message' => $e->getMessage()], 500);
|
||||
}
|
||||
|
||||
$customer = $request->user();
|
||||
$this->activityLogger->log(
|
||||
event: 'created',
|
||||
logName: 'DNS',
|
||||
description: "Customer {$customer->email} tạo DNS record #{$record->id} của domain {$domain->domain_name}",
|
||||
subject: $record,
|
||||
newValues: [
|
||||
'domain_id' => $domain->id,
|
||||
'domain_name' => $domain->domain_name,
|
||||
'type' => $record->type,
|
||||
'name' => $record->name,
|
||||
'value' => $record->value,
|
||||
],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Tạo DNS record thành công',
|
||||
'data' => new DomainNSResource($record),
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function updateRecord(Request $request, int $domainId, int $recordId): JsonResponse
|
||||
{
|
||||
$domain = $this->resolveCustomerDomain($request, $domainId);
|
||||
|
||||
if (! $domain) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy domain'], 404);
|
||||
}
|
||||
|
||||
$record = DnsManagerNS::query()
|
||||
->where('dns_manager_domain_id', $domain->id)
|
||||
->find($recordId);
|
||||
|
||||
if (! $record) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy bản ghi DNS'], 404);
|
||||
}
|
||||
|
||||
$payload = $this->validateDomainRecordPayload($request);
|
||||
|
||||
if (! empty($record->plesk_id)) {
|
||||
$this->serverService->findServerByID($domain->dns_manager_server_id);
|
||||
$serverPayload = $this->serverService->handlePayload([
|
||||
...$payload,
|
||||
'domain' => $domain->domain_name,
|
||||
])->updateRecord((int) $record->plesk_id);
|
||||
|
||||
if (! empty($serverPayload['error'])) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $serverPayload['message'] ?? 'Không thể cập nhật DNS record trên server',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$record->update([
|
||||
'type' => $payload['type'],
|
||||
'type_valid' => $record->type_valid ?: $payload['type'],
|
||||
'name' => $payload['name'],
|
||||
'value' => $payload['value'],
|
||||
'ttl' => $payload['ttl'] ?? $record->ttl,
|
||||
'priority' => isset($payload['priority']) ? (string) $payload['priority'] : $record->priority,
|
||||
'weight' => isset($payload['weight']) ? (string) $payload['weight'] : $record->weight,
|
||||
'port' => isset($payload['port']) ? (string) $payload['port'] : $record->port,
|
||||
'service_name' => $payload['service_name'] ?? $record->service_name,
|
||||
'protocol' => $payload['protocol'] ?? $record->protocol,
|
||||
]);
|
||||
|
||||
$customer = $request->user();
|
||||
$this->activityLogger->log(
|
||||
event: 'updated',
|
||||
logName: 'DNS',
|
||||
description: "Customer {$customer->email} cập nhật DNS record #{$record->id} của domain {$domain->domain_name}",
|
||||
subject: $record,
|
||||
newValues: [
|
||||
'domain_id' => $domain->id,
|
||||
'domain_name' => $domain->domain_name,
|
||||
'type' => $record->type,
|
||||
'name' => $record->name,
|
||||
'value' => $record->value,
|
||||
],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Cập nhật DNS record thành công',
|
||||
'data' => new DomainNSResource($record),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroyRecord(Request $request, int $domainId, int $recordId): JsonResponse
|
||||
{
|
||||
$domain = $this->resolveCustomerDomain($request, $domainId);
|
||||
|
||||
if (! $domain) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy domain'], 404);
|
||||
}
|
||||
|
||||
$record = DnsManagerNS::query()
|
||||
->where('dns_manager_domain_id', $domain->id)
|
||||
->find($recordId);
|
||||
|
||||
if (! $record) {
|
||||
return response()->json(['error' => true, 'message' => 'Không tìm thấy bản ghi DNS'], 404);
|
||||
}
|
||||
|
||||
if (! empty($record->plesk_id)) {
|
||||
$this->serverService->findServerByID($domain->dns_manager_server_id);
|
||||
$serverPayload = $this->serverService->deleteRecord((int) $record->plesk_id);
|
||||
|
||||
if (! empty($serverPayload['error'])) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $serverPayload['message'] ?? 'Không thể xóa DNS record trên server',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$snapshot = new DomainNSResource($record);
|
||||
$record->delete();
|
||||
|
||||
$customer = $request->user();
|
||||
$this->activityLogger->log(
|
||||
event: 'deleted',
|
||||
logName: 'DNS',
|
||||
description: "Customer {$customer->email} xóa DNS record #{$recordId} của domain {$domain->domain_name}",
|
||||
subject: $domain,
|
||||
newValues: [
|
||||
'domain_id' => $domain->id,
|
||||
'domain_name' => $domain->domain_name,
|
||||
'record_id' => $recordId,
|
||||
],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Xóa DNS record thành công',
|
||||
'data' => $snapshot,
|
||||
]);
|
||||
}
|
||||
|
||||
private function resolveCustomerDomain(Request $request, int $domainId): ?DnsDomain
|
||||
{
|
||||
$customer = $request->user();
|
||||
|
||||
return DnsDomain::query()
|
||||
->with('server')
|
||||
->where('id', $domainId)
|
||||
->whereHas('customer', fn ($query) => $query->where('customers.id', $customer->id))
|
||||
->first();
|
||||
}
|
||||
|
||||
private function validateDomainRecordPayload(Request $request): array
|
||||
{
|
||||
return $request->validate([
|
||||
'type' => ['required', 'string', 'in:A,AAAA,CNAME,TXT,MX,NS,SRV'],
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'value' => ['required', 'string'],
|
||||
'ttl' => ['nullable', 'integer', 'min:60', 'max:86400'],
|
||||
'priority' => ['nullable', 'integer', 'min:0', 'max:65535'],
|
||||
'weight' => ['nullable', 'integer', 'min:0', 'max:65535'],
|
||||
'port' => ['nullable', 'integer', 'min:0', 'max:65535'],
|
||||
'service_name' => ['nullable', 'string', 'max:255'],
|
||||
'protocol' => ['nullable', 'string', 'max:255'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace App\Http\Controllers\Api\Client\Service;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\Api\Service\VpsServiceCollection;
|
||||
use App\Http\Resources\Api\Client\Service\ServiceClientDetailResource;
|
||||
use \App\Http\Resources\Api\Client\Service\ServiceClientResource;
|
||||
use App\Http\Services\vCenter;
|
||||
use App\Models\Order;
|
||||
use App\Models\Service;
|
||||
@@ -31,22 +31,75 @@ class ServiceController extends Controller
|
||||
$this->activityLogger = $activityLogger;
|
||||
}
|
||||
|
||||
public function getService(Request $request)
|
||||
public function indexVps(Request $request): JsonResponse
|
||||
{
|
||||
$limit = $request->get('limit', 10);
|
||||
$services = $this->serviceClientService->getActiveServices($limit);
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
$customer = $request->user();
|
||||
|
||||
return ServiceClientResource::collection($services)->additional([
|
||||
$services = $this->serviceClientService->getActiveServices($customer->id, 'vps', $perPage);
|
||||
|
||||
return (new VpsServiceCollection($services))
|
||||
->additional([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_vps',
|
||||
])
|
||||
->response();
|
||||
}
|
||||
|
||||
public function indexHosting(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service',
|
||||
'page_type' => 'service_hosting',
|
||||
'data' => [
|
||||
'items' => [],
|
||||
'pagination' => [
|
||||
'total' => 0,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function indexWebDesign(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Lấy dữ liệu thành công',
|
||||
'page_type' => 'service_web_design',
|
||||
'data' => [
|
||||
'items' => [],
|
||||
'pagination' => [
|
||||
'total' => 0,
|
||||
'per_page' => $perPage,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDetail(Request $request, string $uuid)
|
||||
{
|
||||
$service = $this->serviceClientService->getDetailService($uuid);
|
||||
$service = $this->serviceClientService->getDetailService($uuid, $request->user()->id);
|
||||
|
||||
if (!$service) {
|
||||
return response()->json(
|
||||
[
|
||||
'error' => true,
|
||||
'message' => 'Không tìm thấy dịch vụ',
|
||||
],
|
||||
404,
|
||||
);
|
||||
}
|
||||
|
||||
return (new ServiceClientDetailResource($service))->additional([
|
||||
'error' => false,
|
||||
@@ -113,7 +166,7 @@ class ServiceController extends Controller
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'error' => false,
|
||||
'message' => $ticket,
|
||||
]);
|
||||
}
|
||||
@@ -188,7 +241,7 @@ class ServiceController extends Controller
|
||||
$customer = $request->user();
|
||||
|
||||
$service = Service::with([
|
||||
'product.osVersions' => fn ($q) => $q->where('status', 1)->with('osGroup'),
|
||||
'product.osVersions' => fn($q) => $q->where('status', 1)->with('osGroup'),
|
||||
])
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $customer->id)
|
||||
@@ -199,7 +252,10 @@ class ServiceController extends Controller
|
||||
}
|
||||
|
||||
if (!in_array($service->status, ['active', 'suspended'], true)) {
|
||||
return response()->json(['success' => false, 'message' => 'Dịch vụ không ở trạng thái hợp lệ để xem OS'], 422);
|
||||
return response()->json(
|
||||
['success' => false, 'message' => 'Dịch vụ không ở trạng thái hợp lệ để xem OS'],
|
||||
422,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$service->product) {
|
||||
@@ -207,32 +263,38 @@ class ServiceController extends Controller
|
||||
}
|
||||
|
||||
$grouped = $service->product->osVersions
|
||||
->groupBy(fn ($os) => $os->osGroup?->id ?? 0)
|
||||
->map(fn ($items) => [
|
||||
'group' => [
|
||||
'id' => $items->first()->osGroup?->id,
|
||||
'name' => $items->first()->osGroup?->name,
|
||||
'thumbnail' => $items->first()->osGroup?->thumbnail,
|
||||
->groupBy(fn($os) => $os->osGroup?->id ?? 0)
|
||||
->map(
|
||||
fn($items) => [
|
||||
'group' => [
|
||||
'id' => $items->first()->osGroup?->id,
|
||||
'name' => $items->first()->osGroup?->name,
|
||||
'thumbnail' => $items->first()->osGroup?->thumbnail,
|
||||
],
|
||||
'items' => $items
|
||||
->map(
|
||||
fn($os) => [
|
||||
'id' => $os->id,
|
||||
'version' => $os->version,
|
||||
'template_id' => $os->template_id,
|
||||
],
|
||||
)
|
||||
->values(),
|
||||
],
|
||||
'items' => $items->map(fn ($os) => [
|
||||
'id' => $os->id,
|
||||
'version' => $os->version,
|
||||
'template_id' => $os->template_id,
|
||||
])->values(),
|
||||
])
|
||||
)
|
||||
->values();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $grouped,
|
||||
'data' => $grouped,
|
||||
]);
|
||||
}
|
||||
|
||||
public function rebuild(Request $request, string $uuid, vCenter $serviceVCenter): JsonResponse
|
||||
{
|
||||
$customer = $request->user();
|
||||
$payload = $request->validate([
|
||||
'os_id' => ['required', 'integer', 'exists:os,id'],
|
||||
$payload = $request->validate([
|
||||
'os_id' => ['required', 'integer', 'exists:os,id'],
|
||||
'password' => ['nullable', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
@@ -246,18 +308,18 @@ class ServiceController extends Controller
|
||||
}
|
||||
|
||||
if ($service->status === 'suspended') {
|
||||
return response()->json(['success' => false, 'message' => 'Dịch vụ đang bị tạm ngưng, không thể rebuild'], 422);
|
||||
return response()->json(
|
||||
['success' => false, 'message' => 'Dịch vụ đang bị tạm ngưng, không thể rebuild'],
|
||||
422,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$service->isActive()) {
|
||||
return response()->json(['success' => false, 'message' => 'Dịch vụ không ở trạng thái active'], 422);
|
||||
}
|
||||
|
||||
$allowedOsIds = $service->product?->osVersions()
|
||||
->where('status', 1)
|
||||
->pluck('os.id')
|
||||
->map('intval')
|
||||
->toArray() ?? [];
|
||||
$allowedOsIds =
|
||||
$service->product?->osVersions()->where('status', 1)->pluck('os.id')->map('intval')->toArray() ?? [];
|
||||
|
||||
if (!in_array((int) $payload['os_id'], $allowedOsIds, true)) {
|
||||
return response()->json(['success' => false, 'message' => 'OS không hợp lệ cho sản phẩm hiện tại'], 422);
|
||||
@@ -269,31 +331,34 @@ class ServiceController extends Controller
|
||||
}
|
||||
|
||||
DB::table('reinstall_logs')->insert([
|
||||
'product_id' => $vm->id,
|
||||
'product_id' => $vm->id,
|
||||
'product_type' => 4,
|
||||
'custome_id' => $customer->id,
|
||||
'os_id' => $payload['os_id'],
|
||||
'status' => 'pending',
|
||||
'initiator' => 'client',
|
||||
'note' => 'Client rebuild via service UUID: ' . $uuid,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'custome_id' => $customer->id,
|
||||
'os_id' => $payload['os_id'],
|
||||
'status' => 'pending',
|
||||
'initiator' => 'client',
|
||||
'note' => 'Client rebuild via service UUID: ' . $uuid,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$result = $serviceVCenter->reBuildVPS((string) $vm->name);
|
||||
if (is_string($result) || ($result['error'] ?? false)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => is_string($result) ? $result : ($result['message'] ?? 'Không thể rebuild VM'),
|
||||
], 400);
|
||||
return response()->json(
|
||||
[
|
||||
'success' => false,
|
||||
'message' => is_string($result) ? $result : $result['message'] ?? 'Không thể rebuild VM',
|
||||
],
|
||||
400,
|
||||
);
|
||||
}
|
||||
|
||||
$this->activityLogger->log(
|
||||
event: 'rebuild',
|
||||
logName: 'VM',
|
||||
event: 'rebuild',
|
||||
logName: 'VM',
|
||||
description: "Customer {$customer->email} rebuild VM: {$vm->name} với OS ID: {$payload['os_id']} qua service UUID: {$uuid}",
|
||||
subject: $vm,
|
||||
newValues: ['os_id' => $payload['os_id'], 'service_uuid' => $uuid],
|
||||
subject: $vm,
|
||||
newValues: ['os_id' => $payload['os_id'], 'service_uuid' => $uuid],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
@@ -301,5 +366,4 @@ class ServiceController extends Controller
|
||||
'message' => 'Đã gửi yêu cầu rebuild VM',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class DomainRequest extends FormRequest
|
||||
'domain_name' => 'required|string|max:255',
|
||||
'dns_manager_server_id' => 'required|integer|exists:dns_manager_servers,id',
|
||||
'user_id' => 'nullable|array',
|
||||
'user_id.*' => 'integer|exists:users,id',
|
||||
'user_id.*' => 'integer|exists:customers,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class DomainCollection extends ResourceCollection
|
||||
{
|
||||
public $collects = DomainResource::class;
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$data = [
|
||||
'items' => $this->collection,
|
||||
];
|
||||
|
||||
if (method_exists($this->resource, 'total')) {
|
||||
$data['pagination'] = [
|
||||
'total' => $this->resource->total(),
|
||||
'per_page' => $this->resource->perPage(),
|
||||
'current_page' => $this->resource->currentPage(),
|
||||
'last_page' => $this->resource->lastPage(),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DomainDetailResource extends JsonResource
|
||||
{
|
||||
public function __construct($resource, protected mixed $records = null)
|
||||
{
|
||||
parent::__construct($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'domain' => new DomainResource($this->resource),
|
||||
'ns_count' => (int) ($this->record_count ?? 0),
|
||||
'records' => $this->records ? new DomainNSCollection($this->records) : [
|
||||
'items' => [],
|
||||
'pagination' => [
|
||||
'total' => 0,
|
||||
'per_page' => 0,
|
||||
'current_page' => 1,
|
||||
'last_page' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class DomainNSCollection extends ResourceCollection
|
||||
{
|
||||
public $collects = DomainNSResource::class;
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$data = [
|
||||
'items' => $this->collection,
|
||||
];
|
||||
|
||||
if (method_exists($this->resource, 'total')) {
|
||||
$data['pagination'] = [
|
||||
'total' => $this->resource->total(),
|
||||
'per_page' => $this->resource->perPage(),
|
||||
'current_page' => $this->resource->currentPage(),
|
||||
'last_page' => $this->resource->lastPage(),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DomainNSResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'dns_manager_domain_id' => $this->dns_manager_domain_id,
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'type_valid' => $this->type_valid,
|
||||
'priority' => $this->priority,
|
||||
'weight' => $this->weight,
|
||||
'port' => $this->port,
|
||||
'service_name' => $this->service_name,
|
||||
'protocol' => $this->protocol,
|
||||
'value' => $this->value,
|
||||
'ttl' => isset($this->ttl) ? (int) $this->ttl : null,
|
||||
'plesk_id' => $this->plesk_id,
|
||||
'created_at' => optional($this->created_at)->toISOString(),
|
||||
'updated_at' => optional($this->updated_at)->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class DomainResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'domain_name' => $this->domain_name,
|
||||
'dns_manager_server_id' => $this->dns_manager_server_id,
|
||||
'server_name' => $this->server?->name,
|
||||
'server_ip' => $this->server?->ip_address,
|
||||
'is_created' => isset($this->is_created) ? (int) $this->is_created : null,
|
||||
'plesk_id' => $this->plesk_id,
|
||||
'record_count' => $this->whenCounted('record'),
|
||||
'created_at' => optional($this->created_at)->toISOString(),
|
||||
'updated_at' => optional($this->updated_at)->toISOString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class VpsServiceCollection extends ResourceCollection
|
||||
{
|
||||
public $collects = VpsServiceResource::class;
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$data = [
|
||||
'items' => $this->collection,
|
||||
];
|
||||
|
||||
if (method_exists($this->resource, 'total')) {
|
||||
$data['pagination'] = [
|
||||
'total' => $this->resource->total(),
|
||||
'per_page' => $this->resource->perPage(),
|
||||
'current_page' => $this->resource->currentPage(),
|
||||
'last_page' => $this->resource->lastPage(),
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources\Api\Service;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class VpsServiceResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$orderItem = $this->orderItem;
|
||||
$product = $orderItem?->product;
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'uuid' => $this->uuid,
|
||||
'server_name' => $this->server_name,
|
||||
'status' => $this->status,
|
||||
'status_label' => $this->status_label,
|
||||
'auto_renew' => (bool) $this->auto_renew,
|
||||
'started_at' => $this->started_at?->toISOString(),
|
||||
'expires_at' => $this->expires_at?->toISOString(),
|
||||
'os' => [
|
||||
'version' => $orderItem?->os?->version,
|
||||
'group' => $orderItem?->os?->osGroup?->name,
|
||||
],
|
||||
'product' => $product ? [
|
||||
'id' => $product->id,
|
||||
'name' => $product->name,
|
||||
'slug' => $product->slug,
|
||||
'product_type' => $product->product_type,
|
||||
'page_type' => 'service_vps',
|
||||
] : null,
|
||||
'specs' => $product ? [
|
||||
'cpu' => (int) ($product->cpu ?? 0),
|
||||
'ram' => (int) ($product->ram ?? 0),
|
||||
'disk' => (int) ($product->disk ?? 0),
|
||||
'bandwidth' => (int) ($product->bandwidth ?? 0),
|
||||
'gpu' => $product->gpu,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
|
||||
protected $fillable = [
|
||||
'fullname',
|
||||
'username',
|
||||
'avatar',
|
||||
'address',
|
||||
'state',
|
||||
@@ -35,6 +36,7 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
'company',
|
||||
'phone',
|
||||
'email',
|
||||
'email_verified_at',
|
||||
'email_verify',
|
||||
'email_hash',
|
||||
'email_marketing',
|
||||
@@ -43,6 +45,7 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
'front_identity_number',
|
||||
'back_identity_number',
|
||||
'password',
|
||||
'type',
|
||||
'credit',
|
||||
'total',
|
||||
'total_pending',
|
||||
@@ -55,6 +58,7 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
|
||||
protected $updatable = [
|
||||
'fullname',
|
||||
'username',
|
||||
'phone',
|
||||
'company',
|
||||
'address',
|
||||
@@ -82,6 +86,7 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
|
||||
protected $casts = [
|
||||
'gender' => \App\Enums\Client\ClientGender::class,
|
||||
'email_verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -215,6 +220,11 @@ class Customer extends Authenticatable implements JWTSubject
|
||||
return (string) floor($this->total / 100);
|
||||
}
|
||||
|
||||
public function getNameAttribute()
|
||||
{
|
||||
return $this->fullname;
|
||||
}
|
||||
|
||||
public function getCmndBeforeAttribute()
|
||||
{
|
||||
return $this->front_identity_number ?? '';
|
||||
|
||||
@@ -30,11 +30,21 @@ class DnsDomain extends Model
|
||||
return $this->belongsToMany(Customer::class, 'dns_domain_user', 'domain_id', 'user_id')->withTimestamps();
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->customer();
|
||||
}
|
||||
|
||||
public function server()
|
||||
{
|
||||
return $this->belongsTo(DnsServer::class, 'dns_manager_server_id', 'id');
|
||||
}
|
||||
|
||||
public function ns()
|
||||
{
|
||||
return $this->hasMany(DnsManagerNS::class, 'dns_manager_domain_id', 'id');
|
||||
}
|
||||
|
||||
public function record()
|
||||
{
|
||||
return $this->hasMany(DnsRecord::class, 'dns_manager_domain_id', 'id');
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DnsManagerNS extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'dns_manager_ns';
|
||||
|
||||
protected $fillable = [
|
||||
'dns_manager_domain_id',
|
||||
'name',
|
||||
'type',
|
||||
'type_valid',
|
||||
'priority',
|
||||
'weight',
|
||||
'port',
|
||||
'service_name',
|
||||
'protocol',
|
||||
'value',
|
||||
'ttl',
|
||||
'plesk_id',
|
||||
];
|
||||
|
||||
public function managerDomain()
|
||||
{
|
||||
return $this->belongsTo(DnsDomain::class, 'dns_manager_domain_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DnsManagerUser extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'dns_domain_user';
|
||||
|
||||
protected $fillable = [
|
||||
'domain_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function domain()
|
||||
{
|
||||
return $this->belongsTo(DnsDomain::class, 'domain_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,21 @@ class ServiceClientRepository extends BaseApiRepository implements ServiceClient
|
||||
/**
|
||||
* Lấy toàn bộ dịch vụ của customer (mọi trạng thái)
|
||||
*/
|
||||
public function getActiveServices(int $limit = 10)
|
||||
public function getActiveServices(int $customerId, ?string $productType = null, int $limit = 10)
|
||||
{
|
||||
return $this->model
|
||||
$query = $this->model
|
||||
->with(['orderItem' => fn($q) => $q->with(['product', 'os.osGroup'])])
|
||||
->where('customer_id', $customerId)
|
||||
->orderByRaw("FIELD(status, 'active', 'pending', 'suspended', 'expired', 'cancelled', 'terminated')")
|
||||
->orderBy('started_at', 'desc')
|
||||
->paginate($limit);
|
||||
->orderBy('started_at', 'desc');
|
||||
|
||||
if ($productType) {
|
||||
$query->whereHas('orderItem.product', function ($builder) use ($productType) {
|
||||
$builder->where('product_type', $productType);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->paginate($limit);
|
||||
}
|
||||
|
||||
public function findActiveBySlug(string $slug)
|
||||
@@ -44,7 +52,7 @@ class ServiceClientRepository extends BaseApiRepository implements ServiceClient
|
||||
return $pricing ? $pricing->price : null;
|
||||
}
|
||||
|
||||
public function getDetailService(string $uuid)
|
||||
public function getDetailService(string $uuid, int $customerId)
|
||||
{
|
||||
return $this->model
|
||||
->with([
|
||||
@@ -54,6 +62,7 @@ class ServiceClientRepository extends BaseApiRepository implements ServiceClient
|
||||
'vmware.ipAddress',
|
||||
])
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $customerId)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ interface ServiceClientRepositoryInterface extends BaseRepositoryInterface
|
||||
/**
|
||||
* Lấy danh sách sản phẩm đang hoạt động
|
||||
*/
|
||||
public function getActiveServices(int $limit = 10);
|
||||
public function getActiveServices(int $customerId, ?string $productType = null, int $limit = 10);
|
||||
|
||||
public function findActiveBySlug(string $slug);
|
||||
|
||||
public function getPricing(int $productId, string $billingCycle);
|
||||
|
||||
public function getDetailService(string $uuid);
|
||||
public function getDetailService(string $uuid, int $customerId);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,16 @@ use App\Repositories\BaseRepository;
|
||||
|
||||
class CustomerRepository extends BaseRepository implements CustomerRepositoryInterface
|
||||
{
|
||||
private const STATUS_MAP = [
|
||||
'active' => 1,
|
||||
'inactive' => 2,
|
||||
'de_active' => 2,
|
||||
'pending' => 0,
|
||||
'closed' => 2,
|
||||
'suspended' => 3,
|
||||
'banned' => 3,
|
||||
];
|
||||
|
||||
public function getModel()
|
||||
{
|
||||
return Customer::class;
|
||||
@@ -19,6 +29,16 @@ class CustomerRepository extends BaseRepository implements CustomerRepositoryInt
|
||||
bool $returnQuery = false
|
||||
) {
|
||||
$model = $this->model->with($relationships)->select($select);
|
||||
if (isset($data['filter']) && ! empty($data['filter'])) {
|
||||
$model = $model->where(function ($query) use ($data) {
|
||||
$filter = trim($data['filter']);
|
||||
$query->where('fullname', 'LIKE', "%{$filter}%")
|
||||
->orWhere('username', 'LIKE', "%{$filter}%")
|
||||
->orWhere('company', 'LIKE', "%{$filter}%")
|
||||
->orWhere('email', 'LIKE', "%{$filter}%")
|
||||
->orWhere('phone', 'LIKE', "%{$filter}%");
|
||||
});
|
||||
}
|
||||
if (isset($data['name']) && ! empty($data['name'])) {
|
||||
$model = $model->where(function ($query) use ($data) {
|
||||
$name = trim($data['name']);
|
||||
@@ -33,11 +53,14 @@ class CustomerRepository extends BaseRepository implements CustomerRepositoryInt
|
||||
$model = $model->where("phone", "LIKE", "%" . $data['phone'] . "%");
|
||||
}
|
||||
if (isset($data['status']) && ! empty($data['status']) && $data['status'] != 'full') {
|
||||
$model = $model->where("status", match ($data['status']) {
|
||||
'active' => 1,
|
||||
'de_active' => 0,
|
||||
'closed' => 2,
|
||||
});
|
||||
$status = (string) $data['status'];
|
||||
$statusValue = is_numeric($status)
|
||||
? (int) $status
|
||||
: (self::STATUS_MAP[$status] ?? null);
|
||||
|
||||
if ($statusValue !== null) {
|
||||
$model = $model->where('status', $statusValue);
|
||||
}
|
||||
}
|
||||
if (isset($data['relations'])) {
|
||||
foreach ($data['relations'] as $key => $value) {
|
||||
|
||||
@@ -35,7 +35,7 @@ class DnsDomainRepository extends BaseRepository implements DnsDomainRepositoryI
|
||||
|
||||
if (isset($data['user_id']) && $data['user_id']) {
|
||||
$model = $model->whereHas('users', function ($query) use ($data) {
|
||||
$query->where('users.id', $data['user_id']);
|
||||
$query->where('customers.id', $data['user_id']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ class ServiceClientService
|
||||
/**
|
||||
* Lấy danh sách dịch vụ đang hoạt động
|
||||
*/
|
||||
public function getActiveServices(int $limit = 10)
|
||||
public function getActiveServices(int $customerId, ?string $productType = null, int $limit = 10)
|
||||
{
|
||||
return $this->serviceClientRepository->getActiveServices($limit);
|
||||
return $this->serviceClientRepository->getActiveServices($customerId, $productType, $limit);
|
||||
}
|
||||
|
||||
public function findActiveBySlug(string $slug)
|
||||
@@ -26,8 +26,8 @@ class ServiceClientService
|
||||
return $this->serviceClientRepository->findActiveBySlug($slug);
|
||||
}
|
||||
|
||||
public function getDetailService(string $uuid)
|
||||
public function getDetailService(string $uuid, int $customerId)
|
||||
{
|
||||
return $this->serviceClientRepository->getDetailService($uuid);
|
||||
return $this->serviceClientRepository->getDetailService($uuid, $customerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,615 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Imports;
|
||||
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
|
||||
class AdminZoneDnsSqlImporter
|
||||
{
|
||||
private ?int $canonicalServerId = null;
|
||||
|
||||
public function import(
|
||||
string $path,
|
||||
bool $preview = false,
|
||||
bool $importCustomers = true,
|
||||
bool $importDns = true,
|
||||
): array {
|
||||
if (! is_file($path)) {
|
||||
throw new RuntimeException("SQL file not found: {$path}");
|
||||
}
|
||||
|
||||
$this->assertRequiredSchema();
|
||||
|
||||
$tables = $this->parseSqlDump($path, [
|
||||
'users',
|
||||
'dns_manager_domain',
|
||||
'dns_domain_user',
|
||||
'dns_manager_ns',
|
||||
]);
|
||||
|
||||
$summary = [
|
||||
'source' => [
|
||||
'users' => count($tables['users']),
|
||||
'dns_manager_domain' => count($tables['dns_manager_domain']),
|
||||
'dns_domain_user' => count($tables['dns_domain_user']),
|
||||
'dns_manager_ns' => count($tables['dns_manager_ns']),
|
||||
],
|
||||
'customers' => [
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'restored' => 0,
|
||||
'skipped' => 0,
|
||||
],
|
||||
'dns_domains' => [
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
],
|
||||
'dns_records' => [
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
],
|
||||
'dns_links' => [
|
||||
'created' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
if (! $importCustomers && ! $importDns) {
|
||||
return $summary;
|
||||
}
|
||||
|
||||
$callback = function () use ($tables, &$summary, $preview, $importCustomers, $importDns) {
|
||||
$customerIdMap = [];
|
||||
$domainIdMap = [];
|
||||
|
||||
if ($importCustomers) {
|
||||
$customerIdMap = $this->importCustomers($tables['users'], $summary, $preview);
|
||||
}
|
||||
|
||||
if ($importDns) {
|
||||
$domainIdMap = $this->importDnsDomains($tables['dns_manager_domain'], $summary, $preview);
|
||||
$this->importDnsRecords($tables['dns_manager_ns'], $domainIdMap, $summary, $preview);
|
||||
$this->importDnsCustomerLinks($tables['dns_domain_user'], $customerIdMap, $domainIdMap, $summary, $preview);
|
||||
}
|
||||
};
|
||||
|
||||
if ($preview) {
|
||||
$callback();
|
||||
return $summary;
|
||||
}
|
||||
|
||||
DB::transaction($callback);
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
private function assertRequiredSchema(): void
|
||||
{
|
||||
foreach (['username', 'email_verified_at', 'type'] as $column) {
|
||||
if (! Schema::hasColumn('customers', $column)) {
|
||||
throw new RuntimeException(
|
||||
"Missing customers.{$column}. Run migrations before importing admin_zonedns data."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function importCustomers(array $rows, array &$summary, bool $preview): array
|
||||
{
|
||||
$idMap = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$sourceId = (int) ($row['id'] ?? 0);
|
||||
if ($sourceId <= 0) {
|
||||
$summary['customers']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = $this->findCustomerMatch($row);
|
||||
$payload = $this->buildCustomerPayload($row);
|
||||
|
||||
if (! $match) {
|
||||
$existingById = Customer::withTrashed()->find($sourceId);
|
||||
|
||||
if ($preview) {
|
||||
$summary['customers']['created']++;
|
||||
$idMap[$sourceId] = $existingById?->id ?? $sourceId;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $existingById) {
|
||||
$payload['id'] = $sourceId;
|
||||
}
|
||||
|
||||
$payload['uuid'] = (string) Str::uuid();
|
||||
|
||||
$customerId = DB::table('customers')->insertGetId($payload);
|
||||
$summary['customers']['created']++;
|
||||
$idMap[$sourceId] = $customerId;
|
||||
continue;
|
||||
}
|
||||
|
||||
$idMap[$sourceId] = $match->id;
|
||||
|
||||
if ($preview) {
|
||||
$summary['customers']['updated']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$restored = method_exists($match, 'trashed') && $match->trashed();
|
||||
if ($restored) {
|
||||
$match->restore();
|
||||
$summary['customers']['restored']++;
|
||||
}
|
||||
|
||||
DB::table('customers')
|
||||
->where('id', $match->id)
|
||||
->update($payload);
|
||||
|
||||
$summary['customers']['updated']++;
|
||||
}
|
||||
|
||||
return $idMap;
|
||||
}
|
||||
|
||||
private function importDnsDomains(array $rows, array &$summary, bool $preview): array
|
||||
{
|
||||
$idMap = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$sourceId = (int) ($row['id'] ?? 0);
|
||||
if ($sourceId <= 0) {
|
||||
$summary['dns_domains']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$domainName = trim((string) ($row['domain_name'] ?? ''));
|
||||
if ($domainName === '') {
|
||||
$summary['dns_domains']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = DB::table('dns_manager_domain')->where('id', $sourceId)->first();
|
||||
if (! $existing) {
|
||||
$existing = DB::table('dns_manager_domain')->where('domain_name', $domainName)->first();
|
||||
}
|
||||
|
||||
$targetId = $existing?->id ?? $sourceId;
|
||||
$idMap[$sourceId] = (int) $targetId;
|
||||
|
||||
$payload = [
|
||||
'domain_name' => $domainName,
|
||||
'dns_manager_server_id' => $this->resolveDnsServerId((int) ($row['dns_manager_server_id'] ?? 0)),
|
||||
'is_created' => (int) ($row['is_created'] ?? 0),
|
||||
'plesk_id' => $this->nullableInt($row['plesk_id'] ?? null),
|
||||
'created_at' => $this->normalizeDateTime($row['created_at'] ?? null) ?? now(),
|
||||
'updated_at' => $this->normalizeDateTime($row['updated_at'] ?? null) ?? now(),
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('dns_manager_domain', 'user_id')) {
|
||||
$payload['user_id'] = $existing?->user_id ?? 0;
|
||||
}
|
||||
|
||||
if ($preview) {
|
||||
$summary['dns_domains'][$existing ? 'updated' : 'created']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
DB::table('dns_manager_domain')->where('id', $existing->id)->update($payload);
|
||||
$summary['dns_domains']['updated']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload['id'] = $sourceId;
|
||||
DB::table('dns_manager_domain')->insert($payload);
|
||||
$summary['dns_domains']['created']++;
|
||||
}
|
||||
|
||||
return $idMap;
|
||||
}
|
||||
|
||||
private function importDnsRecords(array $rows, array $domainIdMap, array &$summary, bool $preview): void
|
||||
{
|
||||
foreach ($rows as $row) {
|
||||
$sourceId = (int) ($row['id'] ?? 0);
|
||||
$sourceDomainId = (int) ($row['dns_manager_domain_id'] ?? 0);
|
||||
$targetDomainId = $domainIdMap[$sourceDomainId] ?? null;
|
||||
|
||||
if ($sourceId <= 0 || ! $targetDomainId) {
|
||||
$summary['dns_records']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = DB::table('dns_manager_ns')->where('id', $sourceId)->first();
|
||||
$payload = [
|
||||
'dns_manager_domain_id' => $targetDomainId,
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
'type' => (string) ($row['type'] ?? 'A'),
|
||||
'priority' => $this->nullableString($row['priority'] ?? null, '0'),
|
||||
'weight' => $this->nullableString($row['weight'] ?? null, '0'),
|
||||
'port' => $this->nullableString($row['port'] ?? null, '0'),
|
||||
'service_name' => $this->nullableString($row['service_name'] ?? null),
|
||||
'protocol' => $this->nullableString($row['protocol'] ?? null),
|
||||
'value' => $this->nullableString($row['value'] ?? null),
|
||||
'plesk_id' => $this->nullableInt($row['plesk_id'] ?? null),
|
||||
'ttl' => (int) ($row['ttl'] ?? 300),
|
||||
'created_at' => $this->normalizeDateTime($row['created_at'] ?? null) ?? now(),
|
||||
'updated_at' => $this->normalizeDateTime($row['updated_at'] ?? null) ?? now(),
|
||||
];
|
||||
|
||||
if (Schema::hasColumn('dns_manager_ns', 'type_valid')) {
|
||||
$payload['type_valid'] = $existing?->type_valid ?? (string) ($row['type'] ?? 'A');
|
||||
}
|
||||
|
||||
if ($preview) {
|
||||
$summary['dns_records'][$existing ? 'updated' : 'created']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
DB::table('dns_manager_ns')->where('id', $sourceId)->update($payload);
|
||||
$summary['dns_records']['updated']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$payload['id'] = $sourceId;
|
||||
DB::table('dns_manager_ns')->insert($payload);
|
||||
$summary['dns_records']['created']++;
|
||||
}
|
||||
}
|
||||
|
||||
private function importDnsCustomerLinks(
|
||||
array $rows,
|
||||
array $customerIdMap,
|
||||
array $domainIdMap,
|
||||
array &$summary,
|
||||
bool $preview,
|
||||
): void {
|
||||
foreach ($rows as $row) {
|
||||
$domainId = $domainIdMap[(int) ($row['domain_id'] ?? 0)] ?? null;
|
||||
$customerId = $customerIdMap[(int) ($row['user_id'] ?? 0)] ?? null;
|
||||
|
||||
if (! $domainId || ! $customerId) {
|
||||
$summary['dns_links']['skipped']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = DB::table('dns_domain_user')
|
||||
->where('domain_id', $domainId)
|
||||
->where('user_id', $customerId)
|
||||
->first();
|
||||
|
||||
$payload = [
|
||||
'domain_id' => $domainId,
|
||||
'user_id' => $customerId,
|
||||
'created_at' => $this->normalizeDateTime($row['created_at'] ?? null) ?? now(),
|
||||
'updated_at' => $this->normalizeDateTime($row['updated_at'] ?? null) ?? now(),
|
||||
];
|
||||
|
||||
if ($preview) {
|
||||
$summary['dns_links'][$existing ? 'updated' : 'created']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
DB::table('dns_domain_user')
|
||||
->where('id', $existing->id)
|
||||
->update(['updated_at' => $payload['updated_at']]);
|
||||
$summary['dns_links']['updated']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DB::table('dns_domain_user')->insert($payload);
|
||||
$summary['dns_links']['created']++;
|
||||
}
|
||||
}
|
||||
|
||||
private function findCustomerMatch(array $row): ?Customer
|
||||
{
|
||||
$email = $this->nullableString($row['email'] ?? null);
|
||||
if ($email) {
|
||||
$match = Customer::withTrashed()->where('email', $email)->first();
|
||||
if ($match) {
|
||||
return $match;
|
||||
}
|
||||
}
|
||||
|
||||
$username = $this->nullableString($row['username'] ?? null);
|
||||
if ($username && Schema::hasColumn('customers', 'username')) {
|
||||
return Customer::withTrashed()->where('username', $username)->first();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function buildCustomerPayload(array $row): array
|
||||
{
|
||||
$email = $this->nullableString($row['email'] ?? null);
|
||||
|
||||
return [
|
||||
'fullname' => $this->nullableString($row['name'] ?? null) ?? $this->nullableString($row['username'] ?? null) ?? $email,
|
||||
'username' => $this->nullableString($row['username'] ?? null),
|
||||
'email' => $email,
|
||||
'email_verified_at' => $this->normalizeDateTime($row['email_verified_at'] ?? null),
|
||||
'password' => $this->nullableString($row['password'] ?? null),
|
||||
'type' => $this->nullableString($row['type'] ?? null, 'guest'),
|
||||
'remember_token' => $this->nullableString($row['remember_token'] ?? null),
|
||||
'status' => $this->mapCustomerStatus($row['status'] ?? null),
|
||||
'created_at' => $this->normalizeDateTime($row['created_at'] ?? null) ?? now(),
|
||||
'updated_at' => $this->normalizeDateTime($row['updated_at'] ?? null) ?? now(),
|
||||
];
|
||||
}
|
||||
|
||||
private function mapCustomerStatus(mixed $status): int
|
||||
{
|
||||
return match (strtolower(trim((string) $status))) {
|
||||
'verified', 'active', 'activated', '1' => 1,
|
||||
'unverified', 'inactive', 'deactivated', '0', '2' => 2,
|
||||
default => 2,
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveDnsServerId(int $sourceServerId): int
|
||||
{
|
||||
if ($sourceServerId > 0 && DB::table('dns_manager_servers')->where('id', $sourceServerId)->exists()) {
|
||||
return $sourceServerId;
|
||||
}
|
||||
|
||||
if ($this->canonicalServerId !== null) {
|
||||
return $this->canonicalServerId;
|
||||
}
|
||||
|
||||
$server = DB::table('dns_manager_servers')
|
||||
->select('id')
|
||||
->orderByRaw('CASE WHEN id = 1 THEN 0 ELSE 1 END')
|
||||
->orderBy('id')
|
||||
->first();
|
||||
|
||||
if (! $server) {
|
||||
throw new RuntimeException('No dns_manager_servers available to map imported domains.');
|
||||
}
|
||||
|
||||
return $this->canonicalServerId = (int) $server->id;
|
||||
}
|
||||
|
||||
private function parseSqlDump(string $path, array $tables): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($tables as $table) {
|
||||
$result[$table] = [];
|
||||
}
|
||||
|
||||
$handle = fopen($path, 'rb');
|
||||
if (! $handle) {
|
||||
throw new RuntimeException("Unable to open SQL file: {$path}");
|
||||
}
|
||||
|
||||
$activeTable = null;
|
||||
$activeColumns = [];
|
||||
$buffer = '';
|
||||
$state = ['in_string' => false, 'escaped' => false];
|
||||
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
if ($activeTable === null) {
|
||||
$trimmed = trim($line);
|
||||
if (
|
||||
preg_match('/^INSERT INTO `([^`]+)` \((.+)\) VALUES$/', $trimmed, $matches)
|
||||
&& in_array($matches[1], $tables, true)
|
||||
) {
|
||||
$activeTable = $matches[1];
|
||||
$activeColumns = $this->parseColumns($matches[2]);
|
||||
$buffer = '';
|
||||
$state = ['in_string' => false, 'escaped' => false];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer .= $line;
|
||||
|
||||
if (! $this->lineEndsStatement($line, $state)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueBlock = preg_replace('/;\s*$/', '', trim($buffer));
|
||||
$result[$activeTable] = array_merge(
|
||||
$result[$activeTable],
|
||||
$this->parseValueBlock($valueBlock, $activeColumns)
|
||||
);
|
||||
|
||||
$activeTable = null;
|
||||
$activeColumns = [];
|
||||
$buffer = '';
|
||||
$state = ['in_string' => false, 'escaped' => false];
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function parseColumns(string $columnSegment): array
|
||||
{
|
||||
preg_match_all('/`([^`]+)`/', $columnSegment, $matches);
|
||||
return $matches[1] ?? [];
|
||||
}
|
||||
|
||||
private function lineEndsStatement(string $line, array &$state): bool
|
||||
{
|
||||
$length = strlen($line);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = $line[$i];
|
||||
|
||||
if ($state['in_string']) {
|
||||
if ($state['escaped']) {
|
||||
$state['escaped'] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === '\\') {
|
||||
$state['escaped'] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === "'") {
|
||||
$state['in_string'] = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === "'") {
|
||||
$state['in_string'] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === ';') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parseValueBlock(string $valueBlock, array $columns): array
|
||||
{
|
||||
$rows = [];
|
||||
$fields = [];
|
||||
$field = '';
|
||||
$depth = 0;
|
||||
$inString = false;
|
||||
$escaped = false;
|
||||
$length = strlen($valueBlock);
|
||||
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$char = $valueBlock[$i];
|
||||
|
||||
if ($inString) {
|
||||
$field .= $char;
|
||||
|
||||
if ($escaped) {
|
||||
$escaped = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === '\\') {
|
||||
$escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === "'") {
|
||||
$inString = false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === "'") {
|
||||
$inString = true;
|
||||
$field .= $char;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === '(') {
|
||||
if ($depth > 0) {
|
||||
$field .= $char;
|
||||
}
|
||||
$depth++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === ')') {
|
||||
if ($depth > 1) {
|
||||
$field .= $char;
|
||||
$depth--;
|
||||
continue;
|
||||
}
|
||||
|
||||
$fields[] = $this->normalizeSqlValue($field);
|
||||
$rows[] = array_combine($columns, $fields);
|
||||
$fields = [];
|
||||
$field = '';
|
||||
$depth = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === ',' && $depth === 1) {
|
||||
$fields[] = $this->normalizeSqlValue($field);
|
||||
$field = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($depth >= 1) {
|
||||
$field .= $char;
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function normalizeSqlValue(string $value): mixed
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
if (strcasecmp($value, 'NULL') === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($value[0] === "'" && substr($value, -1) === "'") {
|
||||
return stripcslashes(substr($value, 1, -1));
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return str_contains($value, '.') ? (float) $value : (int) $value;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function normalizeDateTime(mixed $value): ?string
|
||||
{
|
||||
$value = $this->nullableString($value);
|
||||
if (! $value || $value === '0000-00-00 00:00:00') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function nullableString(mixed $value, ?string $default = null): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$value = trim((string) $value);
|
||||
|
||||
if ($value === '') {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function nullableInt(mixed $value): ?int
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,20 @@ class ServerService
|
||||
}
|
||||
}
|
||||
|
||||
public function checkConnectionByServer(DnsServer $server): bool
|
||||
{
|
||||
try {
|
||||
return $this->checkConnection([
|
||||
'username' => $server->username,
|
||||
'password' => Crypt::decryptString($server->password),
|
||||
'ip_address' => $server->ip_address,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getDomainInfo(string $domain = ''): array|false
|
||||
{
|
||||
if (empty($domain)) {
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# Preview: admin_zonedns -> customers
|
||||
|
||||
## Nguon doi chieu
|
||||
|
||||
- SQL dump: `portal/admin_zonedns (1).sql`
|
||||
- DB hien tai tu `portal/.env`: `tgs_portal`
|
||||
- Model doi chieu: `portal/app/Models/Customer.php`
|
||||
|
||||
## Xac nhan mapping
|
||||
|
||||
- `tbl.users` trong file SQL duoc map sang bang `customers` cua portal.
|
||||
- Vi vay, khong tao bang `users` moi trong portal.
|
||||
- Muc tieu la:
|
||||
- giu `dns_domain_user`, `dns_manager_domain`, `dns_manager_ns`
|
||||
- bo sung cac field con thieu cho `customers`
|
||||
- cap nhat `Customer.php` chi o phan replace/createUpdate
|
||||
|
||||
## Ket qua doi chieu cac bang duoc yeu cau
|
||||
|
||||
| Bang/nguon | Trong SQL | Trong DB `tgs_portal` | Ket luan |
|
||||
|---|---:|---:|---|
|
||||
| `users` | Co | Khong | Khong can tao, vi duoc map vao `customers` |
|
||||
| `customers` | Khong | Co | La bang dich de nhan schema tu `tbl.users` |
|
||||
| `dns_domain_users` | Khong | Khong | Dump va DB dang dung `dns_domain_user` |
|
||||
| `dns_domain_user` | Co | Co | Da ton tai |
|
||||
| `dns_manager_domain` | Co | Co | Da ton tai |
|
||||
| `dns_manager_ns` | Co | Co | Da ton tai |
|
||||
|
||||
## Lech schema hien tai
|
||||
|
||||
### `tbl.users` trong SQL
|
||||
|
||||
```text
|
||||
id
|
||||
name
|
||||
username
|
||||
email
|
||||
email_verified_at
|
||||
password
|
||||
type
|
||||
status
|
||||
remember_token
|
||||
created_at
|
||||
updated_at
|
||||
```
|
||||
|
||||
### `customers` hien tai
|
||||
|
||||
```text
|
||||
id
|
||||
uuid
|
||||
fullname
|
||||
avatar
|
||||
gender
|
||||
address
|
||||
state
|
||||
city
|
||||
postcode
|
||||
country
|
||||
company
|
||||
phone
|
||||
email
|
||||
email_verify
|
||||
email_hash
|
||||
email_marketing
|
||||
receive_deposit
|
||||
password
|
||||
credit
|
||||
total
|
||||
total_pending
|
||||
identity_number
|
||||
front_identity_number
|
||||
back_identity_number
|
||||
province_code
|
||||
ward_code
|
||||
remember_token
|
||||
status
|
||||
created_at
|
||||
updated_at
|
||||
deleted_at
|
||||
```
|
||||
|
||||
### Field can bo sung vao `customers` tu `tbl.users`
|
||||
|
||||
| Field tu SQL `users` | Cach map vao `customers` | Trang thai |
|
||||
|---|---|---|
|
||||
| `name` | map vao `fullname` | Da co, khong tao moi |
|
||||
| `username` | tao cot `username` | Thieu |
|
||||
| `email` | giu `email` | Da co |
|
||||
| `email_verified_at` | tao cot `email_verified_at` | Thieu |
|
||||
| `password` | giu `password` | Da co |
|
||||
| `type` | tao cot `type` | Thieu |
|
||||
| `status` | giu `status` | Da co |
|
||||
| `remember_token` | giu `remember_token` | Da co |
|
||||
| `created_at` | giu `created_at` | Da co |
|
||||
| `updated_at` | giu `updated_at` | Da co |
|
||||
|
||||
## Lech schema cac bang DNS
|
||||
|
||||
### `dns_manager_domain`
|
||||
|
||||
- SQL dump:
|
||||
- `id`, `domain_name`, `dns_manager_server_id`, `is_created`, `plesk_id`, `created_at`, `updated_at`
|
||||
- DB hien tai:
|
||||
- co them `user_id`
|
||||
- Ket luan:
|
||||
- khong thieu bang
|
||||
- khong nen remove `user_id`
|
||||
|
||||
### `dns_manager_ns`
|
||||
|
||||
- SQL dump:
|
||||
- `id`, `dns_manager_domain_id`, `name`, `type`, `service_name`, `protocol`, `priority`, `weight`, `port`, `value`, `plesk_id`, `ttl`, `created_at`, `updated_at`
|
||||
- DB hien tai:
|
||||
- co them `type_valid`
|
||||
- Ket luan:
|
||||
- khong thieu bang
|
||||
- khong nen remove `type_valid`
|
||||
|
||||
### `dns_domain_user`
|
||||
|
||||
- SQL dump va DB hien tai deu dang dung `dns_domain_user`
|
||||
- Khong co bang `dns_domain_users`
|
||||
- Neu ban muon chuyen qua ten plural, can migration rieng
|
||||
|
||||
## Preview migration cho `customers`
|
||||
|
||||
```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('customers', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('customers', 'username')) {
|
||||
$table->string('username', 50)->nullable()->after('fullname');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('customers', 'email_verified_at')) {
|
||||
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('customers', 'type')) {
|
||||
$table->string('type')->default('guest')->after('password');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('customers', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('customers', 'type')) {
|
||||
$table->dropColumn('type');
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('customers', 'email_verified_at')) {
|
||||
$table->dropColumn('email_verified_at');
|
||||
}
|
||||
|
||||
if (Schema::hasColumn('customers', 'username')) {
|
||||
$table->dropColumn('username');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Preview cap nhat `Customer.php`
|
||||
|
||||
### Replace
|
||||
|
||||
- Dung `fullname` thay cho `users.name`
|
||||
- Khong tao them cot `name`
|
||||
|
||||
### Them vao `$fillable`
|
||||
|
||||
```php
|
||||
'username',
|
||||
'email_verified_at',
|
||||
'type',
|
||||
```
|
||||
|
||||
### Them vao `$updatable`
|
||||
|
||||
```php
|
||||
'username',
|
||||
```
|
||||
|
||||
## Tom tat sau khi duyet preview
|
||||
|
||||
1. Khong tao bang `users`.
|
||||
2. Tao 1 migration add field vao `customers`: `username`, `email_verified_at`, `type`.
|
||||
3. Khong tao moi `dns_domain_user`, `dns_manager_domain`, `dns_manager_ns`.
|
||||
4. Chi cap nhat `Customer.php` o phan fillable/updatable va replace mapping `name` -> `fullname`.
|
||||
@@ -0,0 +1,67 @@
|
||||
<?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
|
||||
{
|
||||
$hasUsername = Schema::hasColumn('customers', 'username');
|
||||
$hasEmailVerifiedAt = Schema::hasColumn('customers', 'email_verified_at');
|
||||
$hasType = Schema::hasColumn('customers', 'type');
|
||||
|
||||
if (! $hasUsername || ! $hasEmailVerifiedAt || ! $hasType) {
|
||||
Schema::table('customers', function (Blueprint $table) use ($hasUsername, $hasEmailVerifiedAt, $hasType) {
|
||||
if (! $hasUsername) {
|
||||
$table->string('username', 50)->nullable()->after('fullname');
|
||||
$table->unique('username');
|
||||
}
|
||||
|
||||
if (! $hasEmailVerifiedAt) {
|
||||
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||
}
|
||||
|
||||
if (! $hasType) {
|
||||
$table->string('type', 255)->default('guest')->after('password');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$hasUsername = Schema::hasColumn('customers', 'username');
|
||||
$hasEmailVerifiedAt = Schema::hasColumn('customers', 'email_verified_at');
|
||||
$hasType = Schema::hasColumn('customers', 'type');
|
||||
|
||||
if (! $hasUsername && ! $hasEmailVerifiedAt && ! $hasType) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('customers', function (Blueprint $table) use ($hasUsername, $hasEmailVerifiedAt, $hasType) {
|
||||
if ($hasUsername) {
|
||||
$table->dropUnique('customers_username_unique');
|
||||
}
|
||||
|
||||
$columns = [];
|
||||
|
||||
if ($hasUsername) {
|
||||
$columns[] = 'username';
|
||||
}
|
||||
|
||||
if ($hasEmailVerifiedAt) {
|
||||
$columns[] = 'email_verified_at';
|
||||
}
|
||||
|
||||
if ($hasType) {
|
||||
$columns[] = 'type';
|
||||
}
|
||||
|
||||
if ($columns !== []) {
|
||||
$table->dropColumn($columns);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$serverId = $this->resolveCanonicalServerId();
|
||||
|
||||
if ($serverId === null) {
|
||||
throw new RuntimeException('Không tìm thấy dns_manager_servers để sync foreign key.');
|
||||
}
|
||||
|
||||
DB::statement("
|
||||
UPDATE dns_manager_domain d
|
||||
LEFT JOIN dns_manager_servers s ON s.id = d.dns_manager_server_id
|
||||
SET d.dns_manager_server_id = {$serverId}
|
||||
WHERE d.dns_manager_server_id = 0 OR s.id IS NULL
|
||||
");
|
||||
|
||||
DB::statement('ALTER TABLE dns_manager_domain MODIFY dns_manager_server_id BIGINT UNSIGNED NOT NULL');
|
||||
DB::statement('ALTER TABLE dns_manager_ns MODIFY dns_manager_domain_id BIGINT UNSIGNED NOT NULL');
|
||||
|
||||
$this->addForeignKeyIfMissing(
|
||||
'dns_manager_domain',
|
||||
'dns_manager_domain_dns_manager_server_id_foreign',
|
||||
'ALTER TABLE dns_manager_domain
|
||||
ADD CONSTRAINT dns_manager_domain_dns_manager_server_id_foreign
|
||||
FOREIGN KEY (dns_manager_server_id) REFERENCES dns_manager_servers(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE RESTRICT'
|
||||
);
|
||||
|
||||
$this->addForeignKeyIfMissing(
|
||||
'dns_domain_user',
|
||||
'dns_domain_user_domain_id_foreign',
|
||||
'ALTER TABLE dns_domain_user
|
||||
ADD CONSTRAINT dns_domain_user_domain_id_foreign
|
||||
FOREIGN KEY (domain_id) REFERENCES dns_manager_domain(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE'
|
||||
);
|
||||
|
||||
$this->addForeignKeyIfMissing(
|
||||
'dns_domain_user',
|
||||
'dns_domain_user_user_id_foreign',
|
||||
'ALTER TABLE dns_domain_user
|
||||
ADD CONSTRAINT dns_domain_user_user_id_foreign
|
||||
FOREIGN KEY (user_id) REFERENCES customers(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE'
|
||||
);
|
||||
|
||||
$this->addForeignKeyIfMissing(
|
||||
'dns_manager_ns',
|
||||
'dns_manager_ns_dns_manager_domain_id_foreign',
|
||||
'ALTER TABLE dns_manager_ns
|
||||
ADD CONSTRAINT dns_manager_ns_dns_manager_domain_id_foreign
|
||||
FOREIGN KEY (dns_manager_domain_id) REFERENCES dns_manager_domain(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE'
|
||||
);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->dropForeignKeyIfExists('dns_manager_ns', 'dns_manager_ns_dns_manager_domain_id_foreign');
|
||||
$this->dropForeignKeyIfExists('dns_domain_user', 'dns_domain_user_user_id_foreign');
|
||||
$this->dropForeignKeyIfExists('dns_domain_user', 'dns_domain_user_domain_id_foreign');
|
||||
$this->dropForeignKeyIfExists('dns_manager_domain', 'dns_manager_domain_dns_manager_server_id_foreign');
|
||||
|
||||
DB::statement('ALTER TABLE dns_manager_ns MODIFY dns_manager_domain_id INT NOT NULL');
|
||||
DB::statement('ALTER TABLE dns_manager_domain MODIFY dns_manager_server_id INT NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
private function resolveCanonicalServerId(): ?int
|
||||
{
|
||||
$server = DB::table('dns_manager_servers')
|
||||
->select('id')
|
||||
->orderByRaw('CASE WHEN id = 1 THEN 0 ELSE 1 END')
|
||||
->orderBy('id')
|
||||
->first();
|
||||
|
||||
return $server ? (int) $server->id : null;
|
||||
}
|
||||
|
||||
private function addForeignKeyIfMissing(string $table, string $constraint, string $statement): void
|
||||
{
|
||||
$exists = DB::table('information_schema.TABLE_CONSTRAINTS')
|
||||
->whereRaw('TABLE_SCHEMA = DATABASE()')
|
||||
->where('TABLE_NAME', $table)
|
||||
->where('CONSTRAINT_NAME', $constraint)
|
||||
->where('CONSTRAINT_TYPE', 'FOREIGN KEY')
|
||||
->exists();
|
||||
|
||||
if (! $exists) {
|
||||
DB::statement($statement);
|
||||
}
|
||||
}
|
||||
|
||||
private function dropForeignKeyIfExists(string $table, string $constraint): void
|
||||
{
|
||||
$exists = DB::table('information_schema.TABLE_CONSTRAINTS')
|
||||
->whereRaw('TABLE_SCHEMA = DATABASE()')
|
||||
->where('TABLE_NAME', $table)
|
||||
->where('CONSTRAINT_NAME', $constraint)
|
||||
->where('CONSTRAINT_TYPE', 'FOREIGN KEY')
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
DB::statement("ALTER TABLE {$table} DROP FOREIGN KEY {$constraint}");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -19,6 +19,7 @@ use App\Http\Controllers\Api\Admin\VPS\VPSController;
|
||||
use App\Http\Controllers\Api\Admin\VCenter\VCenterController;
|
||||
use App\Http\Controllers\Api\Admin\DnsManager\ServerController as DnsServerController;
|
||||
use App\Http\Controllers\Api\Admin\DnsManager\DomainController as DnsDomainController;
|
||||
use App\Http\Controllers\Api\Admin\DnsManager\RecordController as DnsRecordController;
|
||||
use App\Http\Controllers\Api\Admin\OsManager\OsGroupController;
|
||||
use App\Http\Controllers\Api\Admin\OsManager\OsController;
|
||||
use App\Http\Controllers\Api\Admin\IpPool\IpPoolController;
|
||||
@@ -344,10 +345,19 @@ Route::prefix('v1')
|
||||
Route::get('/', 'index')->middleware('perm:domain.manager');
|
||||
Route::post('/', 'store')->middleware('perm:domain.manager');
|
||||
Route::get('{id}', 'show')->middleware('perm:domain.manager');
|
||||
Route::post('{id}/check-connection', 'checkConnection')->middleware('perm:domain.manager');
|
||||
Route::put('{id}', 'update')->middleware('perm:domain.manager');
|
||||
Route::delete('{id}', 'destroy')->middleware('perm:domain.manager');
|
||||
});
|
||||
|
||||
Route::prefix('dns-records')
|
||||
->controller(DnsRecordController::class)
|
||||
->group(function () {
|
||||
Route::get('/', 'index')->middleware('perm:domain.manager');
|
||||
Route::get('{id}', 'show')->middleware('perm:domain.manager');
|
||||
Route::put('{id}', 'update')->middleware('perm:domain.manager');
|
||||
});
|
||||
|
||||
// InfraManagement (jumphost, vcenter config)
|
||||
Route::prefix('infra-management')
|
||||
->controller(InfraManagementController::class)
|
||||
|
||||
+17
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
use App\Http\Controllers\Api\Setting\SettingController;
|
||||
use App\Http\Controllers\Api\Client\ClientController;
|
||||
use App\Http\Controllers\Api\Client\Service\DomainController as ClientDomainController;
|
||||
use App\Http\Controllers\Api\Client\Service\ServiceController as ClientServiceController;
|
||||
use App\Http\Controllers\Api\Service\OrderController;
|
||||
use App\Http\Controllers\Api\Service\ProductCategoryController;
|
||||
@@ -55,12 +56,27 @@ Route::prefix('v1')
|
||||
Route::post('dich-vu/{id}/renew', 'renewService')->name('services.renew');
|
||||
});
|
||||
|
||||
Route::middleware(['api.auth'])
|
||||
->prefix('client-service/domains')
|
||||
->controller(ClientDomainController::class)
|
||||
->as('client-service.domains.')
|
||||
->group(function () {
|
||||
Route::get('/', 'index')->name('list');
|
||||
Route::get('{domainId}', 'show')->name('detail');
|
||||
Route::get('{domainId}/records', 'show')->name('records');
|
||||
Route::post('{domainId}/records', 'storeRecord')->name('records.store');
|
||||
Route::put('{domainId}/records/{recordId}', 'updateRecord')->name('records.update');
|
||||
Route::delete('{domainId}/records/{recordId}', 'destroyRecord')->name('records.destroy');
|
||||
});
|
||||
|
||||
Route::middleware(['api.auth'])
|
||||
->prefix('client-service')
|
||||
->controller(ClientServiceController::class)
|
||||
->as('client-service.')
|
||||
->group(function () {
|
||||
Route::get('/', 'getService')->name('list');
|
||||
Route::get('vps', 'indexVps')->name('vps.list');
|
||||
Route::get('hosting', 'indexHosting')->name('hosting.list');
|
||||
Route::get('web-design', 'indexWebDesign')->name('web-design.list');
|
||||
Route::get('{uuid}', 'getDetail')->name('detail');
|
||||
Route::get('{uuid}/console-ticket', 'getConsoleTicket')->name('console-ticket');
|
||||
Route::get('{uuid}/available-os', 'getAvailableOs')->name('available-os');
|
||||
|
||||
Reference in New Issue
Block a user