platform qr_code id → local id */ private array $codeMap = []; 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['qr_wallets'] ?? [] as $row) { $this->importWallet($row, $commit); } foreach ($payload['qr_codes'] ?? [] as $row) { $this->importCode($row, $commit); } if ($commit) { $this->remapAllProgrammeLinks(); } foreach ($payload['qr_event_registrations'] ?? [] as $row) { $this->importRegistration($row, $commit); } foreach ($payload['qr_scan_events'] ?? [] as $row) { $this->importScanEvent($row, $commit); } foreach ($payload['qr_transactions'] ?? [] as $row) { $this->importTransaction($row, $commit); } }); if (! $this->option('storage-source')) { $this->warn('Pass --storage-source=/path/to/platform/storage/app/private/qr to copy event cover/logo files.'); } $this->info(($commit ? 'Imported' : 'Would import').' '.count($this->codeMap).' event code(s).'); return self::SUCCESS; } private function resolveUser(array $row): ?User { $publicId = $row['owner_public_id'] ?? null; if (! $publicId) { return null; } return User::firstOrCreate( ['public_id' => $publicId], ['email' => $row['owner_email'] ?? $publicId.'@users.ladill.com', 'name' => 'Imported user'], ); } /** @param array $row */ private function importWallet(array $row, bool $commit): void { $user = $this->resolveUser($row); if (! $user) { return; } $attrs = collect($row)->except(['platform_id', 'owner_public_id', 'owner_email', 'id', 'user_id'])->all(); if ($commit) { QrWallet::updateOrCreate(['user_id' => $user->id], $attrs); } } /** @param array $row */ private function importCode(array $row, bool $commit): void { $user = $this->resolveUser($row); if (! $user) { return; } $platformId = (int) ($row['platform_id'] ?? 0); $shortCode = (string) ($row['short_code'] ?? ''); $attrs = collect($row)->except([ 'platform_id', 'owner_public_id', 'owner_email', 'id', 'user_id', ])->all(); $attrs['user_id'] = $user->id; $attrs = $this->normalizeJsonAttributes($attrs, ['payload']); if ($commit) { $code = QrCode::updateOrCreate(['short_code' => $shortCode], $attrs); $this->codeMap[$platformId] = $code->id; $this->copyCodeAssets($code); } else { $this->codeMap[$platformId] = $platformId; } } private function remapAllProgrammeLinks(): void { QrCode::query() ->where('type', QrCode::TYPE_EVENT) ->each(fn (QrCode $code) => $this->remapProgrammeLinks($code)); } private function remapProgrammeLinks(QrCode $code): void { if ($code->type !== QrCode::TYPE_EVENT) { return; } $content = $code->content(); $programmeId = (int) ($content['programme_qr_id'] ?? 0); if ($programmeId <= 0 || ! isset($this->codeMap[$programmeId])) { return; } $content['programme_qr_id'] = $this->codeMap[$programmeId]; $payload = $code->payload ?? []; $payload['content'] = $content; $code->update(['payload' => $payload]); } /** * @param array $attrs * @param list $keys * @return array */ private function normalizeJsonAttributes(array $attrs, array $keys): array { foreach ($keys as $key) { if (! isset($attrs[$key]) || ! is_string($attrs[$key])) { continue; } $decoded = json_decode($attrs[$key], true); if (is_array($decoded)) { $attrs[$key] = $decoded; } } return $attrs; } /** @param array $row */ private function importRegistration(array $row, bool $commit): void { $platformCodeId = (int) ($row['platform_qr_code_id'] ?? 0); $localCodeId = $this->codeMap[$platformCodeId] ?? null; if (! $localCodeId) { return; } $reference = (string) ($row['reference'] ?? ''); $attrs = collect($row)->except(['platform_id', 'platform_qr_code_id', 'id', 'qr_code_id', 'user_id'])->all(); $attrs['qr_code_id'] = $localCodeId; $attrs['user_id'] = QrCode::find($localCodeId)?->user_id; if ($commit && $reference !== '') { QrEventRegistration::updateOrCreate(['reference' => $reference], $attrs); } } /** @param array $row */ private function importScanEvent(array $row, bool $commit): void { $platformCodeId = (int) ($row['platform_qr_code_id'] ?? 0); $localCodeId = $this->codeMap[$platformCodeId] ?? null; if (! $localCodeId) { return; } $attrs = collect($row)->except(['platform_id', 'platform_qr_code_id', 'id', 'qr_code_id'])->all(); $attrs['qr_code_id'] = $localCodeId; if ($commit) { QrScanEvent::updateOrCreate( ['qr_code_id' => $localCodeId, 'created_at' => $attrs['created_at'] ?? now()], $attrs, ); } } /** @param array $row */ private function importTransaction(array $row, bool $commit): void { $user = $this->resolveUser($row); if (! $user) { return; } $wallet = QrWallet::firstOrCreate(['user_id' => $user->id]); $reference = (string) ($row['reference'] ?? ''); $attrs = collect($row)->except(['platform_id', 'owner_public_id', 'platform_qr_code_id', 'id', 'user_id', 'qr_wallet_id', 'qr_code_id'])->all(); $attrs['user_id'] = $user->id; $attrs['qr_wallet_id'] = $wallet->id; $platformCodeId = (int) ($row['platform_qr_code_id'] ?? 0); if ($platformCodeId && isset($this->codeMap[$platformCodeId])) { $attrs['qr_code_id'] = $this->codeMap[$platformCodeId]; } if ($commit && $reference !== '') { QrTransaction::updateOrCreate(['reference' => $reference], $attrs); } } private function copyCodeAssets(QrCode $code): void { $sourceRoot = $this->option('storage-source'); if (! is_string($sourceRoot) || $sourceRoot === '' || ! is_dir($sourceRoot)) { return; } $paths = array_filter([ $code->png_path, $code->svg_path, ]); $content = $code->content(); foreach (['logo_path', 'cover_path'] as $key) { if (! empty($content[$key])) { $paths[] = $content[$key]; } } foreach (array_unique($paths) as $relativePath) { $this->copyStorageFile($sourceRoot, (string) $relativePath); } } private function copyStorageFile(string $sourceRoot, string $relativePath): void { if ($relativePath === '') { return; } $sourceFile = rtrim($sourceRoot, '/').'/'.$relativePath; if (! is_file($sourceFile)) { $this->warn("Missing file on platform storage: {$sourceFile}"); return; } if (Storage::disk('qr')->exists($relativePath)) { return; } $directory = dirname($relativePath); if ($directory !== '.' && $directory !== '') { Storage::disk('qr')->makeDirectory($directory); } Storage::disk('qr')->put($relativePath, (string) file_get_contents($sourceFile)); } }