Deploy Ladill Meet / deploy (push) Successful in 42s
Enforce invite-only room access, validate invite emails on create, assign attendee role for listener invitations, and add invite UI on the room show page. Redesign meetings, rooms, conferences, and webinars indexes to match the Events hero, stats cards, and icon list layout. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
3.6 KiB
PHP
123 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Meet;
|
|
|
|
use App\Models\Invitation;
|
|
use App\Models\Room;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Str;
|
|
|
|
class InvitationService
|
|
{
|
|
public function __construct(
|
|
protected MeetNotificationService $notifications,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $invites
|
|
*/
|
|
public function inviteToRoom(Room $room, array $invites, User $host): void
|
|
{
|
|
$defaultRole = $room->isSpace() ? 'attendee' : 'participant';
|
|
|
|
$normalized = collect($invites)->map(function ($invite) use ($defaultRole) {
|
|
$email = trim((string) ($invite['email'] ?? ''));
|
|
if ($email === '') {
|
|
return null;
|
|
}
|
|
|
|
$user = User::where('email', $email)->first();
|
|
|
|
return [
|
|
'email' => $email,
|
|
'name' => $invite['name'] ?? $invite['display_name'] ?? $user?->name,
|
|
'role' => $invite['role'] ?? $defaultRole,
|
|
'user_ref' => $invite['user_ref'] ?? $user?->ownerRef(),
|
|
];
|
|
})->filter()->unique('email')->values()->all();
|
|
|
|
$this->notifications->sendInvitations($room, $normalized, $host);
|
|
}
|
|
|
|
public function resend(Invitation $invitation, User $host): Invitation
|
|
{
|
|
$this->notifications->sendInvitationEmail($invitation->room, $invitation, $host);
|
|
$invitation->update(['sent_at' => now()]);
|
|
|
|
return $invitation->fresh();
|
|
}
|
|
|
|
public function respond(Invitation $invitation, string $status, ?string $token = null): Invitation
|
|
{
|
|
abort_unless(in_array($status, ['accepted', 'declined'], true), 422);
|
|
if ($token !== null) {
|
|
abort_unless(hash_equals((string) $invitation->rsvp_token, $token), 403);
|
|
}
|
|
|
|
$invitation->update([
|
|
'status' => $status,
|
|
'responded_at' => now(),
|
|
]);
|
|
|
|
return $invitation->fresh();
|
|
}
|
|
|
|
public function markAcceptedOnJoin(Room $room, ?User $user, ?string $email): void
|
|
{
|
|
$query = $room->invitations()->where('status', 'pending');
|
|
|
|
if ($user) {
|
|
$query->where(function ($q) use ($user, $email) {
|
|
$q->where('user_ref', $user->ownerRef());
|
|
if ($email) {
|
|
$q->orWhere('email', $email);
|
|
}
|
|
});
|
|
} elseif ($email) {
|
|
$query->where('email', $email);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
$query->update(['status' => 'accepted', 'responded_at' => now()]);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function parseCsv(string $content): array
|
|
{
|
|
$lines = preg_split('/\r\n|\r|\n/', trim($content)) ?: [];
|
|
$invites = [];
|
|
|
|
foreach ($lines as $index => $line) {
|
|
$line = trim($line);
|
|
if ($line === '' || ($index === 0 && str_contains(strtolower($line), 'email'))) {
|
|
continue;
|
|
}
|
|
|
|
$parts = str_getcsv($line);
|
|
$email = trim((string) ($parts[0] ?? ''));
|
|
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
continue;
|
|
}
|
|
|
|
$invites[] = [
|
|
'email' => $email,
|
|
'name' => trim((string) ($parts[1] ?? '')) ?: null,
|
|
'role' => trim((string) ($parts[2] ?? '')) ?: 'participant',
|
|
];
|
|
}
|
|
|
|
return $invites;
|
|
}
|
|
|
|
public function bulkFromCsv(Room $room, string $csvContent, User $host): int
|
|
{
|
|
$invites = $this->parseCsv($csvContent);
|
|
$this->inviteToRoom($room, $invites, $host);
|
|
|
|
return count($invites);
|
|
}
|
|
}
|