assertEventTenant($request, $event); TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage'); /** @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); TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage'); $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); TenantMemberPermissions::ensureEventPermission($request, $event, 'members:manage'); 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->isSuperAdmin()) { throw ValidationException::withMessages([ 'email' => __('Dieser Benutzer ist einem anderen Mandanten zugeordnet.'), ]); } $user->tenant_id = $tenant->id; if ($role === 'tenant_admin' && ! $user->isSuperAdmin()) { $user->role = 'tenant_admin'; } elseif (! in_array($user->role, ['tenant_admin', 'super_admin', 'superadmin'], 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', ]; } }