- Event-Medien tragen Typ (image/video/audio), Dauer und Notiz; Videos bekommen ein Poster, Audio wird per ffmpeg nach AAC (audio/mp4) gewandelt, Originale werden mit Range-Unterstützung gestreamt. - Upload nimmt eine Notiz (caption) direkt mit, PATCH ändert sie später. - Neue Event-Felder: Zeitraum (end_date), Kategorie, Personen, Meilenstein. Regeln zentral in EventFields, damit CRUD und Sync gleich prüfen. - Titel darf leer sein: ein Event entsteht mit dem ersten Eintrag. - Artisan-Befehl media:transcode-voice-notes wandelt alte WebM/Ogg-Notizen, die Safari nicht abspielen kann. - Login-Throttling, Settings-Validierung, Sync-Fehlercodes ohne Rohtext. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
108 lines
3.5 KiB
PHP
108 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\User;
|
|
use App\Services\EventMediaStore;
|
|
use Database\Seeders\LifelineSeeder;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
/**
|
|
* Attaches generated key images to the seeded lifeline events.
|
|
*
|
|
* Images are expected as `<age>-<month>.png|jpg` (zero padded, e.g. 04-07)
|
|
* in `storage/app/private/lifeline-images/<handle>/`. Events that already
|
|
* have a key image are skipped unless --force is given.
|
|
*/
|
|
class ImportLifelineImages extends Command
|
|
{
|
|
protected $signature = 'lifelines:import-images
|
|
{handle : Lifeline handle, e.g. bruno}
|
|
{--from= : Image directory (default: storage/app/private/lifeline-images/<handle>)}
|
|
{--force : Replace key images that are already attached}
|
|
{--dry-run : Only report what would be imported}';
|
|
|
|
protected $description = 'Attach generated key images to seeded lifeline events';
|
|
|
|
public function handle(EventMediaStore $store): int
|
|
{
|
|
$handle = (string) $this->argument('handle');
|
|
$dataPath = database_path("data/lifelines/{$handle}.json");
|
|
$imageDirectory = (string) ($this->option('from') ?: storage_path("app/private/lifeline-images/{$handle}"));
|
|
|
|
if (! File::exists($dataPath)) {
|
|
$this->error("No lifeline data found at {$dataPath}.");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$lifeline = json_decode(File::get($dataPath), true, flags: JSON_THROW_ON_ERROR);
|
|
$user = User::query()->where('email', $lifeline['person']['email'])->first();
|
|
|
|
if ($user === null) {
|
|
$this->error("User {$lifeline['person']['email']} is not seeded yet. Run the LifelineSeeder first.");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$imported = $skipped = $missing = 0;
|
|
|
|
foreach ($lifeline['events'] as $data) {
|
|
$slug = sprintf('%02d-%02d', $data['age'], $data['month']);
|
|
$imagePath = $this->findImage($imageDirectory, $slug);
|
|
|
|
if ($imagePath === null) {
|
|
$missing++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$clientId = LifelineSeeder::clientId($lifeline['person']['email'], $data['age'], $data['month'], $data['day'] ?? null);
|
|
$event = Event::query()->where('user_id', $user->id)->where('client_id', $clientId)->first();
|
|
|
|
if ($event === null) {
|
|
$this->warn("{$slug}: event not seeded, skipping.");
|
|
$missing++;
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($event->image !== null && ! $this->option('force')) {
|
|
$skipped++;
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($this->option('dry-run')) {
|
|
$this->line("{$slug}: would import ".basename($imagePath)." → {$event->title}");
|
|
$imported++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$store->store($event, new UploadedFile($imagePath, basename($imagePath), null, null, true), 'key_image');
|
|
$this->line("{$slug}: {$event->title}");
|
|
$imported++;
|
|
}
|
|
|
|
$this->info("Imported {$imported}, skipped {$skipped} (already attached), {$missing} without image.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function findImage(string $directory, string $slug): ?string
|
|
{
|
|
foreach (['png', 'jpg', 'jpeg'] as $extension) {
|
|
$path = "{$directory}/{$slug}.{$extension}";
|
|
|
|
if (File::exists($path)) {
|
|
return $path;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|