option('dry-run'); $refresh = (bool) $this->option('refresh'); $skipImages = (bool) $this->option('skip-images'); $deactivateSource = (bool) $this->option('deactivate-source'); $platform = DB::connection('platform'); $query = $platform->table('qr_codes')->whereIn('type', $this->types); if ($owner = $this->option('user')) { $platformOwner = $platform->table('users') ->where('public_id', $owner) ->orWhere('email', $owner) ->first(); if (! $platformOwner) { $this->error("No platform user matches '{$owner}'."); return self::FAILURE; } $query->where('user_id', $platformOwner->id); } $rows = $query->orderBy('id')->get(); $this->info("Found {$rows->count()} legacy storefront code(s) on the platform."); if ($rows->isEmpty()) { return self::SUCCESS; } $platformUsers = $platform->table('users') ->whereIn('id', $rows->pluck('user_id')->unique()->all()) ->get() ->keyBy('id'); $imported = 0; $skipped = 0; $usersCreated = 0; $imagesCopied = 0; $deactivated = 0; $failed = 0; foreach ($rows as $row) { $platformUser = $platformUsers->get($row->user_id); if (! $platformUser) { $this->warn(" {$row->short_code}: platform owner #{$row->user_id} not found — skipped."); $failed++; continue; } $localUser = User::query() ->when($platformUser->public_id, fn ($q) => $q->where('public_id', $platformUser->public_id)) ->when(! $platformUser->public_id, fn ($q) => $q->where('email', $platformUser->email)) ->first(); if (! $localUser && $platformUser->public_id) { $localUser = User::where('email', $platformUser->email)->first(); } if (! $localUser) { if (! $platformUser->public_id) { $this->warn(" {$row->short_code}: owner has no public_id and no local match — skipped."); $failed++; continue; } if ($dry) { $this->line(" would create local user {$platformUser->email}"); } else { $localUser = User::create([ 'public_id' => $platformUser->public_id, 'name' => $platformUser->name, 'email' => $platformUser->email ?: $platformUser->public_id.'@users.ladill.com', 'avatar_url' => $platformUser->avatar_url ?? null, ]); } $usersCreated++; } $existing = DB::table('qr_codes')->where('short_code', $row->short_code)->first(); if ($existing && ! $refresh) { $skipped++; if ($deactivateSource && ! $dry && $row->is_active) { $platform->table('qr_codes')->where('id', $row->id)->update(['is_active' => 0]); $deactivated++; $this->line(" {$row->short_code}: already exists — skipped (platform copy deactivated)."); } else { $this->line(" {$row->short_code}: already exists — skipped."); } continue; } if ($dry) { $this->line(" would import {$row->short_code} ({$row->type}) for {$platformUser->email}"); $imported++; continue; } $attrs = [ 'user_id' => $localUser->id, 'short_code' => $row->short_code, 'type' => $row->type, 'label' => $row->label, 'destination_url' => $row->destination_url, 'qr_document_id' => null, 'payload' => $row->payload, 'is_active' => $row->is_active, 'png_path' => $row->png_path, 'svg_path' => $row->svg_path, 'scans_total' => $row->scans_total ?? 0, 'unique_scans_total' => $row->unique_scans_total ?? 0, 'last_scanned_at' => $row->last_scanned_at, 'destination_updated_at' => $row->destination_updated_at, 'created_at' => $row->created_at, 'updated_at' => $row->updated_at, ]; if ($existing) { DB::table('qr_codes')->where('id', $existing->id)->update($attrs); } else { DB::table('qr_codes')->insert($attrs); } $imported++; if (! $skipImages) { $imagesCopied += $this->copyImages($row->png_path, $row->svg_path, $row->payload); } if ($deactivateSource && $row->is_active) { $platform->table('qr_codes')->where('id', $row->id)->update(['is_active' => 0]); $deactivated++; } } $this->newLine(); $this->info("Imported: {$imported} · Skipped: {$skipped} · Local users created: {$usersCreated} · Images copied: {$imagesCopied} · Source deactivated: {$deactivated} · Failed: {$failed}"); if ($dry) { $this->comment('Dry run — no changes were written.'); } return self::SUCCESS; } /** * Copy a code's image files (QR PNG/SVG + every *_path / image_path in the * payload, e.g. logo_path, cover_path, sections[][items][].image_path) from * the platform QR storage to the local QR disk. Best-effort. */ private function copyImages(?string $png, ?string $svg, ?string $payloadJson): int { $paths = [$png, $svg]; $this->collectPaths(json_decode((string) $payloadJson, true) ?: [], $paths); $paths = array_values(array_unique(array_filter($paths, fn ($p) => is_string($p) && $p !== ''))); $src = Storage::disk('platform_qr'); $dst = Storage::disk('qr'); $copied = 0; foreach ($paths as $path) { if ($dst->exists($path)) { continue; } if (! $src->exists($path)) { $this->warn(" image missing on source: {$path}"); continue; } $dst->put($path, $src->get($path)); $copied++; } return $copied; } /** * @param array $data * @param array $paths */ private function collectPaths(array $data, array &$paths): void { foreach ($data as $key => $value) { if (is_array($value)) { $this->collectPaths($value, $paths); continue; } if (is_string($value) && $value !== '' && is_string($key) && (str_ends_with($key, '_path') || $key === 'path')) { $paths[] = $value; } } } }