Files
ladill-hosting/app/Console/Commands/ImportHostingCommand.php
T
isaaccladandCursor d532f5b206
Deploy Ladill Hosting / deploy (push) Successful in 23s
Fix legacy RC hosting orders not appearing after app extraction.
Import RC service orders in hosting:import, map platform_id on legacy models, and wire legacy order manage links to hosting.legacy.show.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 20:31:29 +00:00

354 lines
12 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\CustomerHostingOrder;
use App\Models\HostedSite;
use App\Models\HostingAccount;
use App\Models\HostingAccountMember;
use App\Models\HostingBillingInvoice;
use App\Models\HostingNode;
use App\Models\HostingOrder;
use App\Models\HostingPlanChange;
use App\Models\HostingProduct;
use App\Models\RcServiceOrder;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Zero-loss import of hosting data exported from the platform (`hosting:export`).
* Dry-run by default; pass --commit to write.
*/
class ImportHostingCommand extends Command
{
protected $signature = 'hosting:import
{file : Path to hosting-export.json from the platform}
{--commit : Actually write changes}';
protected $description = 'Import hosting accounts and orders from the platform export';
/** @var array<int,int> platform user_id → local user id */
private array $userMap = [];
/** @var array<int,int> platform account id → local account id */
private array $accountMap = [];
/** @var array<int,int> platform node id → local node id */
private array $nodeMap = [];
public function handle(): int
{
$commit = (bool) $this->option('commit');
if (! $commit) {
$this->warn('DRY RUN — no changes will be written. Re-run with --commit to apply.');
}
$path = $this->argument('file');
if (! is_file($path)) {
$this->error("File not found: {$path}");
return self::FAILURE;
}
$payload = json_decode((string) file_get_contents($path), true);
if (! is_array($payload)) {
$this->error('Invalid JSON export file.');
return self::FAILURE;
}
$apply = function (callable $callback) use ($commit): void {
if ($commit) {
DB::transaction($callback);
} else {
$callback();
}
};
$apply(function () use ($payload, $commit): void {
foreach ($payload['hosting_nodes'] ?? [] as $row) {
$this->importNode($row, $commit);
}
foreach ($payload['hosting_accounts'] ?? [] as $row) {
$this->importAccount($row, $commit);
}
foreach ($payload['hosted_sites'] ?? [] as $row) {
$this->importSite($row, $commit);
}
foreach ($payload['customer_hosting_orders'] ?? [] as $row) {
$this->importCustomerOrder($row, $commit);
}
foreach ($payload['hosting_orders'] ?? [] as $row) {
$this->importLegacyOrder($row, $commit);
}
foreach ($payload['rc_service_orders'] ?? [] as $row) {
$this->importRcServiceOrder($row, $commit);
}
foreach ($payload['hosting_account_members'] ?? [] as $row) {
$this->importMember($row, $commit);
}
foreach ($payload['hosting_billing_invoices'] ?? [] as $row) {
$this->importInvoice($row, $commit);
}
foreach ($payload['hosting_plan_changes'] ?? [] as $row) {
$this->importPlanChange($row, $commit);
}
});
$this->info(($commit ? 'Imported' : 'Would import').' '.count($this->accountMap).' hosting account(s).');
$siteCount = count($payload['hosted_sites'] ?? []);
if ($siteCount > 0) {
$this->info(($commit ? 'Imported' : 'Would import')." {$siteCount} hosted site(s).");
}
return self::SUCCESS;
}
/** @param array<string, mixed> $row */
private function importNode(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
unset($row['id'], $row['primary_hosting_node_id']);
if ($commit) {
$node = HostingNode::updateOrCreate(['hostname' => $row['hostname']], $row);
$this->nodeMap[$platformId] = $node->id;
} else {
$this->nodeMap[$platformId] = $platformId;
$this->line(" node {$platformId}{$row['hostname']}");
}
}
/** @param array<string, mixed> $row */
private function importAccount(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser(
(string) ($row['owner_public_id'] ?? ''),
(string) ($row['owner_email'] ?? ''),
);
if (! $owner) {
$this->warn("Skipping account #{$platformId}: no owner public_id");
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['platform_id'] = $platformId;
$this->mapProductId($row);
$platformNodeId = (int) ($row['hosting_node_id'] ?? 0);
if ($platformNodeId && isset($this->nodeMap[$platformNodeId])) {
$row['hosting_node_id'] = $this->nodeMap[$platformNodeId];
}
if ($commit) {
$account = HostingAccount::query()->where('platform_id', $platformId)->first();
if (! $account && ($row['username'] ?? '') !== '') {
$account = HostingAccount::query()->where('username', $row['username'])->first();
}
if ($account) {
$account->update($row);
} else {
$account = HostingAccount::create($row);
}
$this->accountMap[$platformId] = $account->id;
} else {
$this->accountMap[$platformId] = $platformId;
$this->line(" account {$platformId} → user {$owner->email}");
}
}
/** @param array<string, mixed> $row */
private function importSite(array $row, bool $commit): void
{
$platformAccountId = (int) ($row['platform_hosting_account_id'] ?? 0);
$localAccountId = $this->accountMap[$platformAccountId] ?? null;
if (! $localAccountId) {
$this->warn("Skipping site {$row['domain']}: unknown hosting account #{$platformAccountId}");
return;
}
unset($row['platform_hosting_account_id'], $row['platform_id']);
$row['hosting_account_id'] = $localAccountId;
$row['domain_id'] = null;
if ($commit) {
HostedSite::withTrashed()->updateOrCreate(
['hosting_account_id' => $localAccountId, 'domain' => $row['domain']],
$row
);
} else {
$this->line(" site {$row['domain']} → account {$localAccountId}");
}
}
/** @param array<string, mixed> $row */
private function importCustomerOrder(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser((string) ($row['owner_public_id'] ?? ''));
if (! $owner) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['platform_id'] = $platformId;
$this->mapProductId($row);
if ($commit) {
CustomerHostingOrder::updateOrCreate(['platform_id' => $platformId], $row);
}
}
/** @param array<string, mixed> $row */
private function importLegacyOrder(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser((string) ($row['owner_public_id'] ?? ''));
if (! $owner) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['platform_id'] = $platformId;
if ($commit) {
HostingOrder::updateOrCreate(['platform_id' => $platformId], $row);
}
}
/** @param array<string, mixed> $row */
private function importRcServiceOrder(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser((string) ($row['owner_public_id'] ?? ''));
if (! $owner) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['platform_id'] = $platformId;
if ($commit) {
RcServiceOrder::updateOrCreate(['platform_id' => $platformId], $row);
}
}
/** @param array<string, mixed> $row */
private function importMember(array $row, bool $commit): void
{
$member = $this->resolveUser((string) ($row['member_public_id'] ?? ''));
$platformAccountId = (int) ($row['hosting_account_id'] ?? 0);
$localAccountId = $this->accountMap[$platformAccountId] ?? null;
if (! $member || ! $localAccountId) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['member_public_id']);
$row['user_id'] = $member->id;
$row['hosting_account_id'] = $localAccountId;
if (! empty($row['invited_by_user_id']) && ! User::query()->whereKey($row['invited_by_user_id'])->exists()) {
$row['invited_by_user_id'] = null;
}
if ($commit) {
HostingAccountMember::updateOrCreate(
['hosting_account_id' => $localAccountId, 'user_id' => $member->id],
$row
);
}
}
/** @param array<string, mixed> $row */
private function importInvoice(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser((string) ($row['owner_public_id'] ?? ''));
$platformAccountId = (int) ($row['hosting_account_id'] ?? 0);
$localAccountId = $this->accountMap[$platformAccountId] ?? null;
if (! $owner || ! $localAccountId) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['hosting_account_id'] = $localAccountId;
$row['platform_id'] = $platformId;
if ($commit) {
HostingBillingInvoice::updateOrCreate(['platform_id' => $platformId], $row);
}
}
/** @param array<string, mixed> $row */
private function importPlanChange(array $row, bool $commit): void
{
$platformId = (int) ($row['id'] ?? 0);
$owner = $this->resolveUser((string) ($row['owner_public_id'] ?? ''));
$platformAccountId = (int) ($row['hosting_account_id'] ?? 0);
$localAccountId = $this->accountMap[$platformAccountId] ?? null;
if (! $owner || ! $localAccountId) {
return;
}
unset($row['id'], $row['owner_public_id'], $row['owner_email']);
$row['user_id'] = $owner->id;
$row['hosting_account_id'] = $localAccountId;
$row['platform_id'] = $platformId;
if ($commit) {
HostingPlanChange::updateOrCreate(['platform_id' => $platformId], $row);
}
}
private function resolveUser(string $publicId, string $email = '', string $name = ''): ?User
{
if ($publicId === '') {
return null;
}
$user = User::query()->where('public_id', $publicId)->first();
if ($user || $email === '') {
return $user;
}
return User::query()->create([
'public_id' => $publicId,
'email' => $email,
'name' => $name !== '' ? $name : $email,
]);
}
/** @param array<string, mixed> $row */
private function mapProductId(array &$row): void
{
$slug = (string) ($row['hosting_product_slug'] ?? '');
unset($row['hosting_product_slug']);
if ($slug === '') {
return;
}
$product = HostingProduct::query()->where('slug', $slug)->first();
if ($product) {
$row['hosting_product_id'] = $product->id;
}
}
}