feat: increase gallery grid from 4 to 5 columns per row on desktopfeat: increase gallery grid from 4 to 5 columns per row on desktop
This commit is contained in:
266
app/Console/Commands/ImportLegacyComments.php
Normal file
266
app/Console/Commands/ImportLegacyComments.php
Normal file
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Migrates legacy `artworks_comments` (projekti_old_skinbase) into `artwork_comments`.
|
||||
*
|
||||
* Column mapping:
|
||||
* legacy.comment_id → artwork_comments.legacy_id (idempotency key)
|
||||
* legacy.artwork_id → artwork_comments.artwork_id
|
||||
* legacy.user_id → artwork_comments.user_id
|
||||
* legacy.description → artwork_comments.content
|
||||
* legacy.date + .time → artwork_comments.created_at / updated_at
|
||||
*
|
||||
* Ignored legacy columns: owner, author (username strings), owner_user_id
|
||||
*
|
||||
* Usage:
|
||||
* php artisan comments:import-legacy
|
||||
* php artisan comments:import-legacy --dry-run
|
||||
* php artisan comments:import-legacy --chunk=1000
|
||||
* php artisan comments:import-legacy --allow-guest-user=0 (import rows where user_id maps to 0 / not found, assigning a fallback user_id)
|
||||
*/
|
||||
class ImportLegacyComments extends Command
|
||||
{
|
||||
protected $signature = 'comments:import-legacy
|
||||
{--dry-run : Preview only — no writes to DB}
|
||||
{--chunk=500 : Rows to process per batch}
|
||||
{--skip-empty : Skip comments with empty/whitespace-only content}';
|
||||
|
||||
protected $description = 'Import legacy artworks_comments into artwork_comments';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$chunk = max(1, (int) $this->option('chunk'));
|
||||
$skipEmpty = (bool) $this->option('skip-empty');
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('[DRY-RUN] No data will be written.');
|
||||
}
|
||||
|
||||
// Verify legacy connection
|
||||
try {
|
||||
DB::connection('legacy')->getPdo();
|
||||
} catch (\Throwable $e) {
|
||||
$this->error('Cannot connect to legacy database: ' . $e->getMessage());
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! DB::connection('legacy')->getSchemaBuilder()->hasTable('artworks_comments')) {
|
||||
$this->error('Legacy table `artworks_comments` not found.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! DB::getSchemaBuilder()->hasColumn('artwork_comments', 'legacy_id')) {
|
||||
$this->error('Column `legacy_id` missing from `artwork_comments`. Run: php artisan migrate');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
// Pre-load valid artwork IDs and user IDs from new DB for O(1) lookup
|
||||
$this->info('Loading new-DB artwork and user ID sets…');
|
||||
|
||||
$validArtworkIds = DB::table('artworks')
|
||||
->whereNull('deleted_at')
|
||||
->pluck('id')
|
||||
->flip()
|
||||
->all();
|
||||
|
||||
$validUserIds = DB::table('users')
|
||||
->whereNull('deleted_at')
|
||||
->pluck('id')
|
||||
->flip()
|
||||
->all();
|
||||
|
||||
$this->info(sprintf(
|
||||
'Found %d artworks and %d users in new DB.',
|
||||
count($validArtworkIds),
|
||||
count($validUserIds)
|
||||
));
|
||||
|
||||
// Already-imported legacy IDs (to resume safely)
|
||||
$this->info('Loading already-imported legacy_ids…');
|
||||
$alreadyImported = DB::table('artwork_comments')
|
||||
->whereNotNull('legacy_id')
|
||||
->pluck('legacy_id')
|
||||
->flip()
|
||||
->all();
|
||||
|
||||
$this->info(sprintf('%d comments already imported (will be skipped).', count($alreadyImported)));
|
||||
|
||||
$total = DB::connection('legacy')->table('artworks_comments')->count();
|
||||
$this->info("Legacy rows to process: {$total}");
|
||||
|
||||
if ($total === 0) {
|
||||
$this->warn('No legacy rows found. Nothing to do.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$stats = [
|
||||
'imported' => 0,
|
||||
'skipped_duplicate' => 0,
|
||||
'skipped_artwork' => 0,
|
||||
'skipped_user' => 0,
|
||||
'skipped_empty' => 0,
|
||||
'errors' => 0,
|
||||
];
|
||||
|
||||
$bar = $this->output->createProgressBar($total);
|
||||
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% | imported: %imported% | skipped: %skipped%');
|
||||
$bar->setMessage('0', 'imported');
|
||||
$bar->setMessage('0', 'skipped');
|
||||
$bar->start();
|
||||
|
||||
DB::connection('legacy')
|
||||
->table('artworks_comments')
|
||||
->orderBy('comment_id')
|
||||
->chunk($chunk, function ($rows) use (
|
||||
&$stats,
|
||||
&$alreadyImported,
|
||||
$validArtworkIds,
|
||||
$validUserIds,
|
||||
$dryRun,
|
||||
$skipEmpty,
|
||||
$bar
|
||||
) {
|
||||
$inserts = [];
|
||||
$now = now();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$legacyId = (int) $row->comment_id;
|
||||
$artworkId = (int) $row->artwork_id;
|
||||
$userId = (int) $row->user_id;
|
||||
$content = trim((string) ($row->description ?? ''));
|
||||
|
||||
// --- Already imported ---
|
||||
if (isset($alreadyImported[$legacyId])) {
|
||||
$stats['skipped_duplicate']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Content ---
|
||||
if ($skipEmpty && $content === '') {
|
||||
$stats['skipped_empty']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace empty content with a placeholder so NOT NULL is satisfied
|
||||
if ($content === '') {
|
||||
$content = '[no content]';
|
||||
}
|
||||
|
||||
// --- Artwork must exist ---
|
||||
if (! isset($validArtworkIds[$artworkId])) {
|
||||
$stats['skipped_artwork']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- User must exist ---
|
||||
if (! isset($validUserIds[$userId])) {
|
||||
$stats['skipped_user']++;
|
||||
$bar->advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Build timestamp from separate date + time columns ---
|
||||
$createdAt = $this->buildTimestamp($row->date, $row->time, $now);
|
||||
|
||||
if (! $dryRun) {
|
||||
$inserts[] = [
|
||||
'legacy_id' => $legacyId,
|
||||
'artwork_id' => $artworkId,
|
||||
'user_id' => $userId,
|
||||
'content' => $content,
|
||||
'is_approved' => 1,
|
||||
'created_at' => $createdAt,
|
||||
'updated_at' => $createdAt,
|
||||
'deleted_at' => null,
|
||||
];
|
||||
|
||||
$alreadyImported[$legacyId] = true;
|
||||
}
|
||||
|
||||
$stats['imported']++;
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
if (! $dryRun && ! empty($inserts)) {
|
||||
try {
|
||||
DB::table('artwork_comments')->insert($inserts);
|
||||
} catch (\Throwable $e) {
|
||||
// Fallback: row-by-row with ignore on unique violations
|
||||
foreach ($inserts as $row) {
|
||||
try {
|
||||
DB::table('artwork_comments')->insertOrIgnore([$row]);
|
||||
} catch (\Throwable) {
|
||||
$stats['errors']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$skippedTotal = $stats['skipped_duplicate']
|
||||
+ $stats['skipped_artwork']
|
||||
+ $stats['skipped_user']
|
||||
+ $stats['skipped_empty'];
|
||||
|
||||
$bar->setMessage((string) $stats['imported'], 'imported');
|
||||
$bar->setMessage((string) $skippedTotal, 'skipped');
|
||||
});
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Summary
|
||||
// -------------------------------------------------------------------------
|
||||
$this->table(
|
||||
['Result', 'Count'],
|
||||
[
|
||||
['Imported', $stats['imported']],
|
||||
['Skipped – already imported', $stats['skipped_duplicate']],
|
||||
['Skipped – artwork gone', $stats['skipped_artwork']],
|
||||
['Skipped – user gone', $stats['skipped_user']],
|
||||
['Skipped – empty content', $stats['skipped_empty']],
|
||||
['Errors', $stats['errors']],
|
||||
]
|
||||
);
|
||||
|
||||
if ($dryRun) {
|
||||
$this->warn('[DRY-RUN] Nothing was written. Re-run without --dry-run to apply.');
|
||||
} else {
|
||||
$this->info('Migration complete.');
|
||||
}
|
||||
|
||||
return $stats['errors'] > 0 ? self::FAILURE : self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine a legacy `date` (DATE) and `time` (TIME) column into a single datetime string.
|
||||
* Falls back to $fallback when both are null.
|
||||
*/
|
||||
private function buildTimestamp(mixed $date, mixed $time, \Illuminate\Support\Carbon $fallback): string
|
||||
{
|
||||
if (! $date) {
|
||||
return $fallback->toDateTimeString();
|
||||
}
|
||||
|
||||
$datePart = substr((string) $date, 0, 10); // '2000-09-13'
|
||||
$timePart = $time ? substr((string) $time, 0, 8) : '00:00:00'; // '09:34:27'
|
||||
|
||||
// Sanity-check: MySQL TIME can be negative or > 24h for intervals — clamp to midnight
|
||||
if (! preg_match('/^\d{2}:\d{2}:\d{2}$/', $timePart) || $timePart < '00:00:00') {
|
||||
$timePart = '00:00:00';
|
||||
}
|
||||
|
||||
return $datePart . ' ' . $timePart;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user