Promote Events speakers from attendee to panelist on rejoin.
Deploy Ladill Meet / deploy (push) Successful in 1m7s
Deploy Ladill Meet / deploy (push) Successful in 1m7s
Upgrade existing participant roles when a verified speaker token is present, and sync panelist access while already in the room so speaker-panel placement and publish rights apply. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -63,6 +63,8 @@ class MeetingRoomController extends Controller
|
||||
|
||||
abort_unless($participant->status === 'joined', 403, "You are not in this {$kind}.");
|
||||
|
||||
$participant = $this->sessions->syncEventsSpeakerRole($request, $participant);
|
||||
|
||||
$token = $this->media->isConfigured()
|
||||
? $this->sessions->generateMediaToken($participant)
|
||||
: '';
|
||||
@@ -520,8 +522,11 @@ class MeetingRoomController extends Controller
|
||||
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||
abort_unless($participantUuid, 403);
|
||||
|
||||
return Participant::where('uuid', $participantUuid)
|
||||
->where('session_id', $session->id)
|
||||
->firstOrFail();
|
||||
return $this->sessions->syncEventsSpeakerRole(
|
||||
$request,
|
||||
Participant::where('uuid', $participantUuid)
|
||||
->where('session_id', $session->id)
|
||||
->firstOrFail()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Room;
|
||||
use App\Models\Session;
|
||||
use App\Models\User;
|
||||
use App\Services\Meet\Media\MediaProviderInterface;
|
||||
use App\Support\EventsRegistrationSession;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -108,15 +109,13 @@ class SessionService
|
||||
$updates['joined_at'] = $existing->joined_at ?? now();
|
||||
}
|
||||
|
||||
if (in_array($role, ['host', 'co_host'], true)) {
|
||||
$updates['role'] = $role;
|
||||
} elseif ($existing->role === 'guest' && $role !== 'guest') {
|
||||
if ($this->shouldPromoteParticipantRole($existing->role, $role)) {
|
||||
$updates['role'] = $role;
|
||||
}
|
||||
|
||||
$existing->update($updates);
|
||||
|
||||
return $existing;
|
||||
return $existing->fresh();
|
||||
}
|
||||
|
||||
$joinedAt = $status === 'joined' ? now() : null;
|
||||
@@ -196,6 +195,43 @@ class SessionService
|
||||
return $participant->fresh();
|
||||
}
|
||||
|
||||
public function syncEventsSpeakerRole(Request $request, Participant $participant): Participant
|
||||
{
|
||||
$participant->loadMissing('session.room');
|
||||
$room = $participant->session->room;
|
||||
|
||||
if (! $room->isEventsLinked() || ! EventsRegistrationSession::isSpeakerVerified($request, $room)) {
|
||||
return $participant;
|
||||
}
|
||||
|
||||
return $this->promoteParticipantRole($participant, 'panelist');
|
||||
}
|
||||
|
||||
private function promoteParticipantRole(Participant $participant, string $role): Participant
|
||||
{
|
||||
if ($this->shouldPromoteParticipantRole($participant->role, $role)) {
|
||||
$participant->update(['role' => $role]);
|
||||
|
||||
return $participant->fresh();
|
||||
}
|
||||
|
||||
return $participant;
|
||||
}
|
||||
|
||||
private function shouldPromoteParticipantRole(string $currentRole, string $newRole): bool
|
||||
{
|
||||
static $rank = [
|
||||
'guest' => 10,
|
||||
'attendee' => 20,
|
||||
'participant' => 25,
|
||||
'panelist' => 40,
|
||||
'co_host' => 50,
|
||||
'host' => 60,
|
||||
];
|
||||
|
||||
return ($rank[$newRole] ?? 0) > ($rank[$currentRole] ?? 0);
|
||||
}
|
||||
|
||||
protected function findExistingParticipant(Session $session, ?string $userRef, ?string $email): ?Participant
|
||||
{
|
||||
$active = fn ($q) => $q->whereIn('status', ['invited', 'waiting', 'joined']);
|
||||
@@ -244,6 +280,7 @@ class SessionService
|
||||
if ($user) {
|
||||
$existing = $this->joinedParticipantForUser($session, $user);
|
||||
if ($existing) {
|
||||
$existing = $this->promoteParticipantRole($existing, $role);
|
||||
$this->rememberParticipantSession($request, $existing, $session);
|
||||
|
||||
return $existing;
|
||||
|
||||
@@ -225,6 +225,48 @@ class EventsJoinGateTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_speaker_rejoin_promotes_existing_attendee_to_panelist(): void
|
||||
{
|
||||
$room = $this->createEventsLinkedRoom('town_hall', [
|
||||
'settings' => array_merge(config('meet.default_settings'), [
|
||||
'join_before_host' => true,
|
||||
'waiting_room' => false,
|
||||
]),
|
||||
]);
|
||||
|
||||
$session = Session::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'room_id' => $room->id,
|
||||
'media_room_name' => 'room-'.$room->uuid,
|
||||
'status' => 'live',
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$room->update(['status' => 'live']);
|
||||
|
||||
\App\Models\Participant::create([
|
||||
'owner_ref' => $this->user->public_id,
|
||||
'session_id' => $session->id,
|
||||
'display_name' => 'Keynote Speaker',
|
||||
'email' => 'speaker@example.com',
|
||||
'role' => 'attendee',
|
||||
'status' => 'joined',
|
||||
'joined_at' => now(),
|
||||
]);
|
||||
|
||||
$this->get('/r/'.$room->uuid.'?speaker=speaker-token-123')
|
||||
->assertRedirect(route('meet.join', $room));
|
||||
|
||||
$this->post('/r/'.$room->uuid.'/enter', [
|
||||
'display_name' => 'Keynote Speaker',
|
||||
])->assertRedirect(route('meet.room', $session));
|
||||
|
||||
$this->assertDatabaseHas('meet_participants', [
|
||||
'session_id' => $session->id,
|
||||
'email' => 'speaker@example.com',
|
||||
'role' => 'panelist',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $overrides */
|
||||
protected function createEventsLinkedRoom(string $type, array $overrides = []): Room
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user