Skip to content

Commit 0dd1db6

Browse files
odkhanglcduongmariobehling
authored
Setup attendee role and Configure video settings for talks (#285)
* change create_world api response * Update code * Update code * Update code * Fix isort, flake8 in pipeline * Config attendee role * Fix black in pipeline * Update code * Update code * Update code * Configure video settings for talks * Fix black in pipeline * Update code * rework code * update get utc time function --------- Co-authored-by: lcduong <[email protected]> Co-authored-by: Mario Behling <[email protected]>
1 parent 08f252b commit 0dd1db6

File tree

3 files changed

+164
-1
lines changed

3 files changed

+164
-1
lines changed

server/venueless/api/task.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import datetime as dt
2+
import logging
3+
import uuid
4+
from http import HTTPStatus
5+
6+
import jwt
7+
import requests
8+
from celery import shared_task
9+
from django.conf import settings
10+
11+
from venueless.core.models.auth import ShortToken
12+
from venueless.core.models.world import World
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
def generate_video_token(world, days, number, traits, long=False):
18+
"""
19+
Generate video token
20+
:param world: World object
21+
:param days: A integer representing the number of days the token is valid
22+
:param number: A integer representing the number of tokens to generate
23+
:param traits: A dictionary representing the traits of the token
24+
:param long: A boolean representing if the token is long or short
25+
:return: A list of tokens
26+
"""
27+
jwt_secrets = world.config.get("JWT_secrets", [])
28+
if not jwt_secrets:
29+
logger.error("JWT_secrets is missing or empty in the configuration")
30+
return
31+
jwt_config = jwt_secrets[0]
32+
secret = jwt_config.get("secret")
33+
audience = jwt_config.get("audience")
34+
issuer = jwt_config.get("issuer")
35+
iat = dt.datetime.now(dt.timezone.utc)
36+
exp = iat + dt.timedelta(days=days)
37+
result = []
38+
bulk_create = []
39+
for _ in range(number):
40+
payload = {
41+
"iss": issuer,
42+
"aud": audience,
43+
"exp": exp,
44+
"iat": iat,
45+
"uid": str(uuid.uuid4()),
46+
"traits": traits,
47+
}
48+
token = jwt.encode(payload, secret, algorithm="HS256")
49+
if long:
50+
result.append(token)
51+
else:
52+
st = ShortToken(world=world, long_token=token, expires=exp)
53+
result.append(st.short_token)
54+
bulk_create.append(st)
55+
56+
if not long:
57+
ShortToken.objects.bulk_create(bulk_create)
58+
return result
59+
60+
61+
def generate_talk_token(video_settings, video_tokens, event_slug):
62+
"""
63+
Generate talk token
64+
:param video_settings: A dictionary representing the video settings
65+
:param video_tokens: A list of video tokens
66+
:param event_slug: A string representing the event slug
67+
:return: A token
68+
"""
69+
iat = dt.datetime.now(dt.timezone.utc)
70+
exp = iat + dt.timedelta(days=30)
71+
payload = {
72+
"exp": exp,
73+
"iat": iat,
74+
"video_tokens": video_tokens,
75+
"slug": event_slug,
76+
}
77+
token = jwt.encode(payload, video_settings.get("secret"), algorithm="HS256")
78+
return token
79+
80+
81+
@shared_task(bind=True, max_retries=5, default_retry_delay=60)
82+
def configure_video_settings_for_talks(
83+
self, world_id, days, number, traits, long=False
84+
):
85+
"""
86+
Configure video settings for talks
87+
:param self: instance of the task
88+
:param world_id: A integer representing the world id
89+
:param days: A integer representing the number of days the token is valid
90+
:param number: A integer representing the number of tokens to generate
91+
:param traits: A dictionary representing the traits of the token
92+
:param long: A boolean representing if the token is long or short
93+
"""
94+
try:
95+
if not isinstance(world_id, str) or not world_id.isalnum():
96+
raise ValueError("Invalid world_id format")
97+
world = World.objects.get(id=world_id)
98+
event_slug = world_id
99+
jwt_secrets = world.config.get("JWT_secrets", [])
100+
if not jwt_secrets:
101+
logger.error("JWT_secrets is missing or empty in the configuration")
102+
return
103+
jwt_config = jwt_secrets[0]
104+
video_tokens = generate_video_token(world, days, number, traits, long)
105+
talk_token = generate_talk_token(jwt_config, video_tokens, event_slug)
106+
header = {
107+
"Content-Type": "application/json",
108+
"Authorization": f"Bearer {talk_token}",
109+
}
110+
payload = {
111+
"video_settings": {
112+
"audience": jwt_config.get("audience"),
113+
"issuer": jwt_config.get("issuer"),
114+
"secret": jwt_config.get("secret"),
115+
}
116+
}
117+
requests.post(
118+
"{}/api/configure-video-settings/".format(settings.EVENTYAY_TALK_BASE_PATH),
119+
json=payload,
120+
headers=header,
121+
)
122+
world.config["pretalx"] = {
123+
"event": event_slug,
124+
"domain": "{}".format(settings.EVENTYAY_TALK_BASE_PATH),
125+
"pushed": dt.datetime.now(dt.timezone.utc).isoformat(),
126+
"connected": True,
127+
}
128+
world.save()
129+
except requests.exceptions.ConnectionError as e:
130+
logger.error("Connection error: %s", e)
131+
self.retry(exc=e)
132+
except requests.exceptions.HTTPError as e:
133+
if e.response.status_code in (
134+
HTTPStatus.UNAUTHORIZED.value,
135+
HTTPStatus.FORBIDDEN.value,
136+
HTTPStatus.NOT_FOUND.value,
137+
):
138+
logger.error("Non-retryable error: %s", e)
139+
raise
140+
logger.error("HTTP error: %s", e)
141+
self.retry(exc=e)
142+
except ValueError as e:
143+
logger.error("Error configuring video settings: %s", e)

server/venueless/api/views.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from venueless.core.services.world import notify_schedule_change, notify_world_change
3232

3333
from ..core.models import Room, World
34+
from .task import configure_video_settings_for_talks
3435
from .utils import get_protocol
3536

3637
logger = logging.getLogger(__name__)
@@ -149,6 +150,18 @@ def post(request, *args, **kwargs) -> JsonResponse:
149150

150151
title = titles.get(locale) or titles.get("en") or title_default
151152

153+
attendee_trait_grants = request.data.get("traits", {}).get("attendee", "")
154+
if not isinstance(attendee_trait_grants, str):
155+
raise ValidationError("Attendee traits must be a string")
156+
157+
trait_grants = {
158+
"admin": ["admin"],
159+
"attendee": (
160+
[attendee_trait_grants] if attendee_trait_grants else ["attendee"]
161+
),
162+
"scheduleuser": ["schedule-update"],
163+
}
164+
152165
# if world already exists, update it, otherwise create a new world
153166
world_id = request.data.get("id")
154167
domain_path = "{}{}/{}".format(
@@ -165,6 +178,7 @@ def post(request, *args, **kwargs) -> JsonResponse:
165178
world.domain = domain_path
166179
world.locale = request.data.get("locale") or "en"
167180
world.timezone = request.data.get("timezone") or "UTC"
181+
world.trait_grants = trait_grants
168182
world.save()
169183
else:
170184
world = World.objects.create(
@@ -174,8 +188,11 @@ def post(request, *args, **kwargs) -> JsonResponse:
174188
locale=request.data.get("locale") or "en",
175189
timezone=request.data.get("timezone") or "UTC",
176190
config=config,
191+
trait_grants=trait_grants,
177192
)
178-
193+
configure_video_settings_for_talks.delay(
194+
world_id, days=30, number=1, traits=["schedule-update"], long=True
195+
)
179196
site_url = settings.SITE_URL
180197
protocol = get_protocol(site_url)
181198
world.domain = "{}://{}".format(protocol, domain_path)

server/venueless/settings.py

+3
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@
202202
MEDIA_URL = os.getenv(
203203
"VENUELESS_MEDIA_URL", config.get("urls", "media", fallback="/media/")
204204
)
205+
EVENTYAY_TALK_BASE_PATH = config.get(
206+
"urls", "eventyay-talk", fallback="https://app-test.eventyay.com/talk"
207+
)
205208

206209
WEBSOCKET_PROTOCOL = os.getenv(
207210
"VENUELESS_WEBSOCKET_PROTOCOL",

0 commit comments

Comments
 (0)