Files
fotospiel-app/app/Http/Controllers/Api/TenantController.php
Codex Agent 79b209de9a Limit-Status im Upload-Flow anzeigen (Warnbanner + Sperrzustände).
Upload-Fehlercodes auswerten und freundliche Dialoge zeigen.
2025-11-01 19:50:17 +01:00

259 lines
8.2 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Models\Event;
use App\Models\Photo;
use App\Models\User;
use App\Services\EventJoinTokenService;
use App\Support\ApiError;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class TenantController extends BaseController
{
public function __construct(private readonly EventJoinTokenService $joinTokenService) {}
public function login(Request $request)
{
$creds = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
if (! Auth::attempt($creds)) {
return ApiError::response(
'invalid_credentials',
'Invalid Credentials',
'The provided credentials are incorrect.',
Response::HTTP_UNAUTHORIZED,
['email' => $creds['email']]
);
}
/** @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 $this->forbiddenResponse('events.show', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
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 $this->forbiddenResponse('events.update', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$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 $this->forbiddenResponse('events.toggle', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$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 $this->forbiddenResponse('events.stats', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$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 $this->forbiddenResponse('events.invite', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$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 $this->forbiddenResponse('events.photos', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$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]);
}
private function forbiddenResponse(string $action, array $meta = []): JsonResponse
{
return ApiError::response(
'forbidden',
'Forbidden',
'You are not allowed to perform this action.',
Response::HTTP_FORBIDDEN,
array_merge(['action' => $action], $meta)
);
}
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);
}
}
}