die tenant admin oauth authentifizierung wurde implementiert und funktioniert jetzt. Zudem wurde das marketing frontend dashboard implementiert.
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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' => [
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user