Compare commits

...

4 Commits

Author SHA1 Message Date
lebaudantoine aa96d04b5e wip status while recording 2024-09-13 13:18:07 +02:00
lebaudantoine 5321ade8a5 wip send email with link to doc 2024-09-13 10:14:05 +02:00
lebaudantoine 5d7f9e1325 🚧(backend) poc email all owners of a room
Dirty code, missing proper exceptions catching,
permissions etc.

Try to deploy it on staging, see if email are sent.
2024-09-12 15:25:43 +02:00
lebaudantoine 94c19d026d wip update tag 2024-09-12 11:21:44 +02:00
13 changed files with 147 additions and 6 deletions
+1
View File
@@ -42,3 +42,4 @@ LIVEKIT_API_SECRET=secret
LIVEKIT_API_KEY=devkey
LIVEKIT_API_URL=http://localhost:7880
ALLOW_UNREGISTERED_ROOMS=False
WORKER_SECRET=DevPurposeOnly
+1 -1
Submodule secrets updated: f5fbc16e6e...1a016aca31
+38
View File
@@ -1,6 +1,8 @@
"""API endpoints"""
import smtplib
import uuid
import requests
from django.conf import settings
from django.db.models import Q
@@ -14,6 +16,9 @@ from rest_framework import (
pagination,
viewsets,
)
from rest_framework import (
permissions as drf_permissions,
)
from rest_framework import (
response as drf_response,
)
@@ -249,6 +254,39 @@ class RoomViewSet(
},
)
@decorators.action(
methods=["POST"],
detail=True,
url_path="summary",
permission_classes=[drf_permissions.AllowAny],
)
def summary(self, request, pk=None): # pylint: disable=unused-argument
"""Wip"""
secret = self.request.data.get("secret", None)
if secret is None or secret != settings.WORKER_SECRET:
return drf_response.Response({"message": "Invalid secret"}, status=403)
summary = self.request.data.get("summary", None)
transcript = self.request.data.get("transcript", None)
instance = self.get_object()
owners = instance.get_owners()
email = owners[0].email
url = f"https://dinum-pad-dev.osc-fr1.scalingo.io/summary?email={email}"
headers = {
"Content-Type": "text/plain"
}
response = requests.post(url, headers=headers, data=summary, allow_redirects=False)
try:
instance.email_summary(summary=summary, transcript=transcript, owners=owners, link=response.headers['Location'])
except smtplib.SMTPException:
return drf_response.Response({"message": "Error"}, status=500)
return drf_response.Response({"message": "Webhook data received"}, status=200)
class ResourceAccessListModelMixin:
"""List mixin for resource access API."""
+50 -2
View File
@@ -11,10 +11,14 @@ 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.template.loader import render_to_string
from django.utils.functional import lazy
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
from django.core.mail import EmailMessage
from django.core.files.base import ContentFile
from timezone_field import TimeZoneField
logger = getLogger(__name__)
@@ -150,11 +154,28 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
def __str__(self):
return self.email or self.admin_email or str(self.id)
def email_user(self, subject, message, from_email=None, **kwargs):
def email_user(self, subject, transcript, from_email=None, **kwargs):
"""Email this user."""
if not self.email:
raise ValueError("User has no email address.")
mail.send_mail(subject, message, from_email, [self.email], **kwargs)
# mail.send_mail(subject, message, from_email, [self.email], **kwargs)
email = EmailMessage(
subject,
kwargs['html_message'],
from_email,
[self.email],
)
# email.attach_alternative(kwargs['html_message'], "text/html")
transcript = transcript or ''
wip_file = ContentFile(transcript, 'transcript.txt')
email.content_subtype = "html"
email.attach(wip_file.name, wip_file.read(), 'text/plain')
email.send()
def get_teams(self):
"""
@@ -325,3 +346,30 @@ class Room(Resource):
else:
raise ValidationError({"name": f'Room name "{self.name:s}" is reserved.'})
super().clean_fields(exclude=exclude)
def get_owners(self):
"""Fetch the user who is the owner of the room."""
owner_accesses = self.accesses.filter(role=RoleChoices.OWNER)
return [access.user for access in owner_accesses]
def email_summary(self, owners, summary, transcript, link):
"""Wip"""
template_vars = {
"title": _("A new summary is ready"),
"room": self.slug,
"summary": summary,
"link": link,
}
msg_plain = render_to_string("mail/text/summary.txt", template_vars)
msg_html = render_to_string("mail/html/summary.html", template_vars)
for owner in owners:
owner.email_user(
subject=_("A new summary is ready"),
transcript=transcript,
from_email=settings.EMAIL_FROM,
html_message=msg_html,
fail_silently=False,
)
Binary file not shown.
Binary file not shown.
+4
View File
@@ -258,6 +258,7 @@ class Base(Configuration):
EMAIL_HOST_PASSWORD = values.Value(None)
EMAIL_PORT = values.PositiveIntegerValue(None)
EMAIL_USE_TLS = values.BooleanValue(False)
EMAIL_USE_SSL = values.BooleanValue(False)
EMAIL_FROM = values.Value("from@example.com")
AUTH_USER_MODEL = "core.User"
@@ -371,6 +372,9 @@ class Base(Configuration):
ANALYTICS_KEY = values.Value(
None, environ_name="ANALYTICS_KEY", environ_prefix=None
)
WORKER_SECRET = values.Value(
None, environ_name="WORKER_SECRET", environ_prefix=None
)
# pylint: disable=invalid-name
@property
@@ -1,13 +1,22 @@
import { useRoomContext } from '@livekit/components-react'
import { useEffect, useState } from 'react'
import { RoomEvent } from 'livekit-client'
import { egressStore } from '@/stores/egress.tsx'
import { useSnapshot } from 'valtio'
export const RecordingIndicator = () => {
const room = useRoomContext()
const [isRecording, setIsRecording] = useState(room.isRecording)
const egressSnap = useSnapshot(egressStore)
const egressIsStopping = egressSnap.egressIsStopping
useEffect(() => {
const handleRecordingStatusChanges = (isRecording: boolean) => {
if (!isRecording) {
egressStore.egressIsStopping = false
}
setIsRecording(isRecording)
}
room.on(RoomEvent.RecordingStatusChanged, handleRecordingStatusChanges)
@@ -16,6 +25,18 @@ export const RecordingIndicator = () => {
}
}, [room])
const getStatus = () => {
if (egressIsStopping) {
return 'saving recording'
}
if (isRecording) {
return 'recording'
}
if (!isRecording) {
return 'available'
}
}
return (
<div
style={{
@@ -26,7 +47,7 @@ export const RecordingIndicator = () => {
width: '100%',
}}
>
Room is recording: {isRecording ? 'yes' : 'no'}
Room status: {getStatus()}
</div>
)
}
@@ -17,6 +17,7 @@ export const RecordingMenuItem = () => {
const handleAction = async () => {
if (egressId) {
setIsPending(true)
egressStore.egressIsStopping = true
const response = await stopRecordingRoom(egressId)
console.log(response)
egressStore.egressId = undefined
+2
View File
@@ -2,8 +2,10 @@ import { proxy } from 'valtio'
type State = {
egressId: string | undefined
egressIsStopping: boolean
}
export const egressStore = proxy<State>({
egressId: undefined,
egressIsStopping: false,
})
@@ -1,7 +1,7 @@
image:
repository: lasuite/meet-backend
pullPolicy: Always
tag: "main"
tag: "v-hackathon"
backend:
migrateJobAnnotations:
@@ -92,6 +92,10 @@ backend:
secretKeyRef:
name: backend
key: LIVEKIT_API_KEY
WORKER_SECRET:
secretKeyRef:
name: backend
key: WORKER_SECRET
LIVEKIT_API_URL: https://livekit-staging.beta.numerique.gouv.fr
ANALYTICS_KEY: Roi1k6IAc2DEqHB0
ALLOW_UNREGISTERED_ROOMS: False
@@ -108,7 +112,7 @@ frontend:
image:
repository: lasuite/meet-frontend
pullPolicy: Always
tag: "main"
tag: "v-hackathon"
ingress:
enabled: true
+3
View File
@@ -11,3 +11,6 @@ stringData:
OIDC_RP_CLIENT_SECRET: {{ .Values.oidc.clientSecret }}
LIVEKIT_API_SECRET: {{ .Values.livekitApi.secret }}
LIVEKIT_API_KEY: {{ .Values.livekitApi.key }}
{{- if .Values.workerSecret }}
WORKER_SECRET: {{ .Values.workerSecret }}
{{- end }}
+19
View File
@@ -0,0 +1,19 @@
<mjml>
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<mj-wrapper css-class="wrapper" padding="0 40px 40px 40px">
<mj-section mj-class="bg--white-100" padding="30px 20px 60px 20px">
<mj-column>
<mj-text font-size="14px">
<p>Dear user,</p>
<p>The meeting <strong>{{room}}</strong> has been successfully transcribed and summarized.</p>
</mj-text>
<mj-button href="{{link}}" font-size="14px" background-color="#346DB7" color="white">
Get your summary
</mj-button>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
<mj-include path="./partial/footer.mjml" />
</mjml>