Skip to content

Commit

Permalink
Merge branch 'development' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
untari authored Aug 1, 2024
2 parents 3cc5945 + 2fa2037 commit 59bd13b
Show file tree
Hide file tree
Showing 22 changed files with 2,956 additions and 539 deletions.
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
2 changes: 1 addition & 1 deletion webapp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if (ENV_DEVELOPMENT || !window.venueless) {
feedback: `${httpProtocol}//${hostname}:8443/_feedback/`,
},
defaultLocale: 'en',
locales: ['en', 'de', 'pt_BR'],
locales: ['en', 'de', 'pt_BR', 'ar', 'fr', 'es', 'uk', 'ru'],
theme: {
logo: {
url: "/eventyay-video-logo.svg",
Expand Down
108 changes: 54 additions & 54 deletions webapp/src/components/ChatContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,55 +40,16 @@ export async function contentToPlainText (content) {
}
const generateHTML = (input) => {
if (!input) return
return markdownIt.renderInline(input)
if (!input) return
return markdownIt.renderInline(input)
}
export default {
functional: true,
props: {
content: String
},
data() {
return {
selectedUser: null
}
},
methods: {
showUserModal(user) {
this.selectedUser = user
this.$modal.show('user-modal')
},
closeUserModal() {
this.$modal.hide('user-modal')
}
},
render(createElement, ctx) {
const parts = ctx.props.content.split(mentionRegex)
const content = parts.map(string => {
if (string.match(mentionRegex)) {
const user = ctx.parent.$store.state.chat.usersLookup[string.slice(1)]
if (user) {
return { user }
}
}
return { html: generateHTML(string) }
})
return content.map(part => {
if (part.user) {
return createElement('span', {
class: 'mention',
on: {
click: () => ctx.parent.showUserModal(part.user)
}
}, getUserName(part.user))
}
return createElement('span', { domProps: { innerHTML: part.html } })
})
},
components: {
'user-modal': {
template: `
functional: true,
components: {
props: ['selectedUser'],
'user-modal': {
template: `
<modal name="user-modal" height="auto" @before-close="closeUserModal">
<div class="modal-content">
<h3>User Information</h3>
Expand All @@ -99,13 +60,52 @@ export default {
</div>
</modal>
`,
props: ['selectedUser'],
methods: {
closeUserModal() {
this.$emit('close')
}
}
}
}
methods: {
closeUserModal () {
this.$emit('close')
}
}
}
},
props: {
content: String
},
data () {
return {
selectedUser: null
}
},
methods: {
showUserModal (user) {
this.selectedUser = user
this.$modal.show('user-modal')
},
closeUserModal () {
this.$modal.hide('user-modal')
}
},
render (createElement, ctx) {
const parts = ctx.props.content.split(mentionRegex)
const content = parts.map(string => {
if (string.match(mentionRegex)) {
const user = ctx.parent.$store.state.chat.usersLookup[string.slice(1)]
if (user) {
return { user }
}
}
return { html: generateHTML(string) }
})
return content.map(part => {
if (part.user) {
return createElement('span', {
class: 'mention',
on: {
click: () => ctx.parent.showUserModal(part.user)
}
}, getUserName(part.user))
}
return createElement('span', { domProps: { innerHTML: part.html } })
})
}
}
</script>
30 changes: 15 additions & 15 deletions webapp/src/components/ChatInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,20 @@ export default {
}
}
},
watch: {
async 'autocomplete.search' (search) {
// TODO debounce?
if (!this.autocomplete) return
if (this.autocomplete.type === 'mention') {
const { results } = await api.call('user.list.search', {search_term: search, page: 1, include_banned: false})
this.autocomplete.options = results
// if (results.length === 1) {
// this.autocomplete.selected = 0
// this.handleMention()
// }
}
}
},
mounted () {
this.quill = new Quill(this.$refs.editor, {
debug: ENV_DEVELOPMENT ? 'info' : 'warn',
Expand Down Expand Up @@ -111,20 +125,6 @@ export default {
}
}
},
watch: {
async 'autocomplete.search' (search) {
// TODO debounce?
if (!this.autocomplete) return
if (this.autocomplete.type === 'mention') {
const { results } = await api.call('user.list.search', {search_term: search, page: 1, include_banned: false})
this.autocomplete.options = results
// if (results.length === 1) {
// this.autocomplete.selected = 0
// this.handleMention()
// }
}
}
},
methods: {
onTextChange (delta, oldDelta, source) {
if (source !== 'user') return
Expand Down Expand Up @@ -384,4 +384,4 @@ export default {
padding: 1px
.name
ellipsis()
</style>
</style>
2 changes: 1 addition & 1 deletion webapp/src/components/ChatMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,4 @@ export default {
.display-name
color: $clr-disabled-text-light
text-decoration: line-through
</style>
</style>
Loading

0 comments on commit 59bd13b

Please sign in to comment.