Skip to content

Commit 7090353

Browse files
add video permissions
1 parent c661259 commit 7090353

File tree

24 files changed

+567
-81
lines changed

24 files changed

+567
-81
lines changed

app/eventyay/api/serializers/organizer.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ class Meta:
121121
'can_view_vouchers',
122122
'can_change_vouchers',
123123
'can_checkin_orders',
124+
'can_change_submissions',
125+
'is_reviewer',
126+
'force_hide_speaker_names',
127+
'can_video_create_stages',
128+
'can_video_create_channels',
129+
'can_video_direct_message',
130+
'can_video_manage_announcements',
131+
'can_video_view_users',
132+
'can_video_manage_users',
133+
'can_video_manage_rooms',
134+
'can_video_manage_kiosks',
135+
'can_video_manage_configuration',
124136
)
125137

126138
def validate(self, data):

app/eventyay/api/views/event.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@
6565

6666
logger = logging.getLogger(__name__)
6767

68+
VIDEO_TRAIT_ROLE_MAP = {
69+
'video_stage_manager': 'video_stage_manager',
70+
'video_channel_manager': 'video_channel_manager',
71+
'video_direct_messaging': 'video_direct_messaging',
72+
'video_announcement_manager': 'video_announcement_manager',
73+
'video_user_viewer': 'video_user_viewer',
74+
'video_user_moderator': 'video_user_moderator',
75+
'video_room_manager': 'video_room_manager',
76+
'video_kiosk_manager': 'video_kiosk_manager',
77+
'video_config_manager': 'video_config_manager',
78+
}
79+
6880
with scopes_disabled():
6981

7082
class EventFilter(FilterSet):
@@ -325,8 +337,12 @@ def post(request, *args, **kwargs) -> JsonResponse:
325337

326338
title = titles.get(locale) or titles.get("en") or title_default
327339

328-
attendee_trait_grants = request.data.get("traits", {}).get("attendee", "")
329-
if not isinstance(attendee_trait_grants, str):
340+
traits_payload = request.data.get("traits") or {}
341+
if not isinstance(traits_payload, dict):
342+
raise ValidationError("Traits must be provided as an object.")
343+
344+
attendee_trait_grants = traits_payload.get("attendee", "")
345+
if attendee_trait_grants and not isinstance(attendee_trait_grants, str):
330346
raise ValidationError("Attendee traits must be a string")
331347

332348
trait_grants = {
@@ -337,6 +353,15 @@ def post(request, *args, **kwargs) -> JsonResponse:
337353
"scheduleuser": ["schedule-update"],
338354
}
339355

356+
for trait_name, role_name in VIDEO_TRAIT_ROLE_MAP.items():
357+
trait_value = traits_payload.get(trait_name, "")
358+
if trait_value:
359+
if not isinstance(trait_value, str):
360+
raise ValidationError(
361+
f"Trait '{trait_name}' must be a string value."
362+
)
363+
trait_grants[role_name] = [trait_value]
364+
340365
# if event already exists, update it, otherwise create a new event
341366
event_id = request.data.get("id")
342367
domain_path = "{}{}/{}".format(
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Generated by Django 5.2.5 on 2025-11-27 13:17
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('base', '0002_room_hidden_room_setup_complete_room_sidebar_hidden_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='team',
15+
name='can_video_create_channels',
16+
field=models.BooleanField(default=False, help_text='Allows creating chat/video channels inside Eventyay Video.', verbose_name='Video: Can create channels'),
17+
),
18+
migrations.AddField(
19+
model_name='team',
20+
name='can_video_create_stages',
21+
field=models.BooleanField(default=False, help_text='Allows creating livestream stages inside Eventyay Video.', verbose_name='Video: Can create stages'),
22+
),
23+
migrations.AddField(
24+
model_name='team',
25+
name='can_video_direct_message',
26+
field=models.BooleanField(default=False, help_text='Grants permission to open new direct message conversations.', verbose_name='Video: Can send direct messages'),
27+
),
28+
migrations.AddField(
29+
model_name='team',
30+
name='can_video_manage_announcements',
31+
field=models.BooleanField(default=False, help_text='Allows posting announcements in the Eventyay Video interface.', verbose_name='Video: Can create announcements'),
32+
),
33+
migrations.AddField(
34+
model_name='team',
35+
name='can_video_manage_configuration',
36+
field=models.BooleanField(default=False, help_text='Allows editing the global Eventyay Video configuration.', verbose_name='Video: Can edit event configuration'),
37+
),
38+
migrations.AddField(
39+
model_name='team',
40+
name='can_video_manage_kiosks',
41+
field=models.BooleanField(default=False, help_text='Allows managing kiosk displays inside Eventyay Video.', verbose_name='Video: Can create and edit kiosks'),
42+
),
43+
migrations.AddField(
44+
model_name='team',
45+
name='can_video_manage_rooms',
46+
field=models.BooleanField(default=False, help_text='Allows editing and deleting rooms inside Eventyay Video.', verbose_name='Video: Can create and edit rooms'),
47+
),
48+
migrations.AddField(
49+
model_name='team',
50+
name='can_video_manage_users',
51+
field=models.BooleanField(default=False, help_text='Allows moderating users (ban, silence, reactivate) in Eventyay Video.', verbose_name='Video: Can message, ban, and silence users'),
52+
),
53+
migrations.AddField(
54+
model_name='team',
55+
name='can_video_view_users',
56+
field=models.BooleanField(default=False, help_text='Allows access to the user directory in Eventyay Video.', verbose_name='Video: Can view users'),
57+
),
58+
]

app/eventyay/base/models/event.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
from eventyay.common.text.path import path_with_hash
5353
from eventyay.common.text.phrases import phrases
5454
from eventyay.common.urls import EventUrls
55-
from eventyay.core.permissions import Permission, SYSTEM_ROLES
55+
from eventyay.core.permissions import MAX_PERMISSIONS_IF_SILENCED, Permission, SYSTEM_ROLES
5656
from eventyay.core.utils.json import CustomJSONEncoder
5757
from eventyay.consts import TIMEZONE_CHOICES
5858
from eventyay.helpers.database import GroupConcat
@@ -82,7 +82,6 @@ def default_roles():
8282
attendee = [
8383
Permission.EVENT_VIEW,
8484
Permission.EVENT_EXHIBITION_CONTACT,
85-
Permission.EVENT_CHAT_DIRECT,
8685
]
8786
viewer = attendee + [Permission.ROOM_VIEW, Permission.ROOM_CHAT_READ]
8887
participant = viewer + [
@@ -159,6 +158,19 @@ def default_grants():
159158
}
160159

161160

161+
VIDEO_TRAIT_ROLE_MAP = {
162+
"video_stage_manager": "video_stage_manager",
163+
"video_channel_manager": "video_channel_manager",
164+
"video_direct_messaging": "video_direct_messaging",
165+
"video_announcement_manager": "video_announcement_manager",
166+
"video_user_viewer": "video_user_viewer",
167+
"video_user_moderator": "video_user_moderator",
168+
"video_room_manager": "video_room_manager",
169+
"video_kiosk_manager": "video_kiosk_manager",
170+
"video_config_manager": "video_config_manager",
171+
}
172+
173+
162174
FEATURE_FLAGS = [
163175
"schedule-control",
164176
"iframe-player",
@@ -1388,6 +1400,16 @@ def decode_token(self, token, allow_raise=False):
13881400
if exc and allow_raise:
13891401
raise exc
13901402

1403+
def _get_trait_grants_with_defaults(self):
1404+
base_trait_grants = self.trait_grants if self.trait_grants is not None else default_grants()
1405+
slug = getattr(self, "slug", None) or getattr(self, "id", None)
1406+
if not slug:
1407+
return base_trait_grants
1408+
augmented = dict(base_trait_grants)
1409+
for role, trait_name in VIDEO_TRAIT_ROLE_MAP.items():
1410+
augmented.setdefault(role, [f"eventyay-video-event-{slug}-{trait_name.replace('_', '-')}"])
1411+
return augmented
1412+
13911413
def has_permission_implicit(
13921414
self,
13931415
*,
@@ -1397,7 +1419,7 @@ def has_permission_implicit(
13971419
allow_empty_traits=True,
13981420
):
13991421
# Ensure trait_grants and roles are not None - use defaults if missing
1400-
event_trait_grants = self.trait_grants if self.trait_grants is not None else default_grants()
1422+
event_trait_grants = self._get_trait_grants_with_defaults()
14011423
event_roles = self.roles if self.roles is not None else default_roles()
14021424

14031425
for role, required_traits in event_trait_grants.items():
@@ -1513,7 +1535,7 @@ def get_all_permissions(self, user):
15131535
allow_empty_traits = user.type == User.UserType.PERSON
15141536

15151537
# Ensure trait_grants and roles are not None
1516-
event_trait_grants = self.trait_grants if self.trait_grants is not None else default_grants()
1538+
event_trait_grants = self._get_trait_grants_with_defaults()
15171539
event_roles = self.roles if self.roles is not None else default_roles()
15181540

15191541
for role, required_traits in event_trait_grants.items():

app/eventyay/base/models/organizer.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,52 @@ class Meta:
433433
default=False,
434434
)
435435

436+
can_video_create_stages = models.BooleanField(
437+
default=False,
438+
verbose_name=_('Video: Can create stages'),
439+
help_text=_('Allows creating livestream stages inside Eventyay Video.'),
440+
)
441+
can_video_create_channels = models.BooleanField(
442+
default=False,
443+
verbose_name=_('Video: Can create channels'),
444+
help_text=_('Allows creating chat/video channels inside Eventyay Video.'),
445+
)
446+
can_video_direct_message = models.BooleanField(
447+
default=False,
448+
verbose_name=_('Video: Can send direct messages'),
449+
help_text=_('Grants permission to open new direct message conversations.'),
450+
)
451+
can_video_manage_announcements = models.BooleanField(
452+
default=False,
453+
verbose_name=_('Video: Can create announcements'),
454+
help_text=_('Allows posting announcements in the Eventyay Video interface.'),
455+
)
456+
can_video_view_users = models.BooleanField(
457+
default=False,
458+
verbose_name=_('Video: Can view users'),
459+
help_text=_('Allows access to the user directory in Eventyay Video.'),
460+
)
461+
can_video_manage_users = models.BooleanField(
462+
default=False,
463+
verbose_name=_('Video: Can message, ban, and silence users'),
464+
help_text=_('Allows moderating users (ban, silence, reactivate) in Eventyay Video.'),
465+
)
466+
can_video_manage_rooms = models.BooleanField(
467+
default=False,
468+
verbose_name=_('Video: Can create and edit rooms'),
469+
help_text=_('Allows editing and deleting rooms inside Eventyay Video.'),
470+
)
471+
can_video_manage_kiosks = models.BooleanField(
472+
default=False,
473+
verbose_name=_('Video: Can create and edit kiosks'),
474+
help_text=_('Allows managing kiosk displays inside Eventyay Video.'),
475+
)
476+
can_video_manage_configuration = models.BooleanField(
477+
default=False,
478+
verbose_name=_('Video: Can edit event configuration'),
479+
help_text=_('Allows editing the global Eventyay Video configuration.'),
480+
)
481+
436482
@cached_property
437483
def permission_set_display(self) -> set:
438484
"""The same as :meth:`permission_set`, but with human-readable names."""

app/eventyay/base/models/world.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ def default_roles():
3333
attendee = [
3434
Permission.WORLD_VIEW,
3535
Permission.WORLD_EXHIBITION_CONTACT,
36-
Permission.WORLD_CHAT_DIRECT,
3736
]
3837
viewer = attendee + [Permission.ROOM_VIEW, Permission.ROOM_CHAT_READ]
3938
participant = viewer + [
@@ -88,6 +87,15 @@ def default_roles():
8887
)
8988
apiuser = admin + [Permission.WORLD_API, Permission.WORLD_SECRETS]
9089
scheduleuser = [Permission.WORLD_API]
90+
video_stage_manager = [Permission.EVENT_ROOMS_CREATE_STAGE]
91+
video_channel_manager = [Permission.EVENT_ROOMS_CREATE_CHAT, Permission.EVENT_ROOMS_CREATE_BBB]
92+
video_direct_messaging = [Permission.EVENT_CHAT_DIRECT]
93+
video_announcement_manager = [Permission.EVENT_ANNOUNCE]
94+
video_user_viewer = [Permission.EVENT_USERS_LIST]
95+
video_user_moderator = [Permission.EVENT_USERS_MANAGE]
96+
video_room_manager = [Permission.ROOM_UPDATE, Permission.ROOM_DELETE]
97+
video_kiosk_manager = [Permission.EVENT_KIOSKS_MANAGE]
98+
video_config_manager = [Permission.EVENT_UPDATE]
9199
return {
92100
"attendee": attendee,
93101
"viewer": viewer,
@@ -99,6 +107,15 @@ def default_roles():
99107
"admin": admin,
100108
"apiuser": apiuser,
101109
"scheduleuser": scheduleuser,
110+
"video_stage_manager": video_stage_manager,
111+
"video_channel_manager": video_channel_manager,
112+
"video_direct_messaging": video_direct_messaging,
113+
"video_announcement_manager": video_announcement_manager,
114+
"video_user_viewer": video_user_viewer,
115+
"video_user_moderator": video_user_moderator,
116+
"video_room_manager": video_room_manager,
117+
"video_kiosk_manager": video_kiosk_manager,
118+
"video_config_manager": video_config_manager,
102119
}
103120

104121

app/eventyay/base/services/event.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,8 @@ def get_event_config_for_user(event, user):
218218
for p in event_perm_values:
219219
if p == "event.view":
220220
world_aliases.append("world:view")
221+
elif p == "event.update":
222+
world_aliases.append("world:update")
221223
elif p.startswith("event:"):
222224
world_aliases.append("world:" + p[len("event:"):])
223225
merged_permissions = sorted(set(event_perm_values) | set(world_aliases))

app/eventyay/control/forms/organizer_forms/team_form.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ class Meta:
4141
'is_reviewer',
4242
'force_hide_speaker_names',
4343
'limit_tracks',
44+
'can_video_create_stages',
45+
'can_video_create_channels',
46+
'can_video_direct_message',
47+
'can_video_manage_announcements',
48+
'can_video_view_users',
49+
'can_video_manage_users',
50+
'can_video_manage_rooms',
51+
'can_video_manage_kiosks',
52+
'can_video_manage_configuration',
4453
]
4554
widgets = {
4655
'limit_events': forms.CheckboxSelectMultiple(
@@ -91,6 +100,15 @@ def clean(self):
91100
'can_change_vouchers',
92101
'can_change_submissions',
93102
'is_reviewer',
103+
'can_video_create_stages',
104+
'can_video_create_channels',
105+
'can_video_direct_message',
106+
'can_video_manage_announcements',
107+
'can_video_view_users',
108+
'can_video_manage_users',
109+
'can_video_manage_rooms',
110+
'can_video_manage_kiosks',
111+
'can_video_manage_configuration',
94112
)
95113
if not any(data.get(permission) for permission in permissions):
96114
error = forms.ValidationError(

app/eventyay/core/permissions.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class Permission(Enum):
1515
EVENT_ROOMS_CREATE_POSTER = "event:rooms.create.poster"
1616
EVENT_USERS_LIST = "event:users.list"
1717
EVENT_USERS_MANAGE = "event:users.manage"
18+
EVENT_KIOSKS_MANAGE = "event:kiosks.manage"
1819
EVENT_CHAT_DIRECT = "event:chat.direct"
1920
EVENT_EXHIBITION_CONTACT = "event:exhibition.contact"
2021
EVENT_CONNECTIONS_UNLIMITED = "event:connections.unlimited"
@@ -76,4 +77,33 @@ class Permission(Enum):
7677
Permission.ROOM_POLL_VOTE.value,
7778
Permission.ROOM_VIEW.value,
7879
],
80+
"video_stage_manager": [
81+
Permission.EVENT_ROOMS_CREATE_STAGE.value,
82+
],
83+
"video_channel_manager": [
84+
Permission.EVENT_ROOMS_CREATE_CHAT.value,
85+
Permission.EVENT_ROOMS_CREATE_BBB.value,
86+
],
87+
"video_direct_messaging": [
88+
Permission.EVENT_CHAT_DIRECT.value,
89+
],
90+
"video_announcement_manager": [
91+
Permission.EVENT_ANNOUNCE.value,
92+
],
93+
"video_user_viewer": [
94+
Permission.EVENT_USERS_LIST.value,
95+
],
96+
"video_user_moderator": [
97+
Permission.EVENT_USERS_MANAGE.value,
98+
],
99+
"video_room_manager": [
100+
Permission.ROOM_UPDATE.value,
101+
Permission.ROOM_DELETE.value,
102+
],
103+
"video_kiosk_manager": [
104+
Permission.EVENT_KIOSKS_MANAGE.value,
105+
],
106+
"video_config_manager": [
107+
Permission.EVENT_UPDATE.value,
108+
],
79109
}

app/eventyay/eventyay_common/tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from .base_tasks import CreateWorldTask, SendEventTask
3232
from .billing_invoice import InvoicePDFGenerator
3333
from .schemas.billing import CollectBillingResponse
34+
from .video.permissions import build_video_traits_for_event
3435

3536
logger = logging.getLogger(__name__)
3637

@@ -120,13 +121,15 @@ def _create_world(payload: dict, headers: dict) -> Optional[dict]:
120121
return None
121122

122123
event_slug = event_data.get('id', '')
124+
video_traits = build_video_traits_for_event(event_slug)
123125
payload = {
124126
'id': event_slug,
125127
'title': event_data.get('title', ''),
126128
'timezone': event_data.get('timezone', ''),
127129
'locale': event_data.get('locale', ''),
128130
'traits': {
129131
'attendee': 'eventyay-video-event-{}'.format(event_slug),
132+
**video_traits,
130133
},
131134
}
132135

0 commit comments

Comments
 (0)