die tenant admin oauth authentifizierung wurde implementiert und funktioniert jetzt. Zudem wurde das marketing frontend dashboard implementiert.

This commit is contained in:
Codex Agent
2025-11-04 16:14:17 +01:00
parent 92e64c361a
commit fe380689fb
63 changed files with 4239 additions and 1142 deletions

View File

@@ -17,19 +17,11 @@ class DemoAchievementsSeeder extends Seeder
{
public function run(): void
{
$event = Event::where('slug', 'demo-wedding-2025')->first();
$tenant = Tenant::where('slug', 'demo')->first();
$tenant = Tenant::where('slug', 'demo-tenant')->first();
if (! $event || ! $tenant) {
$this->command?->warn('Demo event/tenant missing skipping DemoAchievementsSeeder');
return;
}
if (! $tenant) {
$this->command?->warn('Demo tenant missing skipping DemoAchievementsSeeder');
$tasks = Task::where('tenant_id', $tenant->id)->pluck('id')->all();
$emotions = Emotion::pluck('id')->all();
if ($tasks === [] || $emotions === []) {
$this->command?->warn('Tasks or emotions missing skipping DemoAchievementsSeeder');
return;
}
@@ -39,79 +31,134 @@ class DemoAchievementsSeeder extends Seeder
if ($sourceFiles->isEmpty()) {
$this->command?->warn('No demo photo files found skipping DemoAchievementsSeeder');
return;
}
$blueprints = [
['guest' => 'Anna Mueller', 'photos' => 6, 'likes' => [12, 8, 5, 4, 2, 1], 'withTasks' => true],
['guest' => 'Max Schmidt', 'photos' => 4, 'likes' => [9, 7, 4, 2], 'withTasks' => true],
['guest' => 'Lisa Weber', 'photos' => 2, 'likes' => [3, 1], 'withTasks' => false],
['guest' => 'Tom Fischer', 'photos' => 1, 'likes' => [14], 'withTasks' => true],
['guest' => 'Team Brautparty', 'photos' => 5, 'likes' => [5, 4, 3, 3, 2], 'withTasks' => true],
];
$emotions = Emotion::pluck('id')->all();
if ($emotions === []) {
$this->command?->warn('No emotions available skipping DemoAchievementsSeeder');
$eventDate = $event->date ? CarbonImmutable::parse($event->date) : CarbonImmutable::now();
$baseDir = "events/{$event->id}/achievements";
Storage::disk('public')->makeDirectory($baseDir);
Storage::disk('public')->makeDirectory("{$baseDir}/thumbs");
$photoIndex = 0;
foreach ($blueprints as $groupIndex => $blueprint) {
for ($i = 0; $i < $blueprint['photos']; $i++) {
$source = $sourceFiles[$photoIndex % $sourceFiles->count()];
$photoIndex++;
$filename = Str::slug($blueprint['guest'] . '-' . $groupIndex . '-' . $i) . '.jpg';
$destPath = "{$baseDir}/{$filename}";
if (! Storage::disk('public')->exists($destPath)) {
Storage::disk('public')->copy($source, $destPath);
}
$thumbSource = str_replace('photos/', 'thumbnails/', $source);
$thumbDest = "{$baseDir}/thumbs/{$filename}";
if (Storage::disk('public')->exists($thumbSource)) {
Storage::disk('public')->copy($thumbSource, $thumbDest);
} else {
Storage::disk('public')->copy($source, $thumbDest);
}
$taskId = $blueprint['withTasks'] ? $tasks[($groupIndex + $i) % count($tasks)] : null;
$emotionId = $emotions[($groupIndex * 3 + $i) % count($emotions)];
$createdAt = $eventDate->addHours($groupIndex * 2 + $i);
$likes = $blueprint['likes'][$i] ?? 0;
$photo = Photo::updateOrCreate(
[
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'guest_name' => $blueprint['guest'],
'file_path' => $destPath,
],
[
'task_id' => $taskId,
'emotion_id' => $emotionId,
'thumbnail_path' => $thumbDest,
'likes_count' => $likes,
'is_featured' => $i === 0,
'metadata' => ['demo' => true],
'created_at' => $createdAt,
'updated_at' => $createdAt,
]
);
PhotoLike::where('photo_id', $photo->id)->delete();
for ($like = 0; $like < min($likes, 15); $like++) {
PhotoLike::create([
'photo_id' => $photo->id,
'guest_name' => 'Guest_' . Str::random(6),
'ip_address' => '10.0.' . rand(0, 254) . '.' . rand(0, 254),
'created_at' => $createdAt->addMinutes($like * 3),
]);
}
}
return;
}
$this->command?->info('Demo achievements seeded.');
$scenarios = [
[
'event' => Event::with(['tasks', 'eventType'])
->where('slug', 'demo-wedding-2025')
->first(),
'blueprints' => [
['guest' => 'Anna Mueller', 'photos' => 6, 'likes' => [12, 8, 5, 4, 2, 1], 'withTasks' => true],
['guest' => 'Max Schmidt', 'photos' => 4, 'likes' => [9, 7, 4, 2], 'withTasks' => true],
['guest' => 'Lisa Weber', 'photos' => 2, 'likes' => [3, 1], 'withTasks' => false],
['guest' => 'Tom Fischer', 'photos' => 1, 'likes' => [14], 'withTasks' => true],
['guest' => 'Team Brautparty', 'photos' => 5, 'likes' => [5, 4, 3, 3, 2], 'withTasks' => true],
],
],
[
'event' => Event::with(['tasks', 'eventType'])
->where('slug', 'demo-corporate-2025')
->first(),
'blueprints' => [
['guest' => 'HR Dream Team', 'photos' => 4, 'likes' => [8, 6, 4, 3], 'withTasks' => true],
['guest' => 'Innovation Squad', 'photos' => 5, 'likes' => [10, 7, 5, 4, 2], 'withTasks' => true],
['guest' => 'Finance Crew', 'photos' => 3, 'likes' => [6, 5, 2], 'withTasks' => false],
['guest' => 'New Joiners', 'photos' => 4, 'likes' => [5, 4, 3, 2], 'withTasks' => true],
],
],
];
foreach ($scenarios as $scenario) {
/** @var Event|null $event */
$event = $scenario['event'];
if (! $event) {
$this->command?->warn('Demo event missing skipping achievements for one scenario.');
continue;
}
$taskIds = $event->tasks()->pluck('tasks.id')->all();
if ($taskIds === []) {
$taskIds = Task::where('event_type_id', $event->event_type_id ?? optional($event->eventType)->id)
->pluck('id')
->all();
}
if ($taskIds === []) {
$this->command?->warn(sprintf('No tasks available for %s achievements will use taskless entries.', $event->slug));
}
$eventDate = $event->date ? CarbonImmutable::parse($event->date) : CarbonImmutable::now();
$baseDir = "events/{$event->id}/achievements";
Storage::disk('public')->makeDirectory($baseDir);
Storage::disk('public')->makeDirectory("{$baseDir}/thumbs");
$photoIndex = 0;
foreach ($scenario['blueprints'] as $groupIndex => $blueprint) {
for ($i = 0; $i < $blueprint['photos']; $i++) {
$sourcePath = $sourceFiles[$photoIndex % $sourceFiles->count()];
$photoIndex++;
$filename = Str::slug($blueprint['guest'].'-'.$groupIndex.'-'.$i).'.jpg';
$destPath = "{$baseDir}/{$filename}";
if (! Storage::disk('public')->exists($destPath)) {
Storage::disk('public')->copy($sourcePath, $destPath);
}
$thumbSource = str_replace('photos/', 'thumbnails/', $sourcePath);
$thumbDest = "{$baseDir}/thumbs/{$filename}";
if (Storage::disk('public')->exists($thumbSource)) {
Storage::disk('public')->copy($thumbSource, $thumbDest);
} else {
Storage::disk('public')->copy($sourcePath, $thumbDest);
}
$taskId = null;
if (! empty($taskIds) && ($blueprint['withTasks'] ?? false)) {
$taskId = $taskIds[($groupIndex + $i) % count($taskIds)];
}
$emotionId = $emotions[($groupIndex * 3 + $i) % count($emotions)];
$createdAt = $eventDate->addHours($groupIndex * 2 + $i);
$likes = $blueprint['likes'][$i] ?? 0;
$photo = Photo::updateOrCreate(
[
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'guest_name' => $blueprint['guest'],
'file_path' => $destPath,
],
[
'task_id' => $taskId,
'emotion_id' => $emotionId,
'thumbnail_path' => $thumbDest,
'likes_count' => $likes,
'is_featured' => $i === 0,
'metadata' => ['demo' => true, 'achievement' => true],
'created_at' => $createdAt,
'updated_at' => $createdAt,
]
);
PhotoLike::where('photo_id', $photo->id)->delete();
for ($like = 0; $like < min($likes, 15); $like++) {
PhotoLike::create([
'photo_id' => $photo->id,
'guest_name' => 'Guest_'.Str::random(6),
'ip_address' => '10.0.'.rand(0, 254).'.'.rand(0, 254),
'created_at' => $createdAt->addMinutes($like * 3),
]);
}
}
}
$this->command?->info(sprintf('Demo achievements seeded for %s.', $event->slug));
}
}
}

View File

@@ -7,79 +7,216 @@ use App\Models\EventPackage;
use App\Models\EventType;
use App\Models\Package;
use App\Models\PackagePurchase;
use App\Models\Task;
use App\Models\TaskCollection;
use App\Models\Tenant;
use App\Services\EventJoinTokenService;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
class DemoEventSeeder extends Seeder
{
public function run(): void
{
$type = EventType::where('slug', 'wedding')->first();
if (! $type) {
return;
}
$demoTenant = Tenant::where('slug', 'demo-tenant')->first();
if (! $demoTenant) {
return;
}
$event = Event::updateOrCreate(['slug' => 'demo-wedding-2025'], [
'tenant_id' => $demoTenant->id,
'name' => ['de' => 'Demo Hochzeit 2025', 'en' => 'Demo Wedding 2025'],
'description' => ['de' => 'Demo-Event', 'en' => 'Demo event'],
'date' => now()->addMonths(3)->toDateString(),
'event_type_id' => $type->id,
'status' => 'published',
'is_active' => true,
'settings' => json_encode([]),
'default_locale' => 'de',
]);
if ($event->joinTokens()->count() === 0) {
/** @var EventJoinTokenService $service */
$service = app(EventJoinTokenService::class);
$service->createToken($event, [
'label' => 'Demo QR',
]);
}
$weddingType = EventType::where('slug', 'wedding')->first();
$corporateType = EventType::where('slug', 'corporate')->first();
$package = Package::where('slug', 'standard')->first();
if (! $package) {
$package = Package::where('type', 'endcustomer')->orderBy('price')->first();
}
$standardPackage = Package::where('slug', 'standard')->first()
?? Package::where('type', 'endcustomer')->orderBy('price')->first();
$premiumPackage = Package::where('slug', 'premium')->first()
?? Package::where('type', 'endcustomer')->orderByDesc('price')->first();
if ($package) {
$eventPackageData = [
'purchased_price' => $package->price,
'purchased_at' => now()->subDays(7),
];
if (Schema::hasColumn('event_packages', 'used_photos')) {
$eventPackageData['used_photos'] = 0;
}
if (Schema::hasColumn('event_packages', 'used_guests')) {
$eventPackageData['used_guests'] = 0;
}
if (Schema::hasColumn('event_packages', 'gallery_expires_at')) {
$eventPackageData['gallery_expires_at'] = now()->addDays($package->gallery_days ?? 30);
}
EventPackage::updateOrCreate(
[
'event_id' => $event->id,
'package_id' => $package->id,
$events = [
[
'slug' => 'demo-wedding-2025',
'name' => ['de' => 'Demo Hochzeit 2025', 'en' => 'Demo Wedding 2025'],
'description' => ['de' => 'Demo-Event', 'en' => 'Demo event'],
'date' => Carbon::now()->addMonths(3),
'event_type' => $weddingType,
'package' => $standardPackage,
'token_label' => 'Demo QR',
'collection_slugs' => ['wedding-classics-2025'],
'task_slug_prefix' => 'wedding-',
'branding' => [
'primary_color' => '#f43f5e',
'secondary_color' => '#fb7185',
'background_color' => '#fff7f4',
'font_family' => 'Playfair Display, serif',
],
$eventPackageData
],
[
'slug' => 'demo-corporate-2025',
'name' => ['de' => 'Demo Firmen-Event 2025', 'en' => 'Demo Corporate Summit 2025'],
'description' => ['de' => 'Launch-Event mit Networking', 'en' => 'Launch event with networking sessions'],
'date' => Carbon::now()->addMonths(2),
'event_type' => $corporateType,
'package' => $premiumPackage,
'token_label' => 'Corporate QR',
'collection_slugs' => ['corporate-classics-2025'],
'task_slug_prefix' => 'corporate-',
'branding' => [
'primary_color' => '#0ea5e9',
'secondary_color' => '#2563eb',
'background_color' => '#0f172a',
'font_family' => 'Inter, sans-serif',
],
],
];
foreach ($events as $config) {
if (! $config['event_type'] || ! $config['package']) {
continue;
}
$event = Event::updateOrCreate(
['slug' => $config['slug']],
[
'tenant_id' => $demoTenant->id,
'name' => $config['name'],
'description' => $config['description'],
'date' => $config['date']->toDateString(),
'event_type_id' => $config['event_type']->id,
'status' => 'published',
'is_active' => true,
'settings' => [
'branding' => $config['branding'],
],
'default_locale' => 'de',
]
);
PackagePurchase::query()
->where('tenant_id', $demoTenant->id)
->where('package_id', $package->id)
->where('provider_id', 'demo-seed')
->update([
'event_id' => $event->id,
]);
$this->ensureJoinToken($event, $config['token_label']);
$this->attachEventPackage(
event: $event,
package: $config['package'],
tenant: $demoTenant,
providerId: 'demo-seed-'.$config['slug'],
purchasedAt: Carbon::now()->subDays(7)
);
$this->attachTaskCollections($event, $config['collection_slugs']);
$this->attachEventTasks($event, $config['task_slug_prefix']);
}
}
private function ensureJoinToken(Event $event, string $label): void
{
if ($event->joinTokens()->exists()) {
return;
}
app(EventJoinTokenService::class)->createToken($event, ['label' => $label]);
}
private function attachEventPackage(Event $event, Package $package, Tenant $tenant, string $providerId, Carbon $purchasedAt): void
{
$eventPackageData = [
'purchased_price' => $package->price,
'purchased_at' => $purchasedAt,
];
if (Schema::hasColumn('event_packages', 'used_photos')) {
$eventPackageData['used_photos'] = 0;
}
if (Schema::hasColumn('event_packages', 'used_guests')) {
$eventPackageData['used_guests'] = 0;
}
if (Schema::hasColumn('event_packages', 'gallery_expires_at')) {
$eventPackageData['gallery_expires_at'] = $purchasedAt->copy()->addDays($package->gallery_days ?? 30);
}
EventPackage::updateOrCreate(
[
'event_id' => $event->id,
'package_id' => $package->id,
],
$eventPackageData
);
PackagePurchase::updateOrCreate(
[
'tenant_id' => $tenant->id,
'package_id' => $package->id,
'provider_id' => $providerId,
],
[
'event_id' => $event->id,
'price' => $package->price,
'type' => $package->type === 'reseller' ? 'reseller_subscription' : 'endcustomer_event',
'purchased_at' => $purchasedAt,
'metadata' => ['demo' => true, 'event_slug' => $event->slug],
'ip_address' => null,
'user_agent' => null,
]
);
}
private function attachTaskCollections(Event $event, array $collectionSlugs): void
{
if ($collectionSlugs === []) {
return;
}
$collections = TaskCollection::whereIn('slug', $collectionSlugs)->get();
$pivot = [];
foreach ($collections as $index => $collection) {
$pivot[$collection->id] = ['sort_order' => ($index + 1) * 10];
}
if ($pivot !== []) {
$event->taskCollections()->syncWithoutDetaching($pivot);
}
}
private function attachEventTasks(Event $event, string $slugPrefix): void
{
$tasks = [];
if ($event->event_type_id) {
$tasks = Task::where('event_type_id', $event->event_type_id)
->orderBy('sort_order')
->limit(25)
->pluck('id')
->all();
}
if ($tasks === [] && $slugPrefix !== '') {
$tasks = Task::where('slug', 'like', $slugPrefix.'%')
->orderBy('sort_order')
->limit(25)
->pluck('id')
->all();
}
if ($tasks === []) {
$tasks = Task::where('tenant_id', $event->tenant_id)
->orderBy('sort_order')
->limit(20)
->pluck('id')
->all();
}
if ($tasks === []) {
return;
}
$tasks = array_slice(array_unique($tasks), 0, 20);
$pivot = [];
foreach ($tasks as $index => $taskId) {
$pivot[$taskId] = ['sort_order' => ($index + 1) * 10];
}
$event->tasks()->syncWithoutDetaching($pivot);
}
}

View File

@@ -2,101 +2,189 @@
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
use App\Models\{Event, Task, Emotion, Photo, PhotoLike, Tenant};
use Illuminate\Support\Facades\File;
use App\Models\Emotion;
use App\Models\Event;
use App\Models\Photo;
use App\Models\PhotoLike;
use App\Models\Task;
use App\Models\Tenant;
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class DemoPhotosSeeder extends Seeder
{
public function run(): void
{
// Get demo event and tenant
$demoEvent = Event::where('slug', 'demo-wedding-2025')->first();
$demoTenant = Tenant::where('slug', 'demo')->first();
if (!$demoEvent || !$demoTenant) {
$this->command->info('Demo event or tenant not found, skipping DemoPhotosSeeder');
$tenant = Tenant::where('slug', 'demo-tenant')->first();
if (! $tenant) {
$this->command->info('Demo tenant not found, skipping DemoPhotosSeeder');
return;
}
$photoDir = storage_path('app/public/photos');
if (! File::exists($photoDir)) {
$this->command->info('No demo photos available in storage/app/public/photos');
return;
}
$photoFiles = collect(File::files($photoDir))
->filter(fn ($file) => str_ends_with(strtolower($file->getFilename()), '.jpg'))
->values();
if ($photoFiles->isEmpty()) {
$this->command->info('No JPG demo photos found, skipping DemoPhotosSeeder');
return;
}
// Get all available tasks and emotions
$tasks = Task::where('tenant_id', $demoTenant->id)->get();
$emotions = Emotion::all();
if ($emotions->isEmpty()) {
$this->command->info('No emotions available, skipping DemoPhotosSeeder');
if ($tasks->isEmpty() || $emotions->isEmpty()) {
$this->command->info('No tasks or emotions found, skipping DemoPhotosSeeder');
return;
}
// List of 20 guest names (ASCII only to avoid encoding issues)
$guestNames = [
'Anna Mueller', 'Max Schmidt', 'Lisa Weber', 'Tom Fischer', 'Sophie Bauer',
'Lukas Hoffmann', 'Emma Wagner', 'Jonas Klein', 'Mia Schwarz', 'Felix Becker',
'Lena Richter', 'Paul Lehmann', 'Julia Neumann', 'David Vogel', 'Sara Krueger',
'Tim Berger', 'Nina Wolf', 'Ben Schaefer', 'Laura Stein', 'Moritz Fuchs'
$events = [
[
'model' => Event::with(['tasks', 'eventPackage', 'eventPackages', 'eventType'])
->where('slug', 'demo-wedding-2025')
->first(),
'guest_names' => [
'Anna Mueller', 'Max Schmidt', 'Lisa Weber', 'Tom Fischer', 'Sophie Bauer',
'Lukas Hoffmann', 'Emma Wagner', 'Jonas Klein', 'Mia Schwarz', 'Felix Becker',
'Lena Richter', 'Paul Lehmann', 'Julia Neumann', 'David Vogel', 'Sara Krueger',
'Tim Berger', 'Nina Wolf', 'Ben Schaefer', 'Laura Stein', 'Moritz Fuchs',
],
'like_range' => [4, 18],
],
[
'model' => Event::with(['tasks', 'eventPackage', 'eventPackages', 'eventType'])
->where('slug', 'demo-corporate-2025')
->first(),
'guest_names' => [
'Clara Schmidt', 'Jan Becker', 'Noah Winkler', 'Sina Albrecht', 'Kai Lenz',
'Tara Nguyen', 'Omar Hassan', 'Elias Roth', 'Greta Sommer', 'Leonard Busch',
'Verena Graf', 'Nico Adler', 'Johanna Kurz', 'Fabian Scholz', 'Mara Kranz',
'Yuki Tanaka', 'Mateo Ruiz', 'Amina Korb', 'Philipp Krüger', 'Selma Vogt',
],
'like_range' => [2, 12],
],
];
// Get all photo files from storage
$photoDir = storage_path('app/public/photos');
$photoFiles = File::files($photoDir);
$seededCount = 0;
foreach ($photoFiles as $file) {
$filename = $file->getFilename();
if (!str_ends_with($filename, '.jpg')) {
foreach ($events as $config) {
/** @var Event|null $event */
$event = $config['model'];
if (! $event) {
$this->command->warn('Demo event missing, skipping photo seeding.');
continue;
}
// Check if already seeded (avoid duplicates)
if (Photo::where('file_path', 'photos/' . $filename)->exists()) {
$taskIds = $event->tasks()->pluck('tasks.id')->all();
if ($taskIds === []) {
$eventTypeId = $event->event_type_id ?? optional($event->eventType)->id;
$taskIds = Task::where('event_type_id', $eventTypeId)->pluck('id')->all();
}
if ($taskIds === []) {
$this->command->warn(sprintf('No tasks assigned to %s. Photos will be seeded without task references.', $event->slug));
}
$guestNames = $config['guest_names'];
$photosToSeed = min($photoFiles->count(), count($guestNames));
if ($photosToSeed === 0) {
continue;
}
// Generate thumbnail path
$thumbnailFilename = str_replace('.jpg', '_thumb.jpg', $filename);
$thumbnailPath = 'thumbnails/' . $thumbnailFilename;
$storage = Storage::disk('public');
$storage->makeDirectory("events/{$event->id}/gallery");
$storage->makeDirectory("events/{$event->id}/gallery/thumbs");
// Random assignments
$randomTask = $tasks->random();
$randomEmotion = $emotions->random();
$randomGuest = $guestNames[array_rand($guestNames)];
$randomLikes = rand(0, 20);
$eventDate = $demoEvent->date;
$randomUploadedAt = Carbon::parse($eventDate)->addHours(rand(0, 24))->addMinutes(rand(0, 59));
$photosSeeded = 0;
// Create photo
$photo = Photo::create([
'tenant_id' => $demoTenant->id,
'event_id' => $demoEvent->id,
'task_id' => $randomTask->id,
'emotion_id' => $randomEmotion->id,
'guest_name' => $randomGuest,
'file_path' => 'photos/' . $filename,
'thumbnail_path' => $thumbnailPath,
'likes_count' => $randomLikes,
'is_featured' => false,
'metadata' => [],
'created_at' => $randomUploadedAt,
'updated_at' => $randomUploadedAt,
]);
for ($i = 0; $i < $photosToSeed; $i++) {
$sourceFile = $photoFiles->get($i % $photoFiles->count());
$baseName = pathinfo($sourceFile->getFilename(), PATHINFO_FILENAME);
$guestName = $guestNames[$i];
$likes = rand($config['like_range'][0], $config['like_range'][1]);
$timestamp = Carbon::parse($event->date ?? now())
->addHours(rand(0, 36))
->addMinutes(rand(0, 59));
// Add random likes
if ($randomLikes > 0) {
for ($i = 0; $i < $randomLikes; $i++) {
$filename = sprintf('%s-demo-%02d.jpg', $event->slug, $i + 1);
$destPath = "events/{$event->id}/gallery/{$filename}";
if (! $storage->exists($destPath)) {
$storage->put($destPath, File::get($sourceFile->getRealPath()));
}
$thumbFilename = sprintf('%s-demo-%02d_thumb.jpg', $event->slug, $i + 1);
$thumbDest = "events/{$event->id}/gallery/thumbs/{$thumbFilename}";
$existingThumb = "thumbnails/{$baseName}_thumb.jpg";
if ($storage->exists($existingThumb)) {
if (! $storage->exists($thumbDest)) {
$storage->copy($existingThumb, $thumbDest);
}
} else {
if (! $storage->exists($thumbDest)) {
$storage->put($thumbDest, File::get($sourceFile->getRealPath()));
}
}
$taskId = $taskIds ? $taskIds[array_rand($taskIds)] : null;
$emotionId = $emotions->random()->id;
$photo = Photo::updateOrCreate(
[
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'file_path' => $destPath,
],
[
'task_id' => $taskId,
'emotion_id' => $emotionId,
'guest_name' => $guestName,
'thumbnail_path' => $thumbDest,
'likes_count' => $likes,
'is_featured' => $i === 0,
'metadata' => ['demo' => true],
'created_at' => $timestamp,
'updated_at' => $timestamp,
]
);
PhotoLike::where('photo_id', $photo->id)->delete();
$maxLikes = min($likes, 10);
for ($like = 0; $like < $maxLikes; $like++) {
PhotoLike::create([
'photo_id' => $photo->id,
'guest_name' => 'GuestLike_' . Str::random(6),
'ip_address' => '10.0.' . rand(0, 254) . '.' . rand(1, 254),
'created_at' => $randomUploadedAt->clone()->addMinutes(rand(0, 60)),
'guest_name' => 'GuestLike_'.Str::random(6),
'ip_address' => '10.0.'.rand(0, 254).'.'.rand(1, 254),
'created_at' => $timestamp->copy()->addMinutes($like * 3),
]);
}
$photosSeeded++;
}
$seededCount++;
}
$eventPackage = $event->eventPackage ?? $event->eventPackages()->orderByDesc('purchased_at')->first();
if ($eventPackage) {
$eventPackage->forceFill([
'used_photos' => max($eventPackage->used_photos ?? 0, $photosSeeded),
'used_guests' => max($eventPackage->used_guests ?? 0, count(array_unique($guestNames))),
])->save();
}
$this->command->info(sprintf('Seeded %d demo photos with random tasks, emotions, uploaders, and likes', $seededCount));
$this->command->info(sprintf('Seeded %d demo photos for %s', $photosSeeded, $event->slug));
}
}
}

View File

@@ -1,56 +0,0 @@
<?php
namespace Database\Seeders;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class E2ETenantSeeder extends Seeder
{
public function run(): void
{
= config('testing.e2e_tenant_email', env('E2E_TENANT_EMAIL', 'tenant-e2e@example.com'));
= config('testing.e2e_tenant_password', env('E2E_TENANT_PASSWORD', 'password123'));
= User::firstOrNew(['email' => ]);
->fill([
'name' => ->name ?? 'E2E Tenant Admin',
'first_name' => ->first_name ?? 'E2E',
'last_name' => ->last_name ?? 'Admin',
'role' => 'tenant_admin',
'pending_purchase' => false,
]);
->password = Hash::make();
->email_verified_at = now();
->save();
= Tenant::firstOrNew(['user_id' => ->id]);
->fill([
'name' => ->name ?? 'E2E Test Tenant',
'slug' => ->slug ?? Str::slug('e2e-tenant-' . ->id),
'email' => ,
'is_active' => true,
'is_suspended' => false,
'event_credits_balance' => ->event_credits_balance ?? 1,
'subscription_status' => ->subscription_status ?? 'active',
'settings' => ->settings ?? [
'branding' => [
'logo_url' => null,
'primary_color' => '#ef476f',
'secondary_color' => '#ffd166',
'font_family' => 'Montserrat, sans-serif',
],
'features' => [
'photo_likes_enabled' => true,
],
'contact_email' => ,
'event_default_type' => 'general',
],
]);
->save();
}
}

View File

@@ -2,18 +2,20 @@
namespace Database\Seeders;
use App\Models\Emotion;
use App\Models\Task;
use App\Models\Tenant;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
use App\Models\{Emotion, Task, Tenant};
class EventTasksSeeder extends Seeder
{
public function run(): void
{
// Get demo tenant
$demoTenant = Tenant::where('slug', 'demo')->first();
if (!$demoTenant) {
$demoTenant = Tenant::where('slug', 'demo-tenant')->first();
if (! $demoTenant) {
$this->command->info('Demo tenant not found, skipping EventTasksSeeder');
return;
}
@@ -198,14 +200,17 @@ class EventTasksSeeder extends Seeder
];
// Difficulty rotation
$difficulties = ['easy','easy','medium','easy','medium','hard'];
$difficulties = ['easy', 'easy', 'medium', 'easy', 'medium', 'hard'];
foreach (Emotion::all() as $emotion) {
$name = is_array($emotion->name) ? ($emotion->name['de'] ?? array_values($emotion->name)[0]) : (string) $emotion->name;
$list = $catalog[$name] ?? null;
if (!$list) continue; // skip unknown emotion labels
if (! $list) {
continue;
} // skip unknown emotion labels
$created = 0; $order = 1;
$created = 0;
$order = 1;
foreach ($list as $i => $row) {
[$deTitle, $deDesc, $enTitle, $enDesc] = $row;
@@ -213,7 +218,11 @@ class EventTasksSeeder extends Seeder
$exists = Task::where('emotion_id', $emotion->id)
->where('title->de', $deTitle)
->exists();
if ($exists) { $order++; continue; }
if ($exists) {
$order++;
continue;
}
Task::create([
'tenant_id' => $demoTenant->id,
@@ -233,7 +242,7 @@ class EventTasksSeeder extends Seeder
$i = 0;
while ($created < 20 && $i < count($list)) {
[$deTitle, $deDesc, $enTitle, $enDesc] = $list[$i];
$suffix = ' #' . ($created + 1);
$suffix = ' #'.($created + 1);
Task::create([
'tenant_id' => $demoTenant->id,
'emotion_id' => $emotion->id,
@@ -245,9 +254,9 @@ class EventTasksSeeder extends Seeder
'sort_order' => $order++,
'is_active' => true,
]);
$created++; $i++;
$created++;
$i++;
}
}
}
}

View File

@@ -6,6 +6,7 @@ use App\Models\Emotion;
use App\Models\EventType;
use App\Models\Task;
use App\Models\TaskCollection;
use App\Models\Tenant;
use Illuminate\Database\Seeder;
use Illuminate\Support\Str;
@@ -13,18 +14,13 @@ class TasksSeeder extends Seeder
{
public function run(): void
{
// Create or get demo tenant
$demoTenant = \App\Models\Tenant::updateOrCreate(
['slug' => 'demo'],
[
'name' => 'Demo Tenant',
'domain' => null,
'is_active' => true,
'is_suspended' => false,
'settings' => json_encode([]),
'settings_updated_at' => null,
]
);
$demoTenant = Tenant::where('slug', 'demo-tenant')->first();
if (! $demoTenant) {
$this->command?->warn('Demo tenant not found, skipping TasksSeeder');
return;
}
$seed = [
'Liebe' => [

View File

@@ -2,18 +2,22 @@
namespace Database\Seeders;
use App\Models\Emotion;
use App\Models\EventType;
use App\Models\Task;
use Illuminate\Database\Seeder;
use App\Models\{Emotion, Task, EventType};
class WeddingTasksSeeder extends Seeder
{
public function run(): void
{
$weddingType = EventType::where('slug','wedding')->first();
if (!$weddingType) return;
$weddingType = EventType::where('slug', 'wedding')->first();
if (! $weddingType) {
return;
}
// Helper to resolve emotion by English name (more stable given encoding issues)
$by = fn(string $en) => Emotion::where('name->en', $en)->first();
$by = fn (string $en) => Emotion::where('name->en', $en)->first();
$emLove = $by('Love');
$emJoy = $by('Joy');
@@ -88,7 +92,9 @@ class WeddingTasksSeeder extends Seeder
$sort = 1;
foreach ($tasks as [$emotion, $titleDe, $titleEn, $descDe, $descEn, $difficulty]) {
if (!$emotion) continue;
if (! $emotion) {
continue;
}
Task::updateOrCreate([
'emotion_id' => $emotion->id,
'title->de' => $titleDe,
@@ -104,4 +110,3 @@ class WeddingTasksSeeder extends Seeder
}
}
}