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

@@ -0,0 +1,408 @@
<?php
namespace Database\Seeders;
use App\Models\Event;
use App\Models\EventPackage;
use App\Models\EventType;
use App\Models\OAuthClient;
use App\Models\Package;
use App\Models\PackagePurchase;
use App\Models\Tenant;
use App\Models\TenantPackage;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class DemoLifecycleSeeder extends Seeder
{
public function run(): void
{
[$standard, $premium, $reseller] = $this->ensurePackages();
[$weddingType, $corporateType] = $this->ensureEventTypes();
$this->seedOnboardingTenant();
$this->seedActiveTenant($standard, $premium, $weddingType, $corporateType);
$this->seedResellerTenant($reseller, $standard, $weddingType);
$this->seedDormantTenant();
}
private function ensurePackages(): array
{
$standard = Package::firstOrCreate(
['slug' => 'standard'],
[
'type' => 'endcustomer',
'name' => 'Standard',
'name_translations' => ['de' => 'Standard', 'en' => 'Standard'],
'price' => 79,
'max_photos' => 1500,
'max_guests' => 400,
'gallery_days' => 60,
'features' => [
'basic_uploads' => true,
'unlimited_sharing' => true,
'no_watermark' => true,
'custom_tasks' => true,
],
]
);
$premium = Package::firstOrCreate(
['slug' => 'premium'],
[
'type' => 'endcustomer',
'name' => 'Premium',
'name_translations' => ['de' => 'Premium', 'en' => 'Premium'],
'price' => 149,
'max_photos' => 5000,
'max_guests' => 1000,
'gallery_days' => 180,
'features' => [
'basic_uploads' => true,
'unlimited_sharing' => true,
'no_watermark' => true,
'custom_branding' => true,
'custom_tasks' => true,
],
]
);
$reseller = Package::firstOrCreate(
['slug' => 'studio-annual'],
[
'type' => 'reseller',
'name' => 'Studio Jahrespaket',
'name_translations' => ['de' => 'Studio Jahrespaket', 'en' => 'Studio Annual'],
'price' => 1299,
'max_events_per_year' => 24,
'features' => [
'custom_branding' => true,
'unlimited_sharing' => true,
'basic_uploads' => true,
],
]
);
return [$standard, $premium, $reseller];
}
private function ensureEventTypes(): array
{
$weddingType = EventType::firstOrCreate(
['slug' => 'wedding'],
[
'name' => 'Wedding',
'name_translations' => ['de' => 'Hochzeit', 'en' => 'Wedding'],
'icon' => 'heart',
]
);
$corporateType = EventType::firstOrCreate(
['slug' => 'corporate'],
[
'name' => 'Corporate Event',
'name_translations' => ['de' => 'Firmen-Event', 'en' => 'Corporate'],
'icon' => 'presentation-chart',
]
);
return [$weddingType, $corporateType];
}
private function seedOnboardingTenant(): void
{
$tenant = $this->createTenant('storycraft-weddings', [
'name' => 'Storycraft Weddings',
'contact_email' => 'hello@storycraft-weddings.demo',
'event_credits_balance' => 0,
'subscription_tier' => 'free',
'subscription_status' => 'free',
'subscription_expires_at' => null,
'is_active' => true,
]);
$this->createTenantAdmin($tenant, 'storycraft-owner@demo.fotospiel');
$this->ensureOAuthClientForTenant($tenant, 'demo-tenant-admin-storycraft');
}
private function seedActiveTenant(Package $standard, Package $premium, EventType $weddingType, EventType $corporateType): void
{
$tenant = $this->createTenant('lumen-moments', [
'name' => 'Lumen Moments',
'contact_email' => 'hello@lumen-moments.demo',
'event_credits_balance' => 2,
'subscription_tier' => 'starter',
'subscription_status' => 'active',
'is_active' => true,
]);
OAuthClient::query()
->where('client_id', config('services.oauth.tenant_admin.id', 'tenant-admin-app'))
->update(['tenant_id' => $tenant->id]);
$this->createTenantAdmin($tenant, 'hello@lumen-moments.demo');
$this->ensureOAuthClientForTenant($tenant, 'demo-tenant-admin-lumen');
$purchase = PackagePurchase::create([
'tenant_id' => $tenant->id,
'package_id' => $premium->id,
'provider' => 'stripe',
'provider_id' => 'stripe_demo_pi',
'price' => $premium->price,
'type' => 'endcustomer_event',
'purchased_at' => Carbon::now()->subDays(3),
'metadata' => ['demo' => true],
]);
$publishedEvent = $this->createEventWithPackage(
tenant: $tenant,
package: $premium,
eventType: $weddingType,
attributes: [
'name' => ['de' => 'Sommerhochzeit Lea & Tim', 'en' => 'Summer Wedding Lea & Tim'],
'slug' => 'summer-wedding-lea-tim',
'status' => 'published',
'is_active' => true,
'date' => Carbon::now()->addWeeks(4),
]
);
$draftEvent = $this->createEventWithPackage(
tenant: $tenant,
package: $standard,
eventType: $corporateType,
attributes: [
'name' => ['de' => 'Startup Social 2025', 'en' => 'Startup Social 2025'],
'slug' => 'startup-social-2025',
'status' => 'draft',
'is_active' => false,
'date' => Carbon::now()->addWeeks(12),
]
);
$purchase->update(['event_id' => $publishedEvent->id]);
PackagePurchase::create([
'tenant_id' => $tenant->id,
'event_id' => $draftEvent->id,
'package_id' => $standard->id,
'provider' => 'paypal',
'provider_id' => 'paypal_demo_capture',
'price' => $standard->price,
'type' => 'endcustomer_event',
'purchased_at' => Carbon::now()->subDays(1),
'metadata' => ['demo' => true],
]);
}
private function seedResellerTenant(Package $reseller, Package $standard, EventType $weddingType): void
{
$tenant = $this->createTenant('viewfinder-studios', [
'name' => 'Viewfinder Studios',
'contact_email' => 'team@viewfinder.demo',
'event_credits_balance' => 0,
'subscription_tier' => 'reseller',
'subscription_status' => 'active',
'is_active' => true,
]);
$this->createTenantAdmin($tenant, 'team@viewfinder.demo');
$this->ensureOAuthClientForTenant($tenant, 'demo-tenant-admin-viewfinder');
$tenantPackage = TenantPackage::create([
'tenant_id' => $tenant->id,
'package_id' => $reseller->id,
'price' => $reseller->price,
'purchased_at' => Carbon::now()->subMonths(2),
'expires_at' => Carbon::now()->addMonths(10),
'used_events' => 6,
'active' => true,
]);
PackagePurchase::create([
'tenant_id' => $tenant->id,
'package_id' => $reseller->id,
'provider' => 'stripe',
'provider_id' => 'stripe_demo_subscription',
'price' => $reseller->price,
'type' => 'reseller_subscription',
'purchased_at' => $tenantPackage->purchased_at,
'metadata' => ['demo' => true, 'plan' => 'studio-annual'],
]);
// Create a mix of events representing allowance consumption.
$statuses = ['published', 'published', 'draft', 'archived'];
foreach ($statuses as $index => $status) {
$event = $this->createEventWithPackage(
tenant: $tenant,
package: $standard,
eventType: $weddingType,
attributes: [
'name' => ['de' => 'Studio Event #'.($index + 1), 'en' => 'Studio Event #'.($index + 1)],
'slug' => 'studio-event-'.($index + 1),
'status' => $status,
'is_active' => $status === 'published',
'date' => $status === 'archived'
? Carbon::now()->subMonths(3)
: Carbon::now()->addWeeks($index * 3 + 1),
]
);
PackagePurchase::create([
'tenant_id' => $tenant->id,
'event_id' => $event->id,
'package_id' => $standard->id,
'provider' => 'manual',
'provider_id' => 'reseller_allowance',
'price' => 0,
'type' => 'endcustomer_event',
'purchased_at' => Carbon::now()->subDays(10 - $index),
'metadata' => ['allowance' => true],
]);
}
}
private function seedDormantTenant(): void
{
$tenant = $this->createTenant('pixel-and-co', [
'name' => 'Pixel & Co',
'contact_email' => 'support@pixelco.demo',
'subscription_status' => 'expired',
'subscription_tier' => 'free',
'subscription_expires_at' => Carbon::now()->subMonths(2),
'is_active' => false,
'is_suspended' => false,
'event_credits_balance' => 0,
]);
$this->createTenantAdmin($tenant, 'support@pixelco.demo', role: 'member');
$this->ensureOAuthClientForTenant($tenant, 'demo-tenant-admin-pixel');
}
private function createTenantAdmin(Tenant $tenant, string $email, string $role = 'tenant_admin'): User
{
return User::updateOrCreate(
['email' => $email],
[
'tenant_id' => $tenant->id,
'role' => $role,
'password' => Hash::make('Demo1234!'),
'first_name' => Str::headline(Str::before($tenant->slug, '-')),
'last_name' => 'Team',
]
);
}
private function createEventWithPackage(
Tenant $tenant,
Package $package,
EventType $eventType,
array $attributes
): Event {
$payload = array_merge([
'tenant_id' => $tenant->id,
'event_type_id' => $eventType->id,
'settings' => [
'features' => [
'photo_likes_enabled' => true,
'event_checklist' => true,
],
],
], $attributes);
$event = Event::updateOrCreate(['slug' => $attributes['slug']], $payload);
EventPackage::updateOrCreate(
[
'event_id' => $event->id,
'package_id' => $package->id,
],
[
'purchased_price' => $package->price,
'purchased_at' => Carbon::now()->subDays(2),
'used_photos' => 0,
'used_guests' => 0,
'gallery_expires_at' => Carbon::now()->addDays($package->gallery_days ?? 30),
]
);
return $event;
}
private function createTenant(string $slug, array $overrides = []): Tenant
{
$email = $overrides['contact_email'] ?? $slug.'@demo.fotospiel';
$defaults = [
'name' => Str::headline(str_replace('-', ' ', $slug)),
'slug' => $slug,
'contact_email' => $email,
'event_credits_balance' => 0,
'subscription_tier' => 'free',
'subscription_status' => 'free',
'subscription_expires_at' => Carbon::now()->addMonths(6),
'is_active' => true,
'is_suspended' => false,
'settings_updated_at' => Carbon::now(),
'settings' => $this->defaultSettings($email),
];
$attributes = array_merge($defaults, $overrides);
$tenant = Tenant::updateOrCreate(['slug' => $slug], $attributes);
return $tenant;
}
private function defaultSettings(string $contactEmail): array
{
return [
'branding' => [
'logo_url' => null,
'primary_color' => '#3B82F6',
'secondary_color' => '#1F2937',
'font_family' => 'Inter, sans-serif',
],
'features' => [
'photo_likes_enabled' => true,
'event_checklist' => true,
'custom_domain' => false,
'advanced_analytics' => false,
],
'custom_domain' => null,
'contact_email' => $contactEmail,
'event_default_type' => 'general',
];
}
private function ensureOAuthClientForTenant(Tenant $tenant, string $clientId): void
{
$redirectUris = config('services.oauth.tenant_admin.redirects', []);
if (empty($redirectUris)) {
$redirectUris = [
'http://localhost:5173/event-admin/auth/callback',
url('/event-admin/auth/callback'),
];
}
$client = OAuthClient::firstOrNew(['client_id' => $clientId]);
if (! $client->exists) {
$client->id = (string) Str::uuid();
}
$client->fill([
'client_secret' => null,
'tenant_id' => $tenant->id,
'redirect_uris' => $redirectUris,
'scopes' => ['tenant:read', 'tenant:write'],
'is_active' => true,
]);
$client->save();
}
}