Current state
This commit is contained in:
291
app/Console/Commands/ImportLegacyArtworks.php
Normal file
291
app/Console/Commands/ImportLegacyArtworks.php
Normal file
@@ -0,0 +1,291 @@
|
||||
<?php
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Import artworks from legacy `wallz` table and attach categories via `connected` table.
|
||||
*
|
||||
* Usage:
|
||||
* php artisan skinbase:import-legacy-artworks --chunk=500 --dry-run
|
||||
*/
|
||||
class ImportLegacyArtworks extends Command
|
||||
{
|
||||
protected $signature = 'skinbase:import-legacy-artworks
|
||||
{--chunk=500 : chunk size for processing}
|
||||
{--limit= : maximum number of legacy rows to import}
|
||||
{--dry-run : do not persist any changes}
|
||||
{--legacy-connection=legacy : name of legacy DB connection}
|
||||
{--legacy-table=wallz : legacy artworks table name}
|
||||
{--connected-table=connected : legacy artwork->category table}
|
||||
';
|
||||
|
||||
protected $description = 'Import artworks from legacy DB (wallz) into new artworks table';
|
||||
|
||||
private function coerceUnsignedInt(mixed $value, int $default = 0): int
|
||||
{
|
||||
if ($value === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value ? 1 : 0;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return max(0, $value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return max(0, (int) $value);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$trimmed = trim($value);
|
||||
if ($trimmed === '') {
|
||||
return $default;
|
||||
}
|
||||
if (is_numeric($trimmed)) {
|
||||
return max(0, (int) $trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
private function coerceString(mixed $value, string $default = ''): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$stringValue = trim((string) $value);
|
||||
|
||||
return $stringValue !== '' ? $stringValue : $default;
|
||||
}
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$chunk = (int) $this->option('chunk');
|
||||
$limit = $this->option('limit') ? (int) $this->option('limit') : null;
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$legacyConn = $this->option('legacy-connection');
|
||||
$legacyTable = $this->option('legacy-table');
|
||||
$connectedTable = $this->option('connected-table');
|
||||
|
||||
$this->info("Starting import from {$legacyConn}.{$legacyTable} (chunk={$chunk})");
|
||||
|
||||
$query = DB::connection($legacyConn)->table($legacyTable)->orderBy('id');
|
||||
|
||||
$processed = 0;
|
||||
|
||||
$query->chunkById($chunk, function ($rows) use (&$processed, $limit, $dryRun, $legacyConn, $connectedTable) {
|
||||
foreach ($rows as $row) {
|
||||
if ($limit !== null && $processed >= $limit) {
|
||||
return false; // stop chunking
|
||||
}
|
||||
|
||||
$legacyId = $row->id ?? null;
|
||||
|
||||
$title = $row->name ?? $row->title ?? ($row->headline ?? ('legacy-' . ($legacyId ?? Str::random(6))));
|
||||
$description = $row->description ?? $row->desc ?? null;
|
||||
|
||||
$slugBase = Str::slug(substr((string) $title, 0, 120));
|
||||
// Use cleaned title slug directly. If no title, fallback to artwork-<id|random>.
|
||||
$slug = $slugBase ? $slugBase : 'artwork-' . ($legacyId ?? Str::random(8));
|
||||
|
||||
$publishedAt = null;
|
||||
if (! empty($row->datum)) {
|
||||
$publishedAt = date('Y-m-d H:i:s', strtotime($row->datum));
|
||||
} elseif (! empty($row->created_at)) {
|
||||
$publishedAt = $row->created_at;
|
||||
}
|
||||
|
||||
// File mapping — try common legacy fields. Normalize and ensure file_path is not null.
|
||||
$rawFileName = $row->pic ?? $row->picture ?? $row->file ?? $row->fname ?? null;
|
||||
$fileName = null;
|
||||
$filePath = '';
|
||||
if (! empty($rawFileName) && trim((string) $rawFileName) !== '') {
|
||||
$fileName = trim((string) $rawFileName);
|
||||
// store legacy path under legacy/ folder, but do not move files here — admin can handle file migration
|
||||
$filePath = 'legacy/uploads/' . ltrim($fileName, '/');
|
||||
}
|
||||
|
||||
// derive mime type if missing (use extension mapping), fallback to application/octet-stream
|
||||
$mime = $row->mimetype ?? $row->mime ?? null;
|
||||
if (empty($mime) && $fileName) {
|
||||
$ext = strtolower(pathinfo((string) $fileName, PATHINFO_EXTENSION));
|
||||
$map = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'bmp' => 'image/bmp',
|
||||
'webp' => 'image/webp',
|
||||
'svg' => 'image/svg+xml',
|
||||
'ico' => 'image/x-icon',
|
||||
'zip' => 'application/zip',
|
||||
'pdf' => 'application/pdf',
|
||||
];
|
||||
|
||||
$mime = $map[$ext] ?? null;
|
||||
}
|
||||
if (empty($mime)) {
|
||||
$mime = 'application/octet-stream';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'id' => $row->id ?? null,
|
||||
// NOTE: artworks.user_id is NOT NULL (no FK constraint, but column cannot be null)
|
||||
'user_id' => $row->user_id ?? 1,
|
||||
'title' => (string) $title,
|
||||
'slug' => (string) $slug,
|
||||
'description' => $description,
|
||||
'file_name' => $fileName,
|
||||
// ensure non-null file_path to satisfy NOT NULL DB constraints
|
||||
'file_path' => $filePath ?? '',
|
||||
// legacy DB sometimes has no filesize; default to 0 to satisfy NOT NULL
|
||||
'file_size' => isset($row->filesize) && $row->filesize !== null ? (int) $row->filesize : (isset($row->size) && $row->size !== null ? (int) $row->size : 0),
|
||||
'mime_type' => $mime,
|
||||
'width' => $row->width ?? null,
|
||||
'height' => $row->height ?? null,
|
||||
'is_public' => isset($row->visible) ? (bool) $row->visible : true,
|
||||
'is_approved' => isset($row->approved) ? (bool) $row->approved : true,
|
||||
'published_at' => $publishedAt,
|
||||
];
|
||||
|
||||
// Coerce required NOT NULL columns to safe defaults (legacy data can be messy)
|
||||
$data['user_id'] = $this->coerceUnsignedInt($data['user_id'], 1);
|
||||
$data['file_name'] = $this->coerceString($data['file_name'], 'legacy-' . ($legacyId ?? Str::random(8)));
|
||||
$data['file_path'] = $this->coerceString($data['file_path'], 'legacy/uploads/' . $data['file_name']);
|
||||
$data['mime_type'] = $this->coerceString($data['mime_type'], 'application/octet-stream');
|
||||
$data['file_size'] = $this->coerceUnsignedInt($data['file_size'], 0);
|
||||
$data['width'] = $this->coerceUnsignedInt($data['width'], 0);
|
||||
$data['height'] = $this->coerceUnsignedInt($data['height'], 0);
|
||||
|
||||
$this->line('Importing legacy id=' . ($legacyId ?? 'unknown') . ' title=' . $data['title']);
|
||||
|
||||
if ($dryRun) {
|
||||
$processed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$art = null;
|
||||
|
||||
DB::connection()->transaction(function () use (&$art, $data, $legacyId, $legacyConn, $connectedTable) {
|
||||
// create artwork (guard against unique slug collisions)
|
||||
$baseSlug = $data['slug'];
|
||||
$attempt = 0;
|
||||
$slug = $baseSlug;
|
||||
while (Artwork::where('slug', $slug)->exists()) {
|
||||
$attempt++;
|
||||
$slug = $baseSlug . '-' . $attempt;
|
||||
}
|
||||
$data['slug'] = $slug;
|
||||
|
||||
// Preserve legacy primary ID if available and safe to do so.
|
||||
if (! empty($legacyId) && is_numeric($legacyId) && (int) $legacyId > 0) {
|
||||
$preserveId = (int) $legacyId;
|
||||
if (Artwork::where('id', $preserveId)->exists()) {
|
||||
// Avoid overwriting an existing artwork with the same id.
|
||||
throw new \RuntimeException("Artwork with id {$preserveId} already exists; skipping import for this legacy id.");
|
||||
}
|
||||
$data['id'] = $preserveId;
|
||||
}
|
||||
|
||||
// If we need to preserve the legacy primary id, perform a raw insert
|
||||
// so auto-increment doesn't assign a different id. Otherwise use Eloquent.
|
||||
if (! empty($data['id'])) {
|
||||
$insert = $data;
|
||||
$ts = date('Y-m-d H:i:s');
|
||||
if (! array_key_exists('created_at', $insert)) {
|
||||
$insert['created_at'] = $ts;
|
||||
}
|
||||
if (! array_key_exists('updated_at', $insert)) {
|
||||
$insert['updated_at'] = $ts;
|
||||
}
|
||||
|
||||
DB::table('artworks')->insert($insert);
|
||||
$art = Artwork::find($insert['id']);
|
||||
} else {
|
||||
$art = Artwork::create($data);
|
||||
}
|
||||
|
||||
// attach categories if connected table exists
|
||||
if (DB::connection($legacyConn)->getSchemaBuilder()->hasTable($connectedTable)) {
|
||||
// attempt to find category ids from connected table; common column names: wallz_id, art_id, connected_id
|
||||
$rows = DB::connection($legacyConn)->table($connectedTable)
|
||||
->where(function ($q) use ($legacyId) {
|
||||
$q->where('wallz_id', $legacyId)
|
||||
->orWhere('art_id', $legacyId)
|
||||
->orWhere('item_id', $legacyId);
|
||||
})->get();
|
||||
|
||||
$categoryIds = [];
|
||||
foreach ($rows as $r) {
|
||||
$cid = $r->category_id ?? $r->cat_id ?? $r->category ?? null;
|
||||
if ($cid) {
|
||||
// try to find matching Category in new DB by id or slug
|
||||
if (is_numeric($cid) && \App\Models\Category::where('id', $cid)->exists()) {
|
||||
$categoryIds[] = (int) $cid;
|
||||
} else {
|
||||
// maybe legacy stores slug
|
||||
$cat = \App\Models\Category::where('slug', $cid)->first();
|
||||
if ($cat) {
|
||||
$categoryIds[] = $cat->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($categoryIds)) {
|
||||
$art->categories()->syncWithoutDetaching(array_values(array_unique($categoryIds)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Post-insert verification: if we attempted to preserve the legacy id,
|
||||
// confirm the row exists with that id. Log mapping if preservation failed.
|
||||
if (! empty($legacyId) && is_numeric($legacyId) && (int) $legacyId > 0) {
|
||||
$preserveId = (int) $legacyId;
|
||||
$exists = Artwork::where('id', $preserveId)->exists();
|
||||
if (! $exists) {
|
||||
// If $art was created but with a different id, log mapping for manual reconciliation
|
||||
if ($art instanceof Artwork) {
|
||||
Log::warning('Imported legacy artwork but failed to preserve id', [
|
||||
'legacy_id' => $preserveId,
|
||||
'created_id' => $art->id,
|
||||
'slug' => $art->slug ?? null,
|
||||
]);
|
||||
} else {
|
||||
Log::warning('Legacy artwork not found after import', ['legacy_id' => $preserveId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$processed++;
|
||||
} catch (Throwable $e) {
|
||||
$this->error('Failed to import legacy id=' . ($legacyId ?? 'unknown') . ': ' . $e->getMessage());
|
||||
Log::error('ImportLegacyArtworks error', [
|
||||
'legacy_id' => $legacyId,
|
||||
'error' => $e->getMessage(),
|
||||
'data' => $data ?? null,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, 'id');
|
||||
|
||||
$this->info('Import complete. Processed: ' . $processed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user