the MailMessage builders to use view(). This keeps the existing copy/labels but aligns the look with resources/views/ emails/partials/layout.blade.php. I also switched the customer add‑on receipt notification to reuse the existing branded view and added missing translations for the upload pipeline alert.
200 lines
6.1 KiB
PHP
200 lines
6.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Tenant;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\Tenant\EventMemberInviteRequest;
|
|
use App\Http\Resources\Tenant\EventMemberResource;
|
|
use App\Models\Event;
|
|
use App\Models\EventMember;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class EventMemberController extends Controller
|
|
{
|
|
public function index(Request $request, Event $event): JsonResponse
|
|
{
|
|
$this->assertEventTenant($request, $event);
|
|
|
|
/** @var LengthAwarePaginator $members */
|
|
$members = $event->members()
|
|
->orderByDesc('created_at')
|
|
->paginate($request->integer('per_page', 25));
|
|
|
|
return EventMemberResource::collection($members)->response();
|
|
}
|
|
|
|
public function store(EventMemberInviteRequest $request, Event $event): JsonResponse
|
|
{
|
|
$this->assertEventTenant($request, $event);
|
|
|
|
$data = $request->validated();
|
|
$tenant = $this->resolveTenantFromRequest($request);
|
|
|
|
if (! $tenant) {
|
|
throw ValidationException::withMessages([
|
|
'tenant_id' => __('Tenant context missing for invitation.'),
|
|
]);
|
|
}
|
|
|
|
$email = Str::lower($data['email']);
|
|
$role = $data['role'];
|
|
$name = $data['name'] ?? null;
|
|
|
|
$user = $this->findOrCreateUser($tenant, $email, $name, $role);
|
|
|
|
if ((int) $user->tenant_id !== (int) $tenant->id) {
|
|
throw ValidationException::withMessages([
|
|
'email' => __('Dieser Benutzer gehört bereits zu einem anderen Mandanten.'),
|
|
]);
|
|
}
|
|
|
|
$existing = EventMember::query()
|
|
->where('event_id', $event->id)
|
|
->where('email', $email)
|
|
->first();
|
|
|
|
$member = EventMember::updateOrCreate(
|
|
[
|
|
'event_id' => $event->id,
|
|
'email' => $email,
|
|
],
|
|
[
|
|
'tenant_id' => $tenant->id,
|
|
'user_id' => $user->id,
|
|
'invited_by' => $request->user()?->id,
|
|
'name' => $name ?? $user->name ?? $email,
|
|
'role' => $role,
|
|
'status' => $user->email_verified_at ? 'active' : 'invited',
|
|
'invite_token' => $this->ensureInviteToken($event, $email),
|
|
'invited_at' => $existing?->invited_at ?? now(),
|
|
'joined_at' => $user->email_verified_at ? ($existing?->joined_at ?? now()) : null,
|
|
'permissions' => $this->defaultPermissionsForRole($role),
|
|
]
|
|
);
|
|
|
|
$member->refresh();
|
|
|
|
if (! $user->hasVerifiedEmail()) {
|
|
$user->sendEmailVerificationNotification();
|
|
}
|
|
|
|
return (new EventMemberResource($member))->response()->setStatusCode(201);
|
|
}
|
|
|
|
public function destroy(Request $request, Event $event, EventMember $member): JsonResponse
|
|
{
|
|
$this->assertEventTenant($request, $event);
|
|
|
|
if ((int) $member->event_id !== (int) $event->id) {
|
|
throw ValidationException::withMessages([
|
|
'member_id' => __('Dieses Mitglied gehört nicht zu diesem Event.'),
|
|
]);
|
|
}
|
|
|
|
$member->delete();
|
|
|
|
return response()->json(['ok' => true]);
|
|
}
|
|
|
|
private function assertEventTenant(Request $request, Event $event): void
|
|
{
|
|
$tenantId = $request->attributes->get('tenant_id');
|
|
if ($tenantId === null || (int) $event->tenant_id !== (int) $tenantId) {
|
|
abort(403, 'Event belongs to a different tenant.');
|
|
}
|
|
}
|
|
|
|
private function resolveTenantFromRequest(Request $request): ?Tenant
|
|
{
|
|
$tenant = $request->attributes->get('tenant');
|
|
|
|
if ($tenant instanceof Tenant) {
|
|
return $tenant;
|
|
}
|
|
|
|
$tenantId = $request->attributes->get('tenant_id');
|
|
|
|
return $tenantId ? Tenant::query()->find($tenantId) : null;
|
|
}
|
|
|
|
private function findOrCreateUser(Tenant $tenant, string $email, ?string $name, string $role): User
|
|
{
|
|
$user = User::query()->where('email', $email)->first();
|
|
|
|
if (! $user) {
|
|
$user = new User;
|
|
$user->email = $email;
|
|
$user->password = Hash::make(Str::random(32));
|
|
}
|
|
|
|
if ($user->tenant_id && (int) $user->tenant_id !== (int) $tenant->id && $user->role !== 'super_admin') {
|
|
throw ValidationException::withMessages([
|
|
'email' => __('Dieser Benutzer ist einem anderen Mandanten zugeordnet.'),
|
|
]);
|
|
}
|
|
|
|
$user->tenant_id = $tenant->id;
|
|
|
|
if ($role === 'tenant_admin' && $user->role !== 'super_admin') {
|
|
$user->role = 'tenant_admin';
|
|
} elseif (! in_array($user->role, ['tenant_admin', 'super_admin'], true)) {
|
|
$user->role = 'member';
|
|
}
|
|
|
|
if ($name) {
|
|
$user->name = $name;
|
|
[$first, $last] = $this->splitName($name);
|
|
$user->first_name = $first;
|
|
$user->last_name = $last;
|
|
}
|
|
|
|
if (! $user->exists) {
|
|
$user->email_verified_at = null;
|
|
}
|
|
|
|
$user->save();
|
|
|
|
return $user;
|
|
}
|
|
|
|
private function splitName(string $name): array
|
|
{
|
|
$parts = preg_split('/\s+/', trim($name));
|
|
$first = Arr::first($parts) ?? $name;
|
|
$last = count($parts) > 1 ? Arr::last($parts) : null;
|
|
|
|
return [$first, $last];
|
|
}
|
|
|
|
private function ensureInviteToken(Event $event, string $email): string
|
|
{
|
|
$existing = EventMember::query()
|
|
->where('event_id', $event->id)
|
|
->where('email', $email)
|
|
->value('invite_token');
|
|
|
|
return $existing ?? Str::random(40);
|
|
}
|
|
|
|
private function defaultPermissionsForRole(string $role): array
|
|
{
|
|
if ($role === 'tenant_admin') {
|
|
return ['*'];
|
|
}
|
|
|
|
return [
|
|
'photos:moderate',
|
|
'tasks:manage',
|
|
'join-tokens:manage',
|
|
];
|
|
}
|
|
}
|