validate([ 'email' => ['required','email'], 'password' => ['required','string'], ]); if (! Auth::attempt($creds)) { return response()->json(['error' => ['code' => 'invalid_credentials']], 401); } /** @var User $user */ $user = Auth::user(); // naive token (cache-based), expires in 8 hours $token = Str::random(80); Cache::put('api_token:'.$token, $user->id, now()->addHours(8)); return response()->json([ 'token' => $token, 'user' => [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, 'tenant_id' => $user->tenant_id ?? null, ], ]); } public function me(Request $request) { $u = Auth::user(); return response()->json([ 'id' => $u->id, 'name' => $u->name, 'email' => $u->email, 'tenant_id' => $u->tenant_id ?? null, ]); } public function events() { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $q = Event::query(); if ($tenantId) { $q->where('tenant_id', $tenantId); } return response()->json(['data' => $q->orderByDesc('created_at')->limit(100)->get(['id','name','slug','date','is_active'])]); } public function showEvent(int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } return response()->json($ev->only(['id','name','slug','date','is_active','default_locale'])); } public function storeEvent(Request $request) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $data = $request->validate([ 'name' => ['required','string','max:255'], 'slug' => ['required','string','max:255'], 'date' => ['nullable','date'], 'is_active' => ['boolean'], ]); $ev = new Event(); $ev->tenant_id = $tenantId ?? $ev->tenant_id; $ev->name = ['de' => $data['name'], 'en' => $data['name']]; $ev->slug = $data['slug']; $ev->date = $data['date'] ?? null; $ev->is_active = (bool)($data['is_active'] ?? true); $ev->default_locale = 'de'; $ev->save(); return response()->json(['id' => $ev->id]); } public function updateEvent(Request $request, int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } $data = $request->validate([ 'name' => ['nullable','string','max:255'], 'slug' => ['nullable','string','max:255'], 'date' => ['nullable','date'], 'is_active' => ['nullable','boolean'], ]); if (isset($data['name'])) $ev->name = ['de' => $data['name'], 'en' => $data['name']]; if (isset($data['slug'])) $ev->slug = $data['slug']; if (array_key_exists('date', $data)) $ev->date = $data['date']; if (array_key_exists('is_active', $data)) $ev->is_active = (bool)$data['is_active']; $ev->save(); return response()->json(['ok' => true]); } public function toggleEvent(int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } $ev->is_active = ! (bool) $ev->is_active; $ev->save(); return response()->json(['is_active' => (bool)$ev->is_active]); } public function eventStats(int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } $total = Photo::where('event_id', $id)->count(); $featured = Photo::where('event_id', $id)->where('is_featured', 1)->count(); $likes = Photo::where('event_id', $id)->sum('likes_count'); return response()->json([ 'total' => (int)$total, 'featured' => (int)$featured, 'likes' => (int)$likes, ]); } public function createInvite(Request $request, int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } $joinToken = $this->joinTokenService->createToken($ev, [ 'created_by' => $u?->id, ]); return response()->json([ 'link' => url('/e/'.$joinToken->token), 'legacy_link' => url('/e/'.$ev->slug).'?invite='.$joinToken->token, 'token' => $joinToken->token, ]); } public function eventPhotos(int $id) { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $ev = Event::findOrFail($id); if ($tenantId && $ev->tenant_id !== $tenantId) { return response()->json(['error' => ['code' => 'forbidden']], 403); } $rows = Photo::where('event_id', $id)->orderByDesc('created_at')->limit(100)->get(['id','thumbnail_path','file_path','likes_count','is_featured','created_at']); return response()->json(['data' => $rows]); } public function featurePhoto(int $photoId) { $p = Photo::findOrFail($photoId); $this->authorizePhoto($p); $p->is_featured = 1; $p->save(); return response()->json(['ok' => true]); } public function unfeaturePhoto(int $photoId) { $p = Photo::findOrFail($photoId); $this->authorizePhoto($p); $p->is_featured = 0; $p->save(); return response()->json(['ok' => true]); } public function deletePhoto(int $photoId) { $p = Photo::findOrFail($photoId); $this->authorizePhoto($p); $p->delete(); return response()->json(['ok' => true]); } protected function authorizePhoto(Photo $p): void { $u = Auth::user(); $tenantId = $u->tenant_id ?? null; $event = Event::find($p->event_id); if ($tenantId && $event && $event->tenant_id !== $tenantId) { abort(403); } } }