APP als Hybrid Version - Anbindung an API
This commit is contained in:
parent
d054732bf5
commit
c1514999be
46 changed files with 3418 additions and 196 deletions
54
backend/app/Http/Controllers/Api/AuthController.php
Normal file
54
backend/app/Http/Controllers/Api/AuthController.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\LoginRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(LoginRequest $request): JsonResponse
|
||||
{
|
||||
$credentials = $request->validated();
|
||||
$user = User::query()
|
||||
->where('email', $credentials['email'])
|
||||
->first();
|
||||
|
||||
if (! $user || ! Hash::check($credentials['password'], $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => __('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
$token = $user->createToken('quasar-app')->accessToken;
|
||||
|
||||
return response()->json([
|
||||
'token' => $token,
|
||||
'tokenType' => 'Bearer',
|
||||
'user' => [
|
||||
'id' => (string) $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'avatar' => $this->avatarFor($user),
|
||||
'mode' => 'remote',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()?->token()?->revoke();
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
private function avatarFor(User $user): string
|
||||
{
|
||||
return substr(strtoupper($user->initials()), 0, 3);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ class EventController extends Controller
|
|||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$query = $request->user()->events()->orderBy('date');
|
||||
$query = $request->user()->events()->with('media')->orderBy('date');
|
||||
|
||||
// Delta sync: only events updated since a given timestamp
|
||||
if ($request->has('since')) {
|
||||
|
|
@ -50,7 +50,7 @@ class EventController extends Controller
|
|||
'note' => $request->validated('note'),
|
||||
]);
|
||||
|
||||
return (new EventResource($event))
|
||||
return (new EventResource($event->load('media')))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ class EventController extends Controller
|
|||
->where('client_id', $clientId)
|
||||
->firstOrFail();
|
||||
|
||||
return new EventResource($event);
|
||||
return new EventResource($event->load('media'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,7 +103,7 @@ class EventController extends Controller
|
|||
|
||||
$event->update($data);
|
||||
|
||||
return new EventResource($event->fresh());
|
||||
return new EventResource($event->fresh()->load('media'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
138
backend/app/Http/Controllers/Api/EventMediaController.php
Normal file
138
backend/app/Http/Controllers/Api/EventMediaController.php
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreEventMediaRequest;
|
||||
use App\Http\Resources\EventMediaResource;
|
||||
use App\Models\EventMedia;
|
||||
use App\Services\EventMediaImageProcessor;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class EventMediaController extends Controller
|
||||
{
|
||||
public function index(Request $request, string $clientId): JsonResponse
|
||||
{
|
||||
$event = $request->user()->events()
|
||||
->where('client_id', $clientId)
|
||||
->firstOrFail();
|
||||
|
||||
return response()->json([
|
||||
'data' => EventMediaResource::collection(
|
||||
$event->media()->orderBy('created_at')->get()
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(
|
||||
StoreEventMediaRequest $request,
|
||||
EventMediaImageProcessor $processor,
|
||||
string $clientId
|
||||
): JsonResponse {
|
||||
$event = $request->user()->events()
|
||||
->where('client_id', $clientId)
|
||||
->firstOrFail();
|
||||
|
||||
$collection = $request->validated('collection') ?? 'gallery';
|
||||
$processed = $processor->process($request->file('file'));
|
||||
$uuid = (string) str()->uuid();
|
||||
$directory = "event-media/{$request->user()->id}/{$event->client_id}";
|
||||
$path = "{$directory}/{$uuid}.jpg";
|
||||
$previewPath = "{$directory}/{$uuid}_preview.jpg";
|
||||
$thumbnailPath = "{$directory}/{$uuid}_thumb.jpg";
|
||||
|
||||
Storage::disk('local')->put($path, $processed['original']);
|
||||
Storage::disk('local')->put($previewPath, $processed['preview']);
|
||||
Storage::disk('local')->put($thumbnailPath, $processed['thumbnail']);
|
||||
|
||||
if ($collection === 'key_image') {
|
||||
$this->deleteExistingKeyImages($event->media()->where('collection', 'key_image')->get());
|
||||
}
|
||||
|
||||
$media = $event->media()->create([
|
||||
'uuid' => $uuid,
|
||||
'user_id' => $request->user()->id,
|
||||
'collection' => $collection,
|
||||
'name' => $request->file('file')->getClientOriginalName(),
|
||||
'mime_type' => $processed['mime_type'],
|
||||
'disk' => 'local',
|
||||
'path' => $path,
|
||||
'thumbnail_path' => $thumbnailPath,
|
||||
'preview_path' => $previewPath,
|
||||
'size' => $processed['size'],
|
||||
'width' => $processed['width'],
|
||||
'height' => $processed['height'],
|
||||
'thumbnail_width' => $processed['thumbnail_width'],
|
||||
'thumbnail_height' => $processed['thumbnail_height'],
|
||||
'preview_width' => $processed['preview_width'],
|
||||
'preview_height' => $processed['preview_height'],
|
||||
]);
|
||||
|
||||
if ($collection === 'key_image') {
|
||||
$event->update(['image' => "/event-media/{$media->id}/thumb"]);
|
||||
}
|
||||
|
||||
return (new EventMediaResource($media))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
|
||||
public function show(Request $request, EventMedia $media, string $variant): Response
|
||||
{
|
||||
abort_unless(in_array($variant, ['thumb', 'preview', 'original'], true), 404);
|
||||
abort_unless((int) $media->user_id === (int) $request->user()->id, 404);
|
||||
|
||||
$path = match ($variant) {
|
||||
'thumb' => $media->thumbnail_path,
|
||||
'preview' => $media->preview_path ?: $media->thumbnail_path,
|
||||
default => $media->path,
|
||||
};
|
||||
|
||||
abort_unless(is_string($path) && $path !== '', 404);
|
||||
abort_unless(Storage::disk($media->disk)->exists($path), 404);
|
||||
|
||||
return response(Storage::disk($media->disk)->get($path), 200, [
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'Cache-Control' => 'private, max-age=604800',
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, string $clientId, EventMedia $media): JsonResponse
|
||||
{
|
||||
$event = $request->user()->events()
|
||||
->where('client_id', $clientId)
|
||||
->firstOrFail();
|
||||
|
||||
abort_unless((int) $media->event_id === (int) $event->id, 404);
|
||||
|
||||
$wasKeyImage = $media->collection === 'key_image';
|
||||
$this->deleteMediaFiles($media);
|
||||
$media->delete();
|
||||
|
||||
if ($wasKeyImage) {
|
||||
$event->update(['image' => null]);
|
||||
}
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
private function deleteExistingKeyImages(iterable $mediaItems): void
|
||||
{
|
||||
foreach ($mediaItems as $media) {
|
||||
$this->deleteMediaFiles($media);
|
||||
$media->delete();
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteMediaFiles(EventMedia $media): void
|
||||
{
|
||||
Storage::disk($media->disk)->delete(array_filter([
|
||||
$media->path,
|
||||
$media->preview_path,
|
||||
$media->thumbnail_path,
|
||||
]));
|
||||
}
|
||||
}
|
||||
32
backend/app/Http/Controllers/Api/SettingsController.php
Normal file
32
backend/app/Http/Controllers/Api/SettingsController.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\UpdateSettingsRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
public function show(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => $request->user()->settings?->settings ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateSettingsRequest $request): JsonResponse
|
||||
{
|
||||
$settings = $request->validated('settings');
|
||||
|
||||
$userSettings = $request->user()->settings()->updateOrCreate(
|
||||
[],
|
||||
['settings' => $settings],
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $userSettings->settings,
|
||||
]);
|
||||
}
|
||||
}
|
||||
97
backend/app/Http/Controllers/Api/SettingsMediaController.php
Normal file
97
backend/app/Http/Controllers/Api/SettingsMediaController.php
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreSettingsMediaRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use RuntimeException;
|
||||
|
||||
class SettingsMediaController extends Controller
|
||||
{
|
||||
private const BACKGROUND_MAX_SIDE = 1600;
|
||||
|
||||
private const BACKGROUND_QUALITY = 86;
|
||||
|
||||
public function store(StoreSettingsMediaRequest $request): JsonResponse
|
||||
{
|
||||
$processed = $this->processBackground($request->file('file'));
|
||||
$path = $this->backgroundPath((int) $request->user()->id);
|
||||
|
||||
Storage::disk('local')->put($path, $processed['contents']);
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'url' => '/settings/media/background?v='.time(),
|
||||
'width' => $processed['width'],
|
||||
'height' => $processed['height'],
|
||||
'mimeType' => 'image/jpeg',
|
||||
'size' => strlen($processed['contents']),
|
||||
],
|
||||
], 201);
|
||||
}
|
||||
|
||||
public function show(Request $request): Response
|
||||
{
|
||||
$path = $this->backgroundPath((int) $request->user()->id);
|
||||
|
||||
abort_unless(Storage::disk('local')->exists($path), 404);
|
||||
|
||||
return response(Storage::disk('local')->get($path), 200, [
|
||||
'Content-Type' => 'image/jpeg',
|
||||
'Cache-Control' => 'private, max-age=604800',
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request): JsonResponse
|
||||
{
|
||||
Storage::disk('local')->delete($this->backgroundPath((int) $request->user()->id));
|
||||
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
private function backgroundPath(int $userId): string
|
||||
{
|
||||
return "settings-media/{$userId}/background.jpg";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{contents: string, width: int, height: int}
|
||||
*/
|
||||
private function processBackground(UploadedFile $file): array
|
||||
{
|
||||
$source = imagecreatefromstring((string) file_get_contents($file->getRealPath()));
|
||||
|
||||
if (! $source) {
|
||||
throw new RuntimeException('The uploaded background image could not be processed.');
|
||||
}
|
||||
|
||||
$sourceWidth = imagesx($source);
|
||||
$sourceHeight = imagesy($source);
|
||||
$scale = min(1, self::BACKGROUND_MAX_SIDE / max($sourceWidth, $sourceHeight));
|
||||
$width = max(1, (int) round($sourceWidth * $scale));
|
||||
$height = max(1, (int) round($sourceHeight * $scale));
|
||||
|
||||
$canvas = imagecreatetruecolor($width, $height);
|
||||
$background = imagecolorallocate($canvas, 255, 255, 255);
|
||||
imagefilledrectangle($canvas, 0, 0, $width, $height, $background);
|
||||
imagecopyresampled($canvas, $source, 0, 0, 0, 0, $width, $height, $sourceWidth, $sourceHeight);
|
||||
|
||||
imagedestroy($source);
|
||||
|
||||
ob_start();
|
||||
imagejpeg($canvas, null, self::BACKGROUND_QUALITY);
|
||||
$contents = (string) ob_get_clean();
|
||||
imagedestroy($canvas);
|
||||
|
||||
return [
|
||||
'contents' => $contents,
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
];
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue