diff --git a/src/backend/core/migrations/0011_room_pin_code.py b/src/backend/core/migrations/0011_room_pin_code.py new file mode 100644 index 00000000..d13155a3 --- /dev/null +++ b/src/backend/core/migrations/0011_room_pin_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-01-12 22:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_alter_resourceaccess_options_alter_user_options'), + ] + + operations = [ + migrations.AddField( + model_name='room', + name='pin_code', + field=models.CharField(blank=True, help_text="Unique n-digit code that identifies this room. Automatically generated on creation. Displayed with '#' suffix.", max_length=100, null=True, unique=True, verbose_name='Room PIN code'), + ), + ] diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 53653143..135dc3c0 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -11,13 +11,15 @@ from django.contrib.auth import models as auth_models from django.contrib.auth.base_user import AbstractBaseUser from django.core import mail, validators from django.core.exceptions import PermissionDenied, ValidationError -from django.db import models +from django.db import IntegrityError, models from django.utils.functional import lazy from django.utils.text import capfirst, slugify from django.utils.translation import gettext_lazy as _ from timezone_field import TimeZoneField +from core import utils + logger = getLogger(__name__) @@ -361,13 +363,23 @@ class Room(Resource): primary_key=True, ) slug = models.SlugField(max_length=100, blank=True, null=True, unique=True) - configuration = models.JSONField( blank=True, default=dict, verbose_name=_("Visio room configuration"), help_text=_("Values for Visio parameters to configure the room."), ) + pin_code = models.CharField( + max_length=100, + unique=True, + blank=True, + null=True, + verbose_name=_("Room PIN code"), + help_text=_( + "Unique n-digit code that identifies this room. Automatically generated on creation." + " Displayed with '#' suffix." + ), + ) class Meta: db_table = "meet_room" @@ -378,6 +390,23 @@ class Room(Resource): def __str__(self): return capfirst(self.name) + def save(self, *args, **kwargs): + """Generate a unique n-digit pin code for new rooms. + + This uses the DB to ensure uniqueness of the code. Better than checking separately + with the DB and then saving, which introduces a race condition. + """ + + if self.pk or self.pin_code: + return super().save(*args, **kwargs) + + while True: + try: + self.pin_code = utils.generate_pin_code(n=settings.ROOM_PIN_CODE_LENGTH) + return super().save(*args, **kwargs) + except IntegrityError: + continue + def clean_fields(self, exclude=None): """ Automatically generate the slug from the name and make sure it does not look like a UUID. diff --git a/src/backend/core/utils.py b/src/backend/core/utils.py index 304b5de7..169d5c7c 100644 --- a/src/backend/core/utils.py +++ b/src/backend/core/utils.py @@ -15,6 +15,11 @@ from django.conf import settings from livekit.api import AccessToken, VideoGrants +def generate_pin_code(n: int) -> str: + """Generate a n-digit pin code""" + return f"{''.join([str(random.randint(0, 9)) for _ in range(n)])}" + + def generate_color(identity: str) -> str: """Generates a consistent HSL color based on a given identity string. diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py index 9b30d2de..51f202eb 100755 --- a/src/backend/meet/settings.py +++ b/src/backend/meet/settings.py @@ -483,6 +483,18 @@ class Base(Configuration): ) BREVO_API_CONTACT_ATTRIBUTES = values.DictValue({"VISIO_USER": True}) + # SIP Telephony + ROOM_PIN_CODE_LENGTH = values.PositiveIntegerValue( + 9, # this value cannot exceed 100 digits due to database constraints + environ_name="ROOM_PIN_CODE_LENGTH", + environ_prefix=None, + ) + ROOM_PIN_CODE_GENERATION_MAX_RETRY = values.PositiveIntegerValue( + 3, + environ_name="ROOM_PIN_CODE_GENERATION_MAX_RETRY", + environ_prefix=None, + ) + # pylint: disable=invalid-name @property def ENVIRONMENT(self):