♻️(backend) sync lobby and LiveKit participant UUID generation
Refactor lobby system to use consistent UUID v4 across lobby registration and LiveKit token participant identity instead of generating separate UUIDs. Maintains synchronized identifiers between lobby cache and LiveKit participants, simplifying future participant removal operations by using the same UUID reference across both systems.
This commit is contained in:
committed by
aleb_the_flash
parent
0f76517957
commit
6c633b1ecb
@@ -2,8 +2,6 @@
|
||||
|
||||
# pylint: disable=W0223
|
||||
|
||||
import uuid
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
@@ -222,17 +220,9 @@ class RequestEntrySerializer(BaseValidationOnlySerializer):
|
||||
class ParticipantEntrySerializer(BaseValidationOnlySerializer):
|
||||
"""Validate participant entry decision data."""
|
||||
|
||||
participant_id = serializers.CharField(required=True)
|
||||
participant_id = serializers.UUIDField(required=True)
|
||||
allow_entry = serializers.BooleanField(required=True)
|
||||
|
||||
def validate_participant_id(self, value):
|
||||
"""Validate that the participant_id is a valid UUID hex string."""
|
||||
try:
|
||||
uuid.UUID(hex=value, version=4)
|
||||
except (ValueError, TypeError) as e:
|
||||
raise serializers.ValidationError("Invalid UUID hex format") from e
|
||||
return value
|
||||
|
||||
|
||||
class CreationCallbackSerializer(BaseValidationOnlySerializer):
|
||||
"""Validate room creation callback data."""
|
||||
|
||||
@@ -419,7 +419,8 @@ class RoomViewSet(
|
||||
try:
|
||||
lobby_service.handle_participant_entry(
|
||||
room_id=room.id,
|
||||
**serializer.validated_data,
|
||||
participant_id=str(serializer.validated_data.get("participant_id")),
|
||||
allow_entry=serializer.validated_data.get("allow_entry"),
|
||||
)
|
||||
return drf_response.Response({"message": "Participant was updated."})
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class LobbyService:
|
||||
@staticmethod
|
||||
def _get_or_create_participant_id(request) -> str:
|
||||
"""Extract unique participant identifier from the request."""
|
||||
return request.COOKIES.get(settings.LOBBY_COOKIE_NAME, uuid.uuid4().hex)
|
||||
return request.COOKIES.get(settings.LOBBY_COOKIE_NAME, str(uuid.uuid4()))
|
||||
|
||||
@staticmethod
|
||||
def prepare_response(response, participant_id):
|
||||
@@ -163,6 +163,7 @@ class LobbyService:
|
||||
color=participant.color,
|
||||
configuration=room.configuration,
|
||||
is_admin_or_owner=False,
|
||||
participant_id=participant_id,
|
||||
)
|
||||
return participant, livekit_config
|
||||
|
||||
@@ -183,6 +184,7 @@ class LobbyService:
|
||||
color=participant.color,
|
||||
configuration=room.configuration,
|
||||
is_admin_or_owner=False,
|
||||
participant_id=participant_id,
|
||||
)
|
||||
|
||||
return participant, livekit_config
|
||||
|
||||
@@ -132,9 +132,9 @@ def test_request_entry_with_existing_participants(settings):
|
||||
|
||||
# Add two participants already waiting in the lobby
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
@@ -259,7 +259,7 @@ def test_request_entry_authenticated_user_public_room(settings):
|
||||
mock.patch.object(
|
||||
LobbyService,
|
||||
"_get_or_create_participant_id",
|
||||
return_value="2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
return_value="2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
),
|
||||
mock.patch.object(
|
||||
utils, "generate_livekit_config", return_value={"token": "test-token"}
|
||||
@@ -276,11 +276,11 @@ def test_request_entry_authenticated_user_public_room(settings):
|
||||
# Verify the lobby cookie was set
|
||||
cookie = response.cookies.get("mocked-cookie")
|
||||
assert cookie is not None
|
||||
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
assert cookie.value == "2f7f162f-e7d1-421b-90e7-02bfbfbf8def"
|
||||
|
||||
# Verify response content matches expected structure and values
|
||||
assert response.json() == {
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "test_user",
|
||||
"status": "accepted",
|
||||
"color": "mocked-color",
|
||||
@@ -302,9 +302,9 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
|
||||
# Add a waiting participant to the room's lobby cache
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
@@ -312,7 +312,7 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
)
|
||||
|
||||
# Simulate a browser with existing participant cookie
|
||||
client.cookies.load({"mocked-cookie": "2f7f162fe7d1421b90e702bfbfbf8def"})
|
||||
client.cookies.load({"mocked-cookie": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def"})
|
||||
|
||||
with (
|
||||
mock.patch.object(utils, "notify_participants", return_value=None),
|
||||
@@ -330,11 +330,11 @@ def test_request_entry_waiting_participant_public_room(settings):
|
||||
# Verify the lobby cookie was set
|
||||
cookie = response.cookies.get("mocked-cookie")
|
||||
assert cookie is not None
|
||||
assert cookie.value == "2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
assert cookie.value == "2f7f162f-e7d1-421b-90e7-02bfbfbf8def"
|
||||
|
||||
# Verify response content matches expected structure and values
|
||||
assert response.json() == {
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "user1",
|
||||
"status": "accepted",
|
||||
"color": "#123456",
|
||||
@@ -381,7 +381,7 @@ def test_allow_participant_to_enter_anonymous():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
@@ -396,7 +396,7 @@ def test_allow_participant_to_enter_non_owner():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
@@ -414,7 +414,7 @@ def test_allow_participant_to_enter_public_room():
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -437,9 +437,9 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
||||
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
|
||||
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"status": "waiting",
|
||||
"username": "foo",
|
||||
"color": "123",
|
||||
@@ -449,7 +449,7 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{
|
||||
"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"participant_id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"allow_entry": allow_entry,
|
||||
},
|
||||
)
|
||||
@@ -458,7 +458,7 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
||||
assert response.json() == {"message": "Participant was updated."}
|
||||
|
||||
participant_data = cache.get(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def"
|
||||
)
|
||||
assert participant_data.get("status") == updated_status
|
||||
|
||||
@@ -476,13 +476,13 @@ def test_allow_participant_to_enter_participant_not_found(settings):
|
||||
settings.LOBBY_KEY_PREFIX = "mocked-cache-prefix"
|
||||
|
||||
participant_data = cache.get(
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162fe7d1421b90e702bfbfbf8def"
|
||||
f"mocked-cache-prefix_{room.id!s}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def"
|
||||
)
|
||||
assert participant_data is None
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/rooms/{room.id}/enter/",
|
||||
{"participant_id": "2f7f162fe7d1421b90e702bfbfbf8def", "allow_entry": True},
|
||||
{"participant_id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def", "allow_entry": True},
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
@@ -572,9 +572,9 @@ def test_list_waiting_participants_success(settings):
|
||||
|
||||
# Add participants in the lobby
|
||||
cache.set(
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
f"mocked-cache-prefix_{room.id}_2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
@@ -597,7 +597,7 @@ def test_list_waiting_participants_success(settings):
|
||||
participants = response.json().get("participants")
|
||||
assert sorted(participants, key=lambda p: p["id"]) == [
|
||||
{
|
||||
"id": "2f7f162fe7d1421b90e702bfbfbf8def",
|
||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||
"username": "user1",
|
||||
"status": "waiting",
|
||||
"color": "#123456",
|
||||
|
||||
@@ -271,6 +271,7 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
|
||||
color=None,
|
||||
sources=["mock-source"],
|
||||
is_admin_or_owner=False,
|
||||
participant_id=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -321,6 +322,7 @@ def test_api_rooms_retrieve_authenticated_trusted(mock_token):
|
||||
color=None,
|
||||
sources=None,
|
||||
is_admin_or_owner=False,
|
||||
participant_id=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -407,6 +409,7 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries, setti
|
||||
color=None,
|
||||
sources=["mock-source"],
|
||||
is_admin_or_owner=False,
|
||||
participant_id=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -499,4 +502,5 @@ def test_api_rooms_retrieve_administrators(
|
||||
color=None,
|
||||
sources=None,
|
||||
is_admin_or_owner=True,
|
||||
participant_id=None,
|
||||
)
|
||||
|
||||
@@ -144,10 +144,9 @@ def test_get_or_create_participant_id_from_cookie(lobby_service):
|
||||
assert participant_id == "existing-id"
|
||||
|
||||
|
||||
@mock.patch("uuid.uuid4")
|
||||
@mock.patch.object(uuid, "uuid4", return_value="generated-id")
|
||||
def test_get_or_create_participant_id_new(mock_uuid4, lobby_service):
|
||||
"""Test creating new participant ID when cookie is missing."""
|
||||
mock_uuid4.return_value = mock.Mock(hex="generated-id")
|
||||
request = mock.Mock()
|
||||
request.COOKIES = {}
|
||||
|
||||
@@ -268,6 +267,7 @@ def test_request_entry_public_room(
|
||||
color=participant.color,
|
||||
configuration=room.configuration,
|
||||
is_admin_or_owner=False,
|
||||
participant_id="test-participant-id",
|
||||
)
|
||||
|
||||
lobby_service._get_participant.assert_called_once_with(room.id, participant_id)
|
||||
@@ -306,6 +306,7 @@ def test_request_entry_trusted_room(
|
||||
color=participant.color,
|
||||
configuration=room.configuration,
|
||||
is_admin_or_owner=False,
|
||||
participant_id="test-participant-id",
|
||||
)
|
||||
|
||||
lobby_service._get_participant.assert_called_once_with(room.id, participant_id)
|
||||
@@ -400,6 +401,7 @@ def test_request_entry_accepted_participant(
|
||||
color="#123456",
|
||||
configuration=room.configuration,
|
||||
is_admin_or_owner=False,
|
||||
participant_id="test-participant-id",
|
||||
)
|
||||
lobby_service._get_participant.assert_called_once_with(room.id, participant_id)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ def generate_token(
|
||||
color: Optional[str] = None,
|
||||
sources: Optional[List[str]] = None,
|
||||
is_admin_or_owner: bool = False,
|
||||
participant_id: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Generate a LiveKit access token for a user in a specific room.
|
||||
|
||||
@@ -69,6 +70,8 @@ def generate_token(
|
||||
sources: (Optional[List[str]]): List of media sources the user can publish
|
||||
If none, defaults to LIVEKIT_DEFAULT_SOURCES.
|
||||
is_admin_or_owner (bool): Whether user has admin privileges
|
||||
participant_id (Optional[str]): Stable identifier for anonymous users;
|
||||
used as identity when user.is_anonymous.
|
||||
|
||||
Returns:
|
||||
str: The LiveKit JWT access token.
|
||||
@@ -90,7 +93,7 @@ def generate_token(
|
||||
)
|
||||
|
||||
if user.is_anonymous:
|
||||
identity = str(uuid4())
|
||||
identity = participant_id or str(uuid4())
|
||||
default_username = "Anonymous"
|
||||
else:
|
||||
identity = str(user.sub)
|
||||
@@ -120,6 +123,7 @@ def generate_livekit_config(
|
||||
is_admin_or_owner: bool,
|
||||
color: Optional[str] = None,
|
||||
configuration: Optional[dict] = None,
|
||||
participant_id: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""Generate LiveKit configuration for room access.
|
||||
|
||||
@@ -130,6 +134,8 @@ def generate_livekit_config(
|
||||
is_admin_or_owner (bool): Whether the user has admin/owner privileges for this room.
|
||||
color (Optional[str]): Optional color to associate with the participant.
|
||||
configuration (Optional[dict]): Room configuration dict that can override default settings.
|
||||
participant_id (Optional[str]): Stable identifier for anonymous users;
|
||||
used as identity when user.is_anonymous.
|
||||
|
||||
Returns:
|
||||
dict: LiveKit configuration with URL, room and access token
|
||||
@@ -149,6 +155,7 @@ def generate_livekit_config(
|
||||
color=color,
|
||||
sources=sources,
|
||||
is_admin_or_owner=is_admin_or_owner,
|
||||
participant_id=participant_id,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user