Initialize repo and add session changes (2025-09-08)

This commit is contained in:
Auto Commit
2025-09-08 14:03:43 +02:00
commit 44ab0a534b
327 changed files with 40952 additions and 0 deletions

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Console\Commands;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Illuminate\Console\Attributes\AsCommand;
#[AsCommand(name: 'tenant:add-dummy')]
class AddDummyTenantUser extends Command
{
protected $signature = 'tenant:add-dummy
{--email=demo@example.com}
{--password=secret123!}
{--tenant="Demo Tenant"}
{--name="Demo Admin"}
{--update-password : Overwrite password if user already exists}
';
protected $description = 'Create a demo tenant and a tenant user with given credentials.';
public function handle(): int
{
$email = (string) $this->option('email');
$password = (string) $this->option('password');
$tenantName = (string) $this->option('tenant');
$userName = (string) $this->option('name');
// Pre-flight checks for common failures
if (! Schema::hasTable('users')) {
$this->error("Table 'users' does not exist. Run: php artisan migrate");
return self::FAILURE;
}
if (! Schema::hasTable('tenants')) {
$this->error("Table 'tenants' does not exist. Run: php artisan migrate");
return self::FAILURE;
}
DB::beginTransaction();
try {
// Create or fetch tenant
$slug = Str::slug($tenantName ?: 'demo-tenant');
/** @var Tenant $tenant */
$tenant = Tenant::query()->where('slug', $slug)->first();
if (! $tenant) {
$tenant = new Tenant();
$tenant->name = $tenantName;
$tenant->slug = $slug;
$tenant->domain = null;
$tenant->contact_name = $userName;
$tenant->contact_email = $email;
$tenant->contact_phone = null;
$tenant->event_credits_balance = 1;
$tenant->max_photos_per_event = 500;
$tenant->max_storage_mb = 1024;
$tenant->features = ['custom_branding' => false];
$tenant->save();
}
// Create or fetch user
/** @var User $user */
$user = User::query()->where('email', $email)->first();
$updatePassword = (bool) $this->option('update-password');
if (! $user) {
$user = new User();
if (Schema::hasColumn($user->getTable(), 'name')) $user->name = $userName;
$user->email = $email;
$user->password = Hash::make($password);
} else if ($updatePassword) {
$user->password = Hash::make($password);
}
if (Schema::hasColumn($user->getTable(), 'tenant_id')) {
$user->tenant_id = $tenant->id;
}
if (Schema::hasColumn($user->getTable(), 'role')) {
$user->role = 'tenant_admin';
}
$user->save();
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
$this->error('Failed: '.$e->getMessage());
return self::FAILURE;
}
$this->info('Dummy tenant user created/updated.');
$this->line('Tenant: '.$tenant->name.' (#'.$tenant->id.')');
$this->line('Email: '.$email);
$this->line('Password: '.$password);
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Console\Commands;
use App\Models\Event;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Console\Attributes\AsCommand;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
#[AsCommand(name: 'tenant:attach-demo-event')]
class AttachDemoEvent extends Command
{
protected $signature = 'tenant:attach-demo-event
{--tenant-email=demo@example.com : Email of tenant admin user to locate tenant}
{--tenant-slug= : Tenant slug (overrides tenant-email lookup)}
{--event-id= : Event ID}
{--event-slug= : Event slug}
';
protected $description = 'Attach an existing demo event to a tenant (by email or slug). Safe and idempotent.';
public function handle(): int
{
if (! \Illuminate\Support\Facades\Schema::hasTable('events')) {
$this->error("Table 'events' does not exist. Run: php artisan migrate");
return self::FAILURE;
}
if (! \Illuminate\Support\Facades\Schema::hasColumn('events', 'tenant_id')) {
$this->error("Column 'events.tenant_id' does not exist. Add it and rerun. Suggested: create a migration to add a nullable foreignId to tenants.");
return self::FAILURE;
}
$tenant = null;
if ($slug = $this->option('tenant-slug')) {
$tenant = Tenant::where('slug', $slug)->first();
}
if (! $tenant) {
$email = (string) $this->option('tenant-email');
/** @var User|null $user */
$user = User::where('email', $email)->first();
if ($user && $user->tenant_id) {
$tenant = Tenant::find($user->tenant_id);
}
}
if (! $tenant) {
$this->error('Tenant not found. Provide --tenant-slug or a user with tenant_id via --tenant-email.');
return self::FAILURE;
}
$event = null;
if ($id = $this->option('event-id')) {
$event = Event::find($id);
} elseif ($slug = $this->option('event-slug')) {
$event = Event::where('slug', $slug)->first();
} else {
// Heuristics: first event without tenant, or a demo wedding by slug/name
$event = Event::whereNull('tenant_id')->first();
if (! $event) {
$event = Event::where('slug', 'like', '%demo%')->where('slug', 'like', '%wedding%')->first();
}
if (! $event) {
// Try JSON name contains "Demo" or "Wedding"
$event = Event::where('name', 'like', '%Demo%')->orWhere('name', 'like', '%Wedding%')->first();
}
}
if (! $event) {
$this->error('Event not found. Provide --event-id or --event-slug.');
return self::FAILURE;
}
// Idempotent update
if ((int) $event->tenant_id === (int) $tenant->id) {
$this->info("Event #{$event->id} already attached to tenant #{$tenant->id} ({$tenant->slug}).");
return self::SUCCESS;
}
$event->tenant_id = $tenant->id;
$event->save();
$this->info("Attached event #{$event->id} ({$event->slug}) to tenant #{$tenant->id} ({$tenant->slug}).");
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Console\Commands;
use App\Support\ImageHelper;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class BackfillThumbnails extends Command
{
protected $signature = 'media:backfill-thumbnails {--limit=500}';
protected $description = 'Generate thumbnails for photos missing thumbnail_path or where thumbnail equals original.';
public function handle(): int
{
$limit = (int) $this->option('limit');
$rows = DB::table('photos')
->select(['id','event_id','file_path','thumbnail_path'])
->orderBy('id')
->limit($limit)
->get();
$count = 0;
foreach ($rows as $r) {
$orig = $this->relativeFromUrl((string)$r->file_path);
$thumb = (string)($r->thumbnail_path ?? '');
if ($thumb && $thumb !== $r->file_path) continue; // already set to different thumb
if (! $orig) continue;
$baseName = pathinfo($orig, PATHINFO_FILENAME);
$destRel = "events/{$r->event_id}/photos/thumbs/{$baseName}_thumb.jpg";
$made = ImageHelper::makeThumbnailOnDisk('public', $orig, $destRel, 640, 82);
if ($made) {
$url = Storage::url($made);
DB::table('photos')->where('id', $r->id)->update(['thumbnail_path' => $url, 'updated_at' => now()]);
$count++;
$this->line("Photo {$r->id}: thumb created");
}
}
$this->info("Done. Thumbnails generated: {$count}");
return self::SUCCESS;
}
private function relativeFromUrl(string $url): ?string
{
// Assume Storage::url maps to /storage/*
$p = parse_url($url, PHP_URL_PATH) ?? '';
if (str_starts_with($p, '/storage/')) {
return substr($p, strlen('/storage/'));
}
return null;
}
}