Skip to content

Commit

Permalink
Sync favorite talk list to Talk component (#176)
Browse files Browse the repository at this point in the history
* implement call to talk when user add favourite talk for sync favs

---------

Co-authored-by: lcduong <[email protected]>
Co-authored-by: Mario Behling <[email protected]>
  • Loading branch information
3 people authored Aug 1, 2024
1 parent 824b3ef commit 7b67eec
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
1 change: 1 addition & 0 deletions server/venueless/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
re_path("worlds/(?P<world_id>[^/]+)/delete_user/?$", views.delete_user),
path("worlds/<str:world_id>/", include(world_router.urls)),
path("worlds/<str:world_id>/theme", views.WorldThemeView.as_view()),
path("worlds/<str:world_id>/favourite-talk/", views.UserFavouriteView.as_view()),
]
59 changes: 59 additions & 0 deletions server/venueless/api/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import json
import logging
from contextlib import suppress
from urllib.parse import urlparse
import jwt

from asgiref.sync import async_to_sync
from django.core import exceptions
from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import now
from django.views import View
from rest_framework import viewsets
from rest_framework.authentication import get_authorization_header
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.views import APIView
Expand Down Expand Up @@ -105,6 +110,60 @@ def get(self, request, **kwargs):
return Response("error happened when trying to get theme data of world: " + kwargs["world_id"], status=503)


class UserFavouriteView(APIView):

permission_classes = []

@staticmethod
def post(request, *args, **kwargs) -> JsonResponse:
"""
Handle POST requests to add talks to the user's favourite list.
Being called by eventyay-talk, authenticate by bearer token.
"""
try:
talk_list = json.loads(request.body.decode())
user_code = UserFavouriteView.get_uid_from_token(request, kwargs["world_id"])
user = User.objects.get(token_id=user_code)
if not user_code or not user:
# user not created yet, no error should be returned
logger.error("User not found for adding favourite talks.")
return JsonResponse([], safe=False, status=200)
if user.client_state is None:
# If it's None, create a new dictionary with schedule.favs field
user.client_state = {
'schedule': {
'favs': talk_list
}
}
else:
# If client_state is not None, check if 'schedule' field exists
if 'schedule' not in user.client_state:
# If 'schedule' field doesn't exist, create it
user.client_state['schedule'] = {'favs': talk_list}
else:
# If 'schedule' field exists, update the 'favs' field
user.client_state['schedule']['favs'] = talk_list
user.save()
return JsonResponse(talk_list, safe=False, status=200)
except Exception as e:
logger.error("error happened when trying to add fav talks: %s", kwargs["world_id"])
logger.error(e)
# Since this is called from background so no error should be returned
return JsonResponse([], safe=False, status=200)

@staticmethod
def get_uid_from_token(request, world_id):
world = get_object_or_404(World, id=world_id)
auth_header = get_authorization_header(request).split()
if auth_header and auth_header[0].lower() == b'bearer':
if len(auth_header) == 1:
raise exceptions.AuthenticationFailed('Invalid token header. No credentials provided.')
elif len(auth_header) > 2:
raise exceptions.AuthenticationFailed(
'Invalid token header. Token string should not contain spaces.')
token_decode = world.decode_token(token=auth_header[1])
return token_decode.get("uid")


def get_domain(path):
if not path:
Expand Down
45 changes: 44 additions & 1 deletion server/venueless/core/services/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import json
import jwt
import operator
import datetime as dt
import requests
from collections import namedtuple
from datetime import timedelta
from functools import reduce
Expand All @@ -8,13 +12,14 @@
from django.core.paginator import InvalidPage, Paginator
from django.db.models import Q
from django.db.transaction import atomic
from django.shortcuts import get_object_or_404
from django.utils.timezone import now

from ...live.channels import GROUP_USER
from ..models import AuditLog
from ..models.auth import User
from ..models.room import AnonymousInvite
from ..models.world import WorldView
from ..models.world import WorldView, World
from ..permissions import Permission


Expand Down Expand Up @@ -294,6 +299,9 @@ def update_user(
):
user.client_state = data.get("client_state")
save_fields.append("client_state")
# Call talk component to update favs talks
if user.token_id is not None:
update_fav_talks(user.token_id, data["client_state"], world_id)

if save_fields:
user.save(update_fields=save_fields)
Expand All @@ -307,6 +315,41 @@ def update_user(
)


def update_fav_talks(user_token_id, talks, world_id):
try:
talk_list = talks.get('schedule').get('favs')
world = get_object_or_404(World, id=world_id)
jwt_config = world.config.get("JWT_secrets")
if not jwt_config:
return
talk_token = get_user_video_token(user_token_id,jwt_config[0])

talk_config = world.config.get("pretalx")
if not talk_config:
return
talk_url = talk_config.get('domain') + "/api/events/" + talk_config.get('event') + "/favourite-talk/"
header = {
"Content-Type": "application/json",
"Authorization": f"Bearer {talk_token}"
}
requests.post(talk_url, data=json.dumps(talk_list), headers=header)
except World.DoesNotExist or Exception:
pass


def get_user_video_token(user_code, video_settings):
iat = dt.datetime.utcnow()
exp = iat + dt.timedelta(days=30)
payload = {
"iss": video_settings.get('issuer'),
"aud": video_settings.get('audience'),
"exp": exp,
"iat": iat,
"uid": user_code,
}
token = jwt.encode(payload, video_settings.get('secret'), algorithm="HS256")
return token

def start_view(user: User, delete=False):
# The majority of WorldView that go "abandoned" (i.e. ``end`` is never set) are likely caused by server
# crashes or restarts, in which case ``end`` can't be set. However, after a server crash, the client
Expand Down

0 comments on commit 7b67eec

Please sign in to comment.