diff --git a/app/Console/Commands/ImportLegacyStorefronts.php b/app/Console/Commands/ImportLegacyStorefronts.php new file mode 100644 index 0000000..0380f19 --- /dev/null +++ b/app/Console/Commands/ImportLegacyStorefronts.php @@ -0,0 +1,232 @@ +option('dry-run'); + $refresh = (bool) $this->option('refresh'); + $skipImages = (bool) $this->option('skip-images'); + + $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; + $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++; + $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); + } + } + + $this->newLine(); + $this->info("Imported: {$imported} · Skipped: {$skipped} · Local users created: {$usersCreated} · Images copied: {$imagesCopied} · 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; + } + } + } +} diff --git a/config/filesystems.php b/config/filesystems.php index 4ebaa0b..adeb117 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -67,6 +67,16 @@ return [ 'report' => false, ], + // Read-only view of the monolith's QR storage, used by the + // storefronts:import-legacy command to copy legacy product images. + // Set PLATFORM_QR_ROOT to the monolith's storage/app/private/qr path. + 'platform_qr' => [ + 'driver' => 'local', + 'root' => env('PLATFORM_QR_ROOT', storage_path('app/private/qr')), + 'throw' => false, + 'report' => false, + ], + ], /*