attributes->get('tenant_id'); if (! $tenantId) { throw ValidationException::withMessages([ 'tenant_id' => 'Tenant ID not found in request context.', ]); } $query = Event::where('tenant_id', $tenantId) ->with([ 'eventType', 'photos', 'eventPackages.package', 'eventPackage.package', ]) ->orderBy('created_at', 'desc'); if ($request->has('status')) { $query->where('status', $request->status); } if ($request->has('type_id')) { $query->where('event_type_id', $request->type_id); } $events = $query->paginate($request->get('per_page', 15)); return EventResource::collection($events); } public function store(EventStoreRequest $request): JsonResponse { $tenant = $request->attributes->get('tenant'); if (! $tenant instanceof Tenant) { $tenantId = $request->attributes->get('tenant_id'); $tenant = Tenant::findOrFail($tenantId); } // Package check is now handled by middleware $validated = $request->validated(); $tenantId = $tenant->id; $requestedPackageId = $validated['package_id'] ?? null; unset($validated['package_id']); $tenantPackage = $tenant->tenantPackages() ->with('package') ->where('active', true) ->orderByDesc('purchased_at') ->first(); $package = null; if ($requestedPackageId) { $package = Package::query()->find($requestedPackageId); } if (! $package && $tenantPackage) { $package = $tenantPackage->package ?? Package::query()->find($tenantPackage->package_id); } if (! $package) { throw ValidationException::withMessages([ 'package_id' => __('Aktuell ist kein aktives Paket verfügbar. Bitte buche zunächst ein Paket.'), ]); } $eventData = array_merge($validated, [ 'tenant_id' => $tenantId, 'status' => $validated['status'] ?? 'draft', 'slug' => $this->generateUniqueSlug($validated['name'], $tenantId), ]); if (isset($eventData['event_date'])) { $eventData['date'] = $eventData['event_date']; unset($eventData['event_date']); } $settings = $eventData['settings'] ?? []; foreach (['public_url', 'custom_domain', 'theme_color'] as $key) { if (array_key_exists($key, $eventData)) { $settings[$key] = $eventData[$key]; unset($eventData[$key]); } } if (isset($eventData['features'])) { $settings['features'] = $eventData['features']; unset($eventData['features']); } if ($settings === [] || $settings === null) { unset($eventData['settings']); } else { $eventData['settings'] = $settings; } foreach (['password', 'password_confirmation', 'password_protected', 'logo_image', 'cover_image'] as $unused) { unset($eventData[$unused]); } $allowed = [ 'tenant_id', 'name', 'description', 'date', 'slug', 'location', 'max_participants', 'settings', 'event_type_id', 'is_active', 'join_link_enabled', 'photo_upload_enabled', 'task_checklist_enabled', 'default_locale', 'status', ]; $eventData = Arr::only($eventData, $allowed); $event = DB::transaction(function () use ($tenant, $eventData, $package) { $event = Event::create($eventData); EventPackage::create([ 'event_id' => $event->id, 'package_id' => $package->id, 'purchased_price' => $package->price, 'purchased_at' => now(), 'gallery_expires_at' => $package->gallery_days ? now()->addDays($package->gallery_days) : null, ]); if ($package->isReseller()) { $note = sprintf('Event #%d created (%s)', $event->id, $event->name); if (! $tenant->consumeEventAllowance(1, 'event.create', $note)) { throw new HttpException(402, 'Insufficient credits or package allowance.'); } } return $event; }); $tenant->refresh(); $event->load(['eventType', 'tenant', 'eventPackages.package']); return response()->json([ 'message' => 'Event created successfully', 'data' => new EventResource($event), 'package' => $event->eventPackage ? $event->eventPackage->package->name : 'None', 'remaining_events' => $tenant->activeResellerPackage ? $tenant->activeResellerPackage->remaining_events : 0, ], 201); } public function show(Request $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $event->load([ 'eventType', 'photos' => fn ($query) => $query->with('likes')->latest(), 'tasks', 'tenant' => fn ($query) => $query->select('id', 'name', 'event_credits_balance'), 'eventPackages' => fn ($query) => $query ->with('package') ->orderByDesc('purchased_at') ->orderByDesc('created_at'), ]); return response()->json([ 'data' => new EventResource($event), ]); } public function update(EventStoreRequest $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $validated = $request->validated(); if (isset($validated['event_date'])) { $validated['date'] = $validated['event_date']; unset($validated['event_date']); } if ($validated['name'] !== $event->name) { $validated['slug'] = $this->generateUniqueSlug($validated['name'], $tenantId, $event->id); } foreach (['password', 'password_confirmation', 'password_protected'] as $unused) { unset($validated[$unused]); } $event->update($validated); $event->load(['eventType', 'tenant']); return response()->json([ 'message' => 'Event updated successfully', 'data' => new EventResource($event), ]); } public function destroy(Request $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $event->delete(); return response()->json([ 'message' => 'Event deleted successfully', ]); } public function stats(Request $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $totalPhotos = Photo::where('event_id', $event->id)->count(); $featuredPhotos = Photo::where('event_id', $event->id)->where('is_featured', true)->count(); $likes = Photo::where('event_id', $event->id)->sum('likes_count'); $recentUploads = Photo::where('event_id', $event->id) ->where('created_at', '>=', now()->subDays(7)) ->count(); return response()->json([ 'total' => $totalPhotos, 'featured' => $featuredPhotos, 'likes' => (int) $likes, 'recent_uploads' => $recentUploads, 'status' => $event->status, 'is_active' => (bool) $event->is_active, ]); } public function toggle(Request $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $activate = ! (bool) $event->is_active; $event->is_active = $activate; if ($activate) { $event->status = 'published'; } elseif ($event->status === 'published') { $event->status = 'draft'; } $event->save(); $event->refresh()->load(['eventType', 'tenant']); return response()->json([ 'message' => $activate ? 'Event activated' : 'Event deactivated', 'data' => new EventResource($event), 'is_active' => (bool) $event->is_active, ]); } public function createInvite(Request $request, Event $event): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); if ($event->tenant_id !== $tenantId) { return response()->json(['error' => 'Event not found'], 404); } $validated = $request->validate([ 'label' => ['nullable', 'string', 'max:255'], 'expires_at' => ['nullable', 'date', 'after:now'], 'usage_limit' => ['nullable', 'integer', 'min:1'], ]); $attributes = array_filter([ 'label' => $validated['label'] ?? null, 'expires_at' => $validated['expires_at'] ?? null, 'usage_limit' => $validated['usage_limit'] ?? null, 'created_by' => $request->user()?->id, ], fn ($value) => ! is_null($value)); $joinToken = $this->joinTokenService->createToken($event, $attributes); return response()->json([ 'link' => url("/e/{$event->slug}?invite={$joinToken->token}"), 'token' => $joinToken->token, 'token_url' => url('/e/'.$joinToken->token), 'join_token' => new EventJoinTokenResource($joinToken), ]); } public function bulkUpdateStatus(Request $request): JsonResponse { $tenantId = $request->attributes->get('tenant_id'); $validated = $request->validate([ 'event_ids' => 'required|array', 'event_ids.*' => 'exists:events,id', 'status' => 'required|in:draft,published,archived', ]); $updatedCount = Event::whereIn('id', $validated['event_ids']) ->where('tenant_id', $tenantId) ->update(['status' => $validated['status']]); return response()->json([ 'message' => "{$updatedCount} events updated successfully", 'updated_count' => $updatedCount, ]); } private function generateUniqueSlug(string $name, int $tenantId, ?int $excludeId = null): string { $slug = Str::slug($name); $originalSlug = $slug; $counter = 1; while (Event::where('slug', $slug) ->where('tenant_id', $tenantId) ->when($excludeId, fn ($query) => $query->where('id', '!=', $excludeId)) ->exists()) { $slug = $originalSlug.'-'.$counter; $counter++; } return $slug; } public function search(Request $request): AnonymousResourceCollection { $tenantId = $request->attributes->get('tenant_id'); $query = $request->get('q', ''); if (strlen($query) < 2) { return EventResource::collection(collect([])); } $events = Event::where('tenant_id', $tenantId) ->where(function ($q) use ($query) { $q->where('name', 'like', "%{$query}%") ->orWhere('description', 'like', "%{$query}%"); }) ->with('eventType') ->limit(10) ->get(); return EventResource::collection($events); } }