mirror of
https://github.com/suitenumerique/meet.git
synced 2026-09-08 16:35:49 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a48f29abff | |||
| 7565ede0a7 | |||
| 1a15e9f44e | |||
| 7838d8acfe | |||
| 3bb388b937 | |||
| e1cc8105db | |||
| 7844dfcc12 | |||
| ef71003721 | |||
| f74d23c57e | |||
| acedb21045 | |||
| 67e7d382e3 | |||
| 164ac8d948 |
@@ -8,6 +8,12 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- 🔒️(backend) enforce display name setting on rename API
|
||||||
|
|
||||||
|
## [1.31.0] - 2026-09-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- ✨(frontend) add 1080p sending resolution option #1660
|
- ✨(frontend) add 1080p sending resolution option #1660
|
||||||
@@ -15,6 +21,7 @@ and this project adheres to
|
|||||||
- ✨(backend) update a room's attributes from the external API
|
- ✨(backend) update a room's attributes from the external API
|
||||||
- 🔊(backend) log request duration in Gunicorn workers
|
- 🔊(backend) log request duration in Gunicorn workers
|
||||||
- 📈(frontend) track missing lobby participant on accept/reject
|
- 📈(frontend) track missing lobby participant on accept/reject
|
||||||
|
- ✨(backend) sort waiting participants by their arrival time
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.29.0"
|
version = "1.31.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"livekit-agents==1.6.7",
|
"livekit-agents==1.6.7",
|
||||||
|
|||||||
Generated
+1
-1
@@ -9,7 +9,7 @@ resolution-markers = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.29.0"
|
version = "1.31.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
|
|||||||
@@ -909,6 +909,15 @@ class RoomViewSet(
|
|||||||
"""Rename the current participant in the room."""
|
"""Rename the current participant in the room."""
|
||||||
room = self.get_object()
|
room = self.get_object()
|
||||||
|
|
||||||
|
if (
|
||||||
|
not settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME
|
||||||
|
and request.user.is_authenticated
|
||||||
|
):
|
||||||
|
return drf_response.Response(
|
||||||
|
{"error": "Authenticated participants cannot edit their display name"},
|
||||||
|
status=drf_status.HTTP_403_FORBIDDEN,
|
||||||
|
)
|
||||||
|
|
||||||
serializer = serializers.RenameParticipantSerializer(data=request.data)
|
serializer = serializers.RenameParticipantSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from uuid import UUID
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from core import models, utils
|
from core import models, utils
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ class LobbyParticipant:
|
|||||||
username: str
|
username: str
|
||||||
color: str
|
color: str
|
||||||
id: str
|
id: str
|
||||||
|
entered_at: str
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, str]:
|
def to_dict(self) -> Dict[str, str]:
|
||||||
"""Serialize the participant object to a dict representation."""
|
"""Serialize the participant object to a dict representation."""
|
||||||
@@ -54,6 +56,7 @@ class LobbyParticipant:
|
|||||||
"username": self.username,
|
"username": self.username,
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"color": self.color,
|
"color": self.color,
|
||||||
|
"entered_at": self.entered_at,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -68,6 +71,7 @@ class LobbyParticipant:
|
|||||||
username=data["username"],
|
username=data["username"],
|
||||||
id=data["id"],
|
id=data["id"],
|
||||||
color=data["color"],
|
color=data["color"],
|
||||||
|
entered_at=data["entered_at"],
|
||||||
)
|
)
|
||||||
except (KeyError, ValueError) as e:
|
except (KeyError, ValueError) as e:
|
||||||
logger.exception("Error creating Participant from dict:")
|
logger.exception("Error creating Participant from dict:")
|
||||||
@@ -203,6 +207,7 @@ class LobbyService:
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color=utils.generate_color(participant_id),
|
color=utils.generate_color(participant_id),
|
||||||
|
entered_at=timezone.now().isoformat(),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
participant.status = LobbyParticipantStatus.ACCEPTED
|
participant.status = LobbyParticipantStatus.ACCEPTED
|
||||||
@@ -264,6 +269,7 @@ class LobbyService:
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color=color,
|
color=color,
|
||||||
|
entered_at=timezone.now().isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -338,6 +344,8 @@ class LobbyService:
|
|||||||
|
|
||||||
self._index_remove(room_id, *dead_ids)
|
self._index_remove(room_id, *dead_ids)
|
||||||
|
|
||||||
|
waiting_participants.sort(key=lambda p: p["entered_at"], reverse=True)
|
||||||
|
|
||||||
return tuple(waiting_participants)
|
return tuple(waiting_participants)
|
||||||
|
|
||||||
def handle_participant_entry(
|
def handle_participant_entry(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from unittest import mock
|
|||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from freezegun import freeze_time
|
||||||
from rest_framework.test import APIClient
|
from rest_framework.test import APIClient
|
||||||
|
|
||||||
from ... import utils
|
from ... import utils
|
||||||
@@ -24,6 +25,7 @@ pytestmark = pytest.mark.django_db
|
|||||||
# Tests for request_entry endpoint
|
# Tests for request_entry endpoint
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_anonymous(settings):
|
def test_request_entry_anonymous(settings):
|
||||||
"""Anonymous users should be allowed to request entry to a room."""
|
"""Anonymous users should be allowed to request entry to a room."""
|
||||||
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
|
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
|
||||||
@@ -59,6 +61,7 @@ def test_request_entry_anonymous(settings):
|
|||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "mocked-color",
|
"color": "mocked-color",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"livekit": None,
|
"livekit": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +74,7 @@ def test_request_entry_anonymous(settings):
|
|||||||
assert participant_data.get("username") == "test_user"
|
assert participant_data.get("username") == "test_user"
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_authenticated_user(settings):
|
def test_request_entry_authenticated_user(settings):
|
||||||
"""Authenticated users should be allowed to request entry."""
|
"""Authenticated users should be allowed to request entry."""
|
||||||
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
|
room = RoomFactory(access_level=RoomAccessLevel.RESTRICTED)
|
||||||
@@ -108,6 +112,7 @@ def test_request_entry_authenticated_user(settings):
|
|||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "mocked-color",
|
"color": "mocked-color",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"livekit": None,
|
"livekit": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +125,7 @@ def test_request_entry_authenticated_user(settings):
|
|||||||
assert participant_data.get("username") == "test_user"
|
assert participant_data.get("username") == "test_user"
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_with_existing_participants(settings):
|
def test_request_entry_with_existing_participants(settings):
|
||||||
"""Anonymous users should be allowed to request entry to a room with existing participants."""
|
"""Anonymous users should be allowed to request entry to a room with existing participants."""
|
||||||
# Create a restricted access room
|
# Create a restricted access room
|
||||||
@@ -138,6 +144,7 @@ def test_request_entry_with_existing_participants(settings):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cache.set(
|
cache.set(
|
||||||
@@ -147,6 +154,7 @@ def test_request_entry_with_existing_participants(settings):
|
|||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,6 +186,7 @@ def test_request_entry_with_existing_participants(settings):
|
|||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"id": participant_id,
|
"id": participant_id,
|
||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "mocked-color",
|
"color": "mocked-color",
|
||||||
"livekit": None,
|
"livekit": None,
|
||||||
@@ -192,6 +201,7 @@ def test_request_entry_with_existing_participants(settings):
|
|||||||
assert participant_data.get("username") == "test_user"
|
assert participant_data.get("username") == "test_user"
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_public_room(settings):
|
def test_request_entry_public_room(settings):
|
||||||
"""Entry requests to public rooms should return ACCEPTED status with LiveKit config."""
|
"""Entry requests to public rooms should return ACCEPTED status with LiveKit config."""
|
||||||
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
||||||
@@ -230,6 +240,7 @@ def test_request_entry_public_room(settings):
|
|||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"id": "123",
|
"id": "123",
|
||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "mocked-color",
|
"color": "mocked-color",
|
||||||
"livekit": {"token": "test-token"},
|
"livekit": {"token": "test-token"},
|
||||||
@@ -240,6 +251,7 @@ def test_request_entry_public_room(settings):
|
|||||||
assert not lobby_keys
|
assert not lobby_keys
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_authenticated_user_public_room(settings):
|
def test_request_entry_authenticated_user_public_room(settings):
|
||||||
"""While authenticated, entry request to public rooms should get accepted."""
|
"""While authenticated, entry request to public rooms should get accepted."""
|
||||||
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
||||||
@@ -282,6 +294,7 @@ def test_request_entry_authenticated_user_public_room(settings):
|
|||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||||
"username": "test_user",
|
"username": "test_user",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "mocked-color",
|
"color": "mocked-color",
|
||||||
"livekit": {"token": "test-token"},
|
"livekit": {"token": "test-token"},
|
||||||
@@ -292,6 +305,7 @@ def test_request_entry_authenticated_user_public_room(settings):
|
|||||||
assert not lobby_keys
|
assert not lobby_keys
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_request_entry_waiting_participant_public_room(settings):
|
def test_request_entry_waiting_participant_public_room(settings):
|
||||||
"""While waiting, entry request to public rooms should get accepted."""
|
"""While waiting, entry request to public rooms should get accepted."""
|
||||||
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
room = RoomFactory(access_level=RoomAccessLevel.PUBLIC)
|
||||||
@@ -308,6 +322,7 @@ def test_request_entry_waiting_participant_public_room(settings):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -338,6 +353,7 @@ def test_request_entry_waiting_participant_public_room(settings):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
"livekit": {"token": "test-token"},
|
"livekit": {"token": "test-token"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +459,7 @@ def test_allow_participant_to_enter_success(settings, allow_entry, updated_statu
|
|||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"username": "foo",
|
"username": "foo",
|
||||||
"color": "123",
|
"color": "123",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -578,6 +595,7 @@ def test_list_waiting_participants_success(settings):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cache.set(
|
cache.set(
|
||||||
@@ -587,6 +605,7 @@ def test_list_waiting_participants_success(settings):
|
|||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:05:00+00:00",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
lobby_service = LobbyService()
|
lobby_service = LobbyService()
|
||||||
@@ -597,21 +616,24 @@ def test_list_waiting_participants_success(settings):
|
|||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
participants = response.json().get("participants")
|
assert response.json() == {
|
||||||
assert sorted(participants, key=lambda p: p["id"]) == [
|
"participants": [
|
||||||
{
|
|
||||||
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
|
||||||
"username": "user1",
|
|
||||||
"status": "waiting",
|
|
||||||
"color": "#123456",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
"id": "f4ca3ab8a6c04ad88097b8da33f60f10",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:05:00+00:00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2f7f162f-e7d1-421b-90e7-02bfbfbf8def",
|
||||||
|
"username": "user1",
|
||||||
|
"status": "waiting",
|
||||||
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_list_waiting_participants_empty(settings):
|
def test_list_waiting_participants_empty(settings):
|
||||||
|
|||||||
@@ -372,6 +372,67 @@ def test_rename_participant_unexpected_twirp_error(mock_livekit_client, room, to
|
|||||||
mock_livekit_client.aclose.assert_called_once()
|
mock_livekit_client.aclose.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", ["John Doe", "Admin", "Room Owner"])
|
||||||
|
def test_rename_participant_forbidden_when_display_name_edit_disabled(
|
||||||
|
mock_livekit_client, settings, room, token, name
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test rename is rejected for authenticated users when the self-hoster
|
||||||
|
disables AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME.
|
||||||
|
"""
|
||||||
|
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
url = reverse("rooms-rename", kwargs={"pk": room.id})
|
||||||
|
response = client.post(
|
||||||
|
url, {"name": name}, format="json", HTTP_AUTHORIZATION=f"Bearer {token}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert response.data == {
|
||||||
|
"error": "Authenticated participants cannot edit their display name"
|
||||||
|
}
|
||||||
|
mock_livekit_client.room.update_participant.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_participant_allowed_when_display_name_edit_enabled(
|
||||||
|
mock_livekit_client, settings, room, token
|
||||||
|
):
|
||||||
|
"""Test rename still works for authenticated users when the setting is enabled."""
|
||||||
|
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = True
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
url = reverse("rooms-rename", kwargs={"pk": room.id})
|
||||||
|
response = client.post(
|
||||||
|
url, {"name": "John Doe"}, format="json", HTTP_AUTHORIZATION=f"Bearer {token}"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
mock_livekit_client.room.update_participant.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_participant_anonymous_allowed_when_display_name_edit_disabled(
|
||||||
|
mock_livekit_client, settings, room, anonymous_token
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Test the setting only restricts authenticated users: anonymous participants
|
||||||
|
have no account name to fall back on and can still rename themselves.
|
||||||
|
"""
|
||||||
|
settings.AUTHENTICATED_PARTICIPANTS_CAN_EDIT_DISPLAY_NAME = False
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
url = reverse("rooms-rename", kwargs={"pk": room.id})
|
||||||
|
response = client.post(
|
||||||
|
url,
|
||||||
|
{"name": "Guest User"},
|
||||||
|
format="json",
|
||||||
|
HTTP_AUTHORIZATION=f"Bearer {anonymous_token}",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
mock_livekit_client.room.update_participant.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_rename_participant_success_anonymous(
|
def test_rename_participant_success_anonymous(
|
||||||
mock_livekit_client, room, anonymous_token
|
mock_livekit_client, room, anonymous_token
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from django.core.cache import cache
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
from core.factories import RoomFactory, UserFactory, UserResourceAccessFactory
|
from core.factories import RoomFactory, UserFactory, UserResourceAccessFactory
|
||||||
from core.models import RoleChoices, RoomAccessLevel
|
from core.models import RoleChoices, RoomAccessLevel
|
||||||
@@ -55,6 +56,7 @@ def participant_dict():
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": "test-participant-id",
|
"id": "test-participant-id",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +68,7 @@ def participant_data():
|
|||||||
username="test-username",
|
username="test-username",
|
||||||
id="test-participant-id",
|
id="test-participant-id",
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -77,6 +80,7 @@ def test_lobby_participant_to_dict(participant_data):
|
|||||||
assert result["username"] == "test-username"
|
assert result["username"] == "test-username"
|
||||||
assert result["id"] == "test-participant-id"
|
assert result["id"] == "test-participant-id"
|
||||||
assert result["color"] == "#123456"
|
assert result["color"] == "#123456"
|
||||||
|
assert result["entered_at"] == "2025-01-01T10:00:00+00:00"
|
||||||
|
|
||||||
|
|
||||||
def test_lobby_participant_from_dict_success(participant_dict):
|
def test_lobby_participant_from_dict_success(participant_dict):
|
||||||
@@ -87,6 +91,20 @@ def test_lobby_participant_from_dict_success(participant_dict):
|
|||||||
assert participant.username == "test-username"
|
assert participant.username == "test-username"
|
||||||
assert participant.id == "test-participant-id"
|
assert participant.id == "test-participant-id"
|
||||||
assert participant.color == "#123456"
|
assert participant.color == "#123456"
|
||||||
|
assert participant.entered_at == "2025-01-01T10:00:00+00:00"
|
||||||
|
|
||||||
|
|
||||||
|
def test_lobby_participant_from_dict_missing_entered_at():
|
||||||
|
"""`entered_at` is mandatory; data without it is rejected."""
|
||||||
|
data = {
|
||||||
|
"status": "waiting",
|
||||||
|
"username": "test-username",
|
||||||
|
"id": "test-participant-id",
|
||||||
|
"color": "#123456",
|
||||||
|
}
|
||||||
|
|
||||||
|
with pytest.raises(LobbyParticipantParsingError, match="Invalid participant data"):
|
||||||
|
LobbyParticipant.from_dict(data)
|
||||||
|
|
||||||
|
|
||||||
def test_lobby_participant_from_dict_default_status():
|
def test_lobby_participant_from_dict_default_status():
|
||||||
@@ -95,6 +113,7 @@ def test_lobby_participant_from_dict_default_status():
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": "test-participant-id",
|
"id": "test-participant-id",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
participant = LobbyParticipant.from_dict(data_without_status)
|
participant = LobbyParticipant.from_dict(data_without_status)
|
||||||
@@ -120,6 +139,7 @@ def test_lobby_participant_from_dict_invalid_status():
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": "test-participant-id",
|
"id": "test-participant-id",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(LobbyParticipantParsingError, match="Invalid participant data"):
|
with pytest.raises(LobbyParticipantParsingError, match="Invalid participant data"):
|
||||||
@@ -264,6 +284,7 @@ def test_request_entry_public_room(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
|
|
||||||
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
||||||
@@ -302,6 +323,7 @@ def test_request_entry_trusted_room(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
|
|
||||||
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
||||||
@@ -344,6 +366,7 @@ def test_request_entry_new_participant(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
mock_enter.return_value = participant_data
|
mock_enter.return_value = participant_data
|
||||||
|
|
||||||
@@ -371,6 +394,7 @@ def test_request_entry_waiting_participant(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
||||||
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
||||||
@@ -399,6 +423,7 @@ def test_request_entry_accepted_participant(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
||||||
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
||||||
@@ -439,6 +464,7 @@ def test_request_entry_participant_with_role(
|
|||||||
username=username,
|
username=username,
|
||||||
id=participant_id,
|
id=participant_id,
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
)
|
)
|
||||||
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
lobby_service._get_or_create_participant_id = mock.Mock(return_value=participant_id)
|
||||||
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
lobby_service._get_participant = mock.Mock(return_value=mocked_participant)
|
||||||
@@ -479,6 +505,7 @@ def test_refresh_waiting_status(mock_cache, lobby_service, participant_id):
|
|||||||
@mock.patch("core.utils.generate_color")
|
@mock.patch("core.utils.generate_color")
|
||||||
@mock.patch("core.utils.notify_participants")
|
@mock.patch("core.utils.notify_participants")
|
||||||
@mock.patch("core.services.lobby.LobbyService._index_add")
|
@mock.patch("core.services.lobby.LobbyService._index_add")
|
||||||
|
@freeze_time("2025-01-01 10:00:00")
|
||||||
def test_enter_success(
|
def test_enter_success(
|
||||||
mock_index_add,
|
mock_index_add,
|
||||||
mock_notify,
|
mock_notify,
|
||||||
@@ -500,6 +527,7 @@ def test_enter_success(
|
|||||||
assert participant.username == username
|
assert participant.username == username
|
||||||
assert participant.id == participant_id
|
assert participant.id == participant_id
|
||||||
assert participant.color == "#123456"
|
assert participant.color == "#123456"
|
||||||
|
assert participant.entered_at == "2025-01-01T10:00:00+00:00"
|
||||||
|
|
||||||
lobby_service._get_cache_key.assert_called_once_with(room.id, participant_id)
|
lobby_service._get_cache_key.assert_called_once_with(room.id, participant_id)
|
||||||
|
|
||||||
@@ -629,6 +657,7 @@ def test_list_waiting_participants_multiple(mock_cache, lobby_service):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"id": "participant1",
|
"id": "participant1",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
participant2 = {
|
participant2 = {
|
||||||
@@ -636,6 +665,7 @@ def test_list_waiting_participants_multiple(mock_cache, lobby_service):
|
|||||||
"username": "user2",
|
"username": "user2",
|
||||||
"id": "participant2",
|
"id": "participant2",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:05:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
lobby_service._index_members = mock.Mock(
|
lobby_service._index_members = mock.Mock(
|
||||||
@@ -651,9 +681,10 @@ def test_list_waiting_participants_multiple(mock_cache, lobby_service):
|
|||||||
|
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
|
|
||||||
# Verify both participants are in the result
|
# Most recent entry comes first
|
||||||
assert any(p["id"] == "participant1" and p["username"] == "user1" for p in result)
|
assert [p["id"] for p in result] == ["participant2", "participant1"]
|
||||||
assert any(p["id"] == "participant2" and p["username"] == "user2" for p in result)
|
assert result[0]["username"] == "user2"
|
||||||
|
assert result[1]["username"] == "user1"
|
||||||
|
|
||||||
# Verify all participants have waiting status
|
# Verify all participants have waiting status
|
||||||
assert all(p["status"] == "waiting" for p in result)
|
assert all(p["status"] == "waiting" for p in result)
|
||||||
@@ -689,6 +720,7 @@ def test_list_waiting_participants_partially_corrupted(mock_cache, lobby_service
|
|||||||
"username": "user2",
|
"username": "user2",
|
||||||
"id": "participant2",
|
"id": "participant2",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
corrupted_participant = {"invalid": "data"}
|
corrupted_participant = {"invalid": "data"}
|
||||||
@@ -729,12 +761,14 @@ def test_list_waiting_participants_non_waiting(mock_cache, lobby_service):
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"id": "participant1",
|
"id": "participant1",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
participant2 = {
|
participant2 = {
|
||||||
"status": "accepted",
|
"status": "accepted",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"id": "participant2",
|
"id": "participant2",
|
||||||
"color": "#654321",
|
"color": "#654321",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
lobby_service._index_members = mock.Mock(
|
lobby_service._index_members = mock.Mock(
|
||||||
@@ -832,6 +866,7 @@ def test_update_participant_status_success(mock_cache, lobby_service, participan
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": participant_id,
|
"id": participant_id,
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
|
|
||||||
mock_cache.get.return_value = participant_dict
|
mock_cache.get.return_value = participant_dict
|
||||||
@@ -850,6 +885,7 @@ def test_update_participant_status_success(mock_cache, lobby_service, participan
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": participant_id,
|
"id": participant_id,
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
mock_cache.set.assert_called_once_with(
|
mock_cache.set.assert_called_once_with(
|
||||||
"mocked_cache_key", expected_data, timeout=60
|
"mocked_cache_key", expected_data, timeout=60
|
||||||
@@ -875,6 +911,7 @@ def test_clear_room_cache(settings, lobby_service):
|
|||||||
username="participant1",
|
username="participant1",
|
||||||
id="participant1",
|
id="participant1",
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
),
|
),
|
||||||
timeout=settings.LOBBY_WAITING_TIMEOUT,
|
timeout=settings.LOBBY_WAITING_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -885,6 +922,7 @@ def test_clear_room_cache(settings, lobby_service):
|
|||||||
username="participant2",
|
username="participant2",
|
||||||
id="participant2",
|
id="participant2",
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
),
|
),
|
||||||
timeout=settings.LOBBY_ACCEPTED_TIMEOUT,
|
timeout=settings.LOBBY_ACCEPTED_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -895,6 +933,7 @@ def test_clear_room_cache(settings, lobby_service):
|
|||||||
username="participant3",
|
username="participant3",
|
||||||
id="participant3",
|
id="participant3",
|
||||||
color="#123456",
|
color="#123456",
|
||||||
|
entered_at="2025-01-01T10:00:00+00:00",
|
||||||
),
|
),
|
||||||
timeout=settings.LOBBY_DENIED_TIMEOUT,
|
timeout=settings.LOBBY_DENIED_TIMEOUT,
|
||||||
)
|
)
|
||||||
@@ -930,6 +969,7 @@ def test_clear_participant_cache(lobby_service):
|
|||||||
"username": "test-username",
|
"username": "test-username",
|
||||||
"id": participant_id,
|
"id": participant_id,
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
}
|
}
|
||||||
cache.set(cache_key, participant_data, timeout=settings.LOBBY_WAITING_TIMEOUT)
|
cache.set(cache_key, participant_data, timeout=settings.LOBBY_WAITING_TIMEOUT)
|
||||||
lobby_service._index_add(room_id, participant_id)
|
lobby_service._index_add(room_id, participant_id)
|
||||||
@@ -1000,6 +1040,7 @@ def test_list_waiting_participants_prunes_stale_index_ids(settings, lobby_servic
|
|||||||
"username": "user1",
|
"username": "user1",
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"color": "#123456",
|
"color": "#123456",
|
||||||
|
"entered_at": "2025-01-01T10:00:00+00:00",
|
||||||
},
|
},
|
||||||
timeout=100,
|
timeout=100,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1187,7 +1187,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
|
"@fontsource-variable/atkinson-hyperlegible-next": "5.3.0",
|
||||||
"@fontsource-variable/lexend": "5.3.0",
|
"@fontsource-variable/lexend": "5.3.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "panda codegen && vite",
|
"dev": "panda codegen && vite",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type WaitingParticipant = {
|
|||||||
status: string
|
status: string
|
||||||
username: string
|
username: string
|
||||||
color: string
|
color: string
|
||||||
|
entered_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WaitingParticipantsResponse = {
|
export type WaitingParticipantsResponse = {
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ import {
|
|||||||
} from '../../participants/api/listWaitingParticipants'
|
} from '../../participants/api/listWaitingParticipants'
|
||||||
import { reportError } from '@/features/analytics/telemetry'
|
import { reportError } from '@/features/analytics/telemetry'
|
||||||
|
|
||||||
|
const toTimestamp = (participant: WaitingParticipant): number =>
|
||||||
|
Date.parse(participant.entered_at)
|
||||||
|
|
||||||
|
export const sortWaitingParticipants = (
|
||||||
|
participants: WaitingParticipant[]
|
||||||
|
): WaitingParticipant[] =>
|
||||||
|
[...participants].sort((a, b) => toTimestamp(a) - toTimestamp(b))
|
||||||
|
|
||||||
export const useWaitingParticipants = () => {
|
export const useWaitingParticipants = () => {
|
||||||
const roomData = useRoomData()
|
const roomData = useRoomData()
|
||||||
const roomId = roomData?.id || '' // FIXME - bad practice
|
const roomId = roomData?.id || '' // FIXME - bad practice
|
||||||
@@ -22,7 +30,10 @@ export const useWaitingParticipants = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const waitingParticipants = useMemo(
|
const waitingParticipants = useMemo(
|
||||||
() => (canManageLobby ? waitingData?.participants || [] : []),
|
() =>
|
||||||
|
canManageLobby
|
||||||
|
? sortWaitingParticipants(waitingData?.participants || [])
|
||||||
|
: [],
|
||||||
[waitingData, canManageLobby]
|
[waitingData, canManageLobby]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const LobbyProvider = () => {
|
|||||||
// 3. Rights regained.
|
// 3. Rights regained.
|
||||||
const prevCanManageLobby = usePrevious(canManageLobby)
|
const prevCanManageLobby = usePrevious(canManageLobby)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!prevCanManageLobby && canManageLobby && isConnected) {
|
if (prevCanManageLobby != canManageLobby && isConnected) {
|
||||||
fetchIfManager()
|
fetchIfManager()
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const Card = styled('div', {
|
|||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
boxShadow: '',
|
boxShadow: '',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '380px',
|
maxWidth: '410px',
|
||||||
minHeight: '196px',
|
minHeight: '196px',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -229,7 +229,7 @@ const ConfirmationMessage = ({ onNext }: { onNext: () => void }) => {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '380px',
|
maxWidth: '410px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ const Heading = styled('h1', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const buttonClass = css({
|
const buttonClass = css({
|
||||||
width: { base: '100%', xsm: 'auto' },
|
width: '100%',
|
||||||
|
flex: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
enum DisconnectReasonKey {
|
enum DisconnectReasonKey {
|
||||||
@@ -64,7 +65,7 @@ const FeedbackRoute = () => {
|
|||||||
<Heading>{t(`feedback.heading.${reasonKey || 'normal'}`)}</Heading>
|
<Heading>{t(`feedback.heading.${reasonKey || 'normal'}`)}</Heading>
|
||||||
<Stack
|
<Stack
|
||||||
direction={{ base: 'column', xsm: 'row' }}
|
direction={{ base: 'column', xsm: 'row' }}
|
||||||
width={{ base: '100%', xsm: 'auto' }}
|
width="100%"
|
||||||
maxWidth="410px"
|
maxWidth="410px"
|
||||||
>
|
>
|
||||||
{showBackButton && (
|
{showBackButton && (
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@html-to/text-cli": "0.6.1",
|
"@html-to/text-cli": "0.6.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"description": "An util to generate html and text django's templates from mjml templates",
|
"description": "An util to generate html and text django's templates from mjml templates",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./library",
|
"./library",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.30.0",
|
"version": "1.31.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "summary"
|
name = "summary"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi[standard]>=0.105.0",
|
"fastapi[standard]>=0.105.0",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1507,7 +1507,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "summary"
|
name = "summary"
|
||||||
version = "1.30.0"
|
version = "1.31.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "celery" },
|
{ name = "celery" },
|
||||||
|
|||||||
Reference in New Issue
Block a user