217 lines
11 KiB
PHP
217 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Tenant;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Resources\Tenant\EventJoinTokenResource;
|
|
use App\Models\Event;
|
|
use App\Models\EventJoinToken;
|
|
use App\Services\EventJoinTokenService;
|
|
use App\Support\TenantMemberPermissions;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Validation\Rule;
|
|
|
|
class EventJoinTokenController extends Controller
|
|
{
|
|
public function __construct(private readonly EventJoinTokenService $joinTokenService) {}
|
|
|
|
public function index(Request $request, Event $event): AnonymousResourceCollection
|
|
{
|
|
$this->authorizeEvent($request, $event, 'join-tokens:manage');
|
|
|
|
$tokens = $event->joinTokens()
|
|
->orderByDesc('created_at')
|
|
->get();
|
|
|
|
return EventJoinTokenResource::collection($tokens);
|
|
}
|
|
|
|
public function store(Request $request, Event $event): JsonResponse
|
|
{
|
|
$this->authorizeEvent($request, $event, 'join-tokens:manage');
|
|
|
|
$validated = $this->validatePayload($request);
|
|
|
|
$token = $this->joinTokenService->createToken($event, array_merge($validated, [
|
|
'created_by' => Auth::id(),
|
|
]));
|
|
|
|
return (new EventJoinTokenResource($token))
|
|
->response()
|
|
->setStatusCode(201);
|
|
}
|
|
|
|
public function update(Request $request, Event $event, EventJoinToken $joinToken): EventJoinTokenResource
|
|
{
|
|
$this->authorizeEvent($request, $event, 'join-tokens:manage');
|
|
|
|
if ($joinToken->event_id !== $event->id) {
|
|
abort(404);
|
|
}
|
|
|
|
$validated = $this->validatePayload($request, true);
|
|
|
|
$payload = [];
|
|
|
|
if (array_key_exists('label', $validated)) {
|
|
$payload['label'] = $validated['label'];
|
|
}
|
|
|
|
if (array_key_exists('expires_at', $validated)) {
|
|
$payload['expires_at'] = $validated['expires_at'];
|
|
}
|
|
|
|
if (array_key_exists('usage_limit', $validated)) {
|
|
$payload['usage_limit'] = $validated['usage_limit'];
|
|
}
|
|
|
|
if (! empty($payload)) {
|
|
$joinToken->fill($payload);
|
|
}
|
|
|
|
if (array_key_exists('metadata', $validated)) {
|
|
$current = is_array($joinToken->metadata) ? $joinToken->metadata : [];
|
|
$incoming = $validated['metadata'];
|
|
|
|
if ($incoming === null) {
|
|
$joinToken->metadata = null;
|
|
} else {
|
|
$joinToken->metadata = array_replace_recursive($current, $incoming);
|
|
}
|
|
}
|
|
|
|
$joinToken->save();
|
|
|
|
return new EventJoinTokenResource($joinToken->fresh());
|
|
}
|
|
|
|
public function destroy(Request $request, Event $event, EventJoinToken $joinToken): EventJoinTokenResource
|
|
{
|
|
$this->authorizeEvent($request, $event, 'join-tokens:manage');
|
|
|
|
if ($joinToken->event_id !== $event->id) {
|
|
abort(404);
|
|
}
|
|
|
|
$reason = $request->input('reason');
|
|
$token = $this->joinTokenService->revoke($joinToken, $reason);
|
|
|
|
return new EventJoinTokenResource($token);
|
|
}
|
|
|
|
private function authorizeEvent(Request $request, Event $event, ?string $permission = null): void
|
|
{
|
|
$tenantId = $request->attributes->get('tenant_id');
|
|
|
|
if ($event->tenant_id !== $tenantId) {
|
|
abort(404, 'Event not found');
|
|
}
|
|
|
|
if ($permission) {
|
|
TenantMemberPermissions::ensureEventPermission($request, $event, $permission);
|
|
}
|
|
}
|
|
|
|
private function validatePayload(Request $request, bool $partial = false): array
|
|
{
|
|
$rules = [
|
|
'label' => [$partial ? 'nullable' : 'sometimes', 'string', 'max:255'],
|
|
'expires_at' => [$partial ? 'nullable' : 'sometimes', 'nullable', 'date', 'after:now'],
|
|
'usage_limit' => [$partial ? 'nullable' : 'sometimes', 'nullable', 'integer', 'min:1'],
|
|
'metadata' => [$partial ? 'nullable' : 'sometimes', 'nullable', 'array'],
|
|
'metadata.layout_customization' => ['nullable', 'array'],
|
|
'metadata.layout_customization.layout_id' => ['nullable', 'string', 'max:100'],
|
|
'metadata.layout_customization.headline' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.subtitle' => ['nullable', 'string', 'max:160'],
|
|
'metadata.layout_customization.description' => ['nullable', 'string', 'max:500'],
|
|
'metadata.layout_customization.badge_label' => ['nullable', 'string', 'max:80'],
|
|
'metadata.layout_customization.instructions_heading' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.link_heading' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.cta_label' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.cta_caption' => ['nullable', 'string', 'max:160'],
|
|
'metadata.layout_customization.link_label' => ['nullable', 'string', 'max:160'],
|
|
'metadata.layout_customization.instructions' => ['nullable', 'array', 'max:6'],
|
|
'metadata.layout_customization.instructions.*' => ['nullable', 'string', 'max:160'],
|
|
'metadata.layout_customization.logo_url' => ['nullable', 'string', 'max:2048'],
|
|
'metadata.layout_customization.logo_data_url' => ['nullable', 'string'],
|
|
'metadata.layout_customization.background_preset' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.accent_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.text_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.background_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.secondary_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.badge_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.background_gradient' => ['nullable', 'array'],
|
|
'metadata.layout_customization.background_gradient.angle' => ['nullable', 'numeric'],
|
|
'metadata.layout_customization.background_gradient.stops' => ['nullable', 'array', 'max:5'],
|
|
'metadata.layout_customization.background_gradient.stops.*' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/'],
|
|
'metadata.layout_customization.mode' => ['nullable', Rule::in(['standard', 'advanced'])],
|
|
'metadata.layout_customization.elements' => ['nullable', 'array', 'max:50'],
|
|
'metadata.layout_customization.elements.*.id' => ['required_with:metadata.layout_customization.elements', 'string', 'max:120'],
|
|
'metadata.layout_customization.elements.*.type' => ['required_with:metadata.layout_customization.elements', Rule::in(['qr', 'headline', 'subtitle', 'description', 'link', 'badge', 'logo', 'cta', 'text'])],
|
|
'metadata.layout_customization.elements.*.x' => ['nullable', 'numeric', 'min:0'],
|
|
'metadata.layout_customization.elements.*.y' => ['nullable', 'numeric', 'min:0'],
|
|
'metadata.layout_customization.elements.*.width' => ['nullable', 'numeric', 'min:40'],
|
|
'metadata.layout_customization.elements.*.height' => ['nullable', 'numeric', 'min:40'],
|
|
'metadata.layout_customization.elements.*.rotation' => ['nullable', 'numeric'],
|
|
'metadata.layout_customization.elements.*.font_size' => ['nullable', 'numeric', 'min:8', 'max:160'],
|
|
'metadata.layout_customization.elements.*.align' => ['nullable', Rule::in(['left', 'center', 'right'])],
|
|
'metadata.layout_customization.elements.*.content' => ['nullable', 'string', 'max:400'],
|
|
'metadata.layout_customization.elements.*.font_family' => ['nullable', 'string', 'max:120'],
|
|
'metadata.layout_customization.elements.*.letter_spacing' => ['nullable', 'numeric', 'min:-5', 'max:20'],
|
|
'metadata.layout_customization.elements.*.line_height' => ['nullable', 'numeric', 'min:0.5', 'max:3'],
|
|
'metadata.layout_customization.elements.*.fill' => ['nullable', 'string', 'max:20'],
|
|
'metadata.layout_customization.elements.*.locked' => ['nullable', 'boolean'],
|
|
'metadata.layout_customization.elements.*.initial' => ['nullable', 'boolean'],
|
|
];
|
|
|
|
$validated = $request->validate($rules);
|
|
|
|
if (isset($validated['metadata']['layout_customization']['instructions'])) {
|
|
$validated['metadata']['layout_customization']['instructions'] = array_values(array_filter(
|
|
$validated['metadata']['layout_customization']['instructions'],
|
|
fn ($value) => is_string($value) && trim($value) !== ''
|
|
));
|
|
}
|
|
|
|
if (isset($validated['metadata']['layout_customization']['logo_data_url'])
|
|
&& ! is_string($validated['metadata']['layout_customization']['logo_data_url'])) {
|
|
unset($validated['metadata']['layout_customization']['logo_data_url']);
|
|
}
|
|
|
|
if (isset($validated['metadata']['layout_customization']['elements'])
|
|
&& is_array($validated['metadata']['layout_customization']['elements'])) {
|
|
$validated['metadata']['layout_customization']['elements'] = array_values(array_filter(array_map(
|
|
static function ($element) {
|
|
if (! is_array($element) || empty($element['id']) || empty($element['type'])) {
|
|
return null;
|
|
}
|
|
|
|
return array_filter([
|
|
'id' => (string) $element['id'],
|
|
'type' => (string) $element['type'],
|
|
'x' => array_key_exists('x', $element) ? (float) $element['x'] : null,
|
|
'y' => array_key_exists('y', $element) ? (float) $element['y'] : null,
|
|
'width' => array_key_exists('width', $element) ? (float) $element['width'] : null,
|
|
'height' => array_key_exists('height', $element) ? (float) $element['height'] : null,
|
|
'rotation' => array_key_exists('rotation', $element) ? (float) $element['rotation'] : null,
|
|
'font_size' => array_key_exists('font_size', $element) ? (float) $element['font_size'] : null,
|
|
'align' => $element['align'] ?? null,
|
|
'content' => array_key_exists('content', $element) ? (string) $element['content'] : null,
|
|
'font_family' => $element['font_family'] ?? null,
|
|
'letter_spacing' => array_key_exists('letter_spacing', $element) ? (float) $element['letter_spacing'] : null,
|
|
'line_height' => array_key_exists('line_height', $element) ? (float) $element['line_height'] : null,
|
|
'fill' => $element['fill'] ?? null,
|
|
'locked' => array_key_exists('locked', $element) ? (bool) $element['locked'] : null,
|
|
], static fn ($value) => $value !== null && $value !== '');
|
|
},
|
|
$validated['metadata']['layout_customization']['elements']
|
|
)));
|
|
}
|
|
|
|
return $validated;
|
|
}
|
|
}
|