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']) ->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; $packageId = $validated['package_id'] ?? 1; // Default to Free package ID 1 unset($validated['package_id']); $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, $packageId) { $event = Event::create($eventData); // Create EventPackage and PackagePurchase for Free package $package = \App\Models\Package::findOrFail($packageId); $eventPackage = \App\Models\EventPackage::create([ 'event_id' => $event->id, 'package_id' => $packageId, 'price' => $package->price, 'purchased_at' => now(), ]); \App\Models\PackagePurchase::create([ 'tenant_id' => $tenant->id, 'event_id' => $event->id, 'package_id' => $packageId, 'provider_id' => 'free', 'price' => $package->price, 'type' => 'endcustomer_event', 'metadata' => json_encode(['note' => 'Free package assigned on event creation']), ]); if ($tenant->activeResellerPackage) { $tenant->incrementUsedEvents(); } return $event; }); $tenant->refresh(); $event->load(['eventType', 'tenant', 'eventPackage.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'), ]); 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); } $token = (string) Str::uuid(); $link = url("/e/{$event->slug}?invite={$token}"); return response()->json([ 'link' => $link, 'token' => $token, ]); } 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); } }