Limit-Status im Upload-Flow anzeigen (Warnbanner + Sperrzustände).

Upload-Fehlercodes auswerten und freundliche Dialoge zeigen.
This commit is contained in:
Codex Agent
2025-11-01 19:50:17 +01:00
parent 2c14493604
commit 79b209de9a
55 changed files with 3348 additions and 462 deletions

View File

@@ -6,32 +6,40 @@ 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 __construct(private readonly EventJoinTokenService $joinTokenService) {}
public function login(Request $request)
{
$creds = $request->validate([
'email' => ['required','email'],
'password' => ['required','string'],
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
if (! Auth::attempt($creds)) {
return response()->json(['error' => ['code' => 'invalid_credentials']], 401);
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' => [
@@ -46,6 +54,7 @@ class TenantController extends BaseController
public function me(Request $request)
{
$u = Auth::user();
return response()->json([
'id' => $u->id,
'name' => $u->name,
@@ -62,7 +71,8 @@ class TenantController extends BaseController
if ($tenantId) {
$q->where('tenant_id', $tenantId);
}
return response()->json(['data' => $q->orderByDesc('created_at')->limit(100)->get(['id','name','slug','date','is_active'])]);
return response()->json(['data' => $q->orderByDesc('created_at')->limit(100)->get(['id', 'name', 'slug', 'date', 'is_active'])]);
}
public function showEvent(int $id)
@@ -71,9 +81,10 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
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']));
return response()->json($ev->only(['id', 'name', 'slug', 'date', 'is_active', 'default_locale']));
}
public function storeEvent(Request $request)
@@ -81,19 +92,20 @@ class TenantController extends BaseController
$u = Auth::user();
$tenantId = $u->tenant_id ?? null;
$data = $request->validate([
'name' => ['required','string','max:255'],
'slug' => ['required','string','max:255'],
'date' => ['nullable','date'],
'name' => ['required', 'string', 'max:255'],
'slug' => ['required', 'string', 'max:255'],
'date' => ['nullable', 'date'],
'is_active' => ['boolean'],
]);
$ev = new Event();
$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->is_active = (bool) ($data['is_active'] ?? true);
$ev->default_locale = 'de';
$ev->save();
return response()->json(['id' => $ev->id]);
}
@@ -103,19 +115,28 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
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'],
'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'];
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]);
}
@@ -125,11 +146,12 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
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]);
return response()->json(['is_active' => (bool) $ev->is_active]);
}
public function eventStats(int $id)
@@ -138,15 +160,16 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
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,
'total' => (int) $total,
'featured' => (int) $featured,
'likes' => (int) $likes,
]);
}
@@ -156,7 +179,7 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
return $this->forbiddenResponse('events.invite', ['event_id' => $ev->id, 'tenant_id' => $tenantId]);
}
$joinToken = $this->joinTokenService->createToken($ev, [
@@ -176,9 +199,10 @@ class TenantController extends BaseController
$tenantId = $u->tenant_id ?? null;
$ev = Event::findOrFail($id);
if ($tenantId && $ev->tenant_id !== $tenantId) {
return response()->json(['error' => ['code' => 'forbidden']], 403);
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']);
$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]);
}
@@ -186,7 +210,9 @@ class TenantController extends BaseController
{
$p = Photo::findOrFail($photoId);
$this->authorizePhoto($p);
$p->is_featured = 1; $p->save();
$p->is_featured = 1;
$p->save();
return response()->json(['ok' => true]);
}
@@ -194,7 +220,9 @@ class TenantController extends BaseController
{
$p = Photo::findOrFail($photoId);
$this->authorizePhoto($p);
$p->is_featured = 0; $p->save();
$p->is_featured = 0;
$p->save();
return response()->json(['ok' => true]);
}
@@ -203,9 +231,21 @@ class TenantController extends BaseController
$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();