diff --git a/app/Http/Controllers/Meet/MeetingRoomController.php b/app/Http/Controllers/Meet/MeetingRoomController.php index c22b4a8..4f3a22c 100644 --- a/app/Http/Controllers/Meet/MeetingRoomController.php +++ b/app/Http/Controllers/Meet/MeetingRoomController.php @@ -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() + ); } } diff --git a/app/Services/Meet/SessionService.php b/app/Services/Meet/SessionService.php index 0488ad8..eb97d3d 100644 --- a/app/Services/Meet/SessionService.php +++ b/app/Services/Meet/SessionService.php @@ -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; diff --git a/tests/Feature/EventsJoinGateTest.php b/tests/Feature/EventsJoinGateTest.php index 5c96711..e40da65 100644 --- a/tests/Feature/EventsJoinGateTest.php +++ b/tests/Feature/EventsJoinGateTest.php @@ -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 $overrides */ protected function createEventsLinkedRoom(string $type, array $overrides = []): Room {