mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-20 07:12:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 13c75697c7 |
+1
-11
@@ -10,19 +10,9 @@ and this project adheres to
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- ✨(backend) accept form-urlencoded on the user token endpoint
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- 📝(docs) fix minor typos in comments and docstrings
|
|
||||||
- ⬆️(backend) bump sqlparse from 0.5.5 to 0.6.0
|
|
||||||
|
|
||||||
## [1.27.0] - 2026-08-14
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- 🔥(frontend) drop unused vendored ConnectionObserver
|
- 🔥(frontend) drop unused vendored ConnectionObserver
|
||||||
- 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines
|
- 🐛(frontend) vendor formatChatMessageLinks and trim surrounding newlines
|
||||||
|
- ✨(summary) add hostname to analytics properties
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Security is very important to us.
|
Security is very important to us.
|
||||||
|
|
||||||
If you have any issue regarding security, please disclose the information responsibly by submitting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at visio@numerique.gouv.fr
|
If you have any issue regarding security, please disclose the information responsibly submiting [this form](https://vdp.numerique.gouv.fr/p/Send-a-report?lang=en) and not by creating an issue on the repository. You can also email us at visio@numerique.gouv.fr
|
||||||
|
|
||||||
We appreciate your effort to make Visio more secure.
|
We appreciate your effort to make Visio more secure.
|
||||||
|
|
||||||
|
|||||||
@@ -50,24 +50,10 @@ paths:
|
|||||||
|
|
||||||
The application must be authorized for the user's email domain.
|
The application must be authorized for the user's email domain.
|
||||||
The returned token expires after a configured duration and must be refreshed by calling this endpoint again.
|
The returned token expires after a configured duration and must be refreshed by calling this endpoint again.
|
||||||
|
|
||||||
Request parameters may be sent either as "application/x-www-form-urlencoded"
|
|
||||||
(as specified by RFC 6749 for OAuth 2.0 token endpoints) or as "application/json".
|
|
||||||
operationId: generateToken
|
operationId: generateToken
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
application/x-www-form-urlencoded:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/TokenRequest'
|
|
||||||
examples:
|
|
||||||
tokenRequest:
|
|
||||||
summary: Request token for user delegation
|
|
||||||
value:
|
|
||||||
client_id: "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
client_secret: "1234567890abcdefghijklmnopqrstuvwxyz"
|
|
||||||
grant_type: "client_credentials"
|
|
||||||
scope: "user@example.com"
|
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/TokenRequest'
|
$ref: '#/components/schemas/TokenRequest'
|
||||||
@@ -131,19 +117,6 @@ paths:
|
|||||||
summary: Domain not authorized
|
summary: Domain not authorized
|
||||||
value:
|
value:
|
||||||
error: "This application is not authorized for this email domain."
|
error: "This application is not authorized for this email domain."
|
||||||
'415':
|
|
||||||
description: |
|
|
||||||
Unsupported media type. The request body must be sent as
|
|
||||||
"application/x-www-form-urlencoded" or "application/json".
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Error'
|
|
||||||
examples:
|
|
||||||
unsupportedMediaType:
|
|
||||||
summary: Unsupported request content type
|
|
||||||
value:
|
|
||||||
detail: 'Unsupported media type "text/plain" in request.'
|
|
||||||
|
|
||||||
/rooms:
|
/rooms:
|
||||||
get:
|
get:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.27.0"
|
version = "1.26.0"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"livekit-agents==1.6.7",
|
"livekit-agents==1.6.7",
|
||||||
|
|||||||
Generated
+1
-1
@@ -9,7 +9,7 @@ resolution-markers = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agents"
|
name = "agents"
|
||||||
version = "1.27.0"
|
version = "1.26.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "livekit-agents" },
|
{ name = "livekit-agents" },
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ from rest_framework import decorators, mixins, viewsets
|
|||||||
from rest_framework import (
|
from rest_framework import (
|
||||||
exceptions as drf_exceptions,
|
exceptions as drf_exceptions,
|
||||||
)
|
)
|
||||||
from rest_framework import (
|
|
||||||
parsers as drf_parsers,
|
|
||||||
)
|
|
||||||
from rest_framework import (
|
from rest_framework import (
|
||||||
response as drf_response,
|
response as drf_response,
|
||||||
)
|
)
|
||||||
@@ -44,7 +41,6 @@ class ApplicationViewSet(viewsets.ViewSet):
|
|||||||
methods=["post"],
|
methods=["post"],
|
||||||
url_path="token",
|
url_path="token",
|
||||||
url_name="token",
|
url_name="token",
|
||||||
parser_classes=[drf_parsers.FormParser, drf_parsers.JSONParser],
|
|
||||||
)
|
)
|
||||||
@FeatureFlag.require("application")
|
@FeatureFlag.require("application")
|
||||||
def generate_jwt_access_token(self, request, *args, **kwargs):
|
def generate_jwt_access_token(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ Tests for external API /token endpoint
|
|||||||
# pylint: disable=W0621
|
# pylint: disable=W0621
|
||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import pytest
|
import pytest
|
||||||
@@ -89,155 +88,6 @@ def test_api_applications_generate_token_success(settings):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_form_urlencoded(settings):
|
|
||||||
"""The token endpoint should accept "application/x-www-form-urlencoded"
|
|
||||||
requests, as mandated by RFC 6749 (sections 3.2 and 4.4.2) for OAuth 2.0
|
|
||||||
token endpoints, so that standard OAuth 2.0 client libraries work
|
|
||||||
out of the box."""
|
|
||||||
UserFactory(email="user@example.com")
|
|
||||||
application = ApplicationFactory(
|
|
||||||
is_active=True,
|
|
||||||
scopes=[ApplicationScope.ROOMS_LIST, ApplicationScope.ROOMS_CREATE],
|
|
||||||
)
|
|
||||||
|
|
||||||
plain_secret = "test-secret-123"
|
|
||||||
application.client_secret = plain_secret
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
(
|
|
||||||
f"client_id={application.client_id}"
|
|
||||||
f"&client_secret={plain_secret}"
|
|
||||||
"&grant_type=client_credentials"
|
|
||||||
"&scope=user%40example.com"
|
|
||||||
),
|
|
||||||
content_type="application/x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "access_token" in response.data
|
|
||||||
|
|
||||||
response.data.pop("access_token")
|
|
||||||
|
|
||||||
assert response.data == {
|
|
||||||
"token_type": "Bearer",
|
|
||||||
"expires_in": settings.APPLICATION_JWT_EXPIRATION_SECONDS,
|
|
||||||
"scope": "rooms:list rooms:create",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_form_urlencoded_invalid_credentials():
|
|
||||||
"""Invalid credentials sent as form-urlencoded should be parsed and
|
|
||||||
rejected with 401, proving the request body is properly decoded."""
|
|
||||||
user = UserFactory(email="user@example.com")
|
|
||||||
application = ApplicationFactory(is_active=True)
|
|
||||||
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
urlencode(
|
|
||||||
{
|
|
||||||
"client_id": application.client_id,
|
|
||||||
"client_secret": "wrong-secret",
|
|
||||||
"grant_type": "client_credentials",
|
|
||||||
"scope": user.email,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content_type="application/x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 401
|
|
||||||
assert "Invalid credentials" in str(response.data)
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_form_urlencoded_missing_fields():
|
|
||||||
"""Missing required fields in a form-urlencoded request should return
|
|
||||||
a 400 validation error, like for JSON requests."""
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
urlencode({"grant_type": "client_credentials"}),
|
|
||||||
content_type="application/x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 400
|
|
||||||
for field in ("client_id", "client_secret", "scope"):
|
|
||||||
assert field in response.data
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_form_urlencoded_invalid_grant_type():
|
|
||||||
"""An unsupported grant_type sent as form-urlencoded should return 400."""
|
|
||||||
user = UserFactory(email="user@example.com")
|
|
||||||
application = ApplicationFactory(is_active=True)
|
|
||||||
|
|
||||||
plain_secret = "test-secret-123"
|
|
||||||
application.client_secret = plain_secret
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
urlencode(
|
|
||||||
{
|
|
||||||
"client_id": application.client_id,
|
|
||||||
"client_secret": plain_secret,
|
|
||||||
"grant_type": "authorization_code",
|
|
||||||
"scope": user.email,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content_type="application/x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert "grant_type" in response.data
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_form_urlencoded_special_characters():
|
|
||||||
"""Percent-encoded reserved characters ("&", "=", "+", "%") in the
|
|
||||||
client_secret should survive form-urlencoded decoding."""
|
|
||||||
UserFactory(email="user@example.com")
|
|
||||||
application = ApplicationFactory(
|
|
||||||
is_active=True,
|
|
||||||
scopes=[ApplicationScope.ROOMS_LIST],
|
|
||||||
)
|
|
||||||
|
|
||||||
plain_secret = "s3cr3t&with=special+chars%42"
|
|
||||||
application.client_secret = plain_secret
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
urlencode(
|
|
||||||
{
|
|
||||||
"client_id": application.client_id,
|
|
||||||
"client_secret": plain_secret,
|
|
||||||
"grant_type": "client_credentials",
|
|
||||||
"scope": "user@example.com",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content_type="application/x-www-form-urlencoded",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "access_token" in response.data
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_unsupported_media_type():
|
|
||||||
"""Content types other than JSON and form-urlencoded should still be
|
|
||||||
rejected with 415 Unsupported Media Type."""
|
|
||||||
client = APIClient()
|
|
||||||
response = client.post(
|
|
||||||
"/external-api/v1.0/application/token/",
|
|
||||||
"client_id=x&client_secret=y&grant_type=client_credentials&scope=a@b.co",
|
|
||||||
content_type="text/plain",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 415
|
|
||||||
|
|
||||||
|
|
||||||
def test_api_applications_generate_token_invalid_client_id():
|
def test_api_applications_generate_token_invalid_client_id():
|
||||||
"""Invalid client_id should return 401."""
|
"""Invalid client_id should return 401."""
|
||||||
user = UserFactory(email="user@example.com")
|
user = UserFactory(email="user@example.com")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "uv_build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.27.0"
|
version = "1.26.0"
|
||||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
|||||||
Generated
+4
-4
@@ -1187,7 +1187,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "meet"
|
name = "meet"
|
||||||
version = "1.27.0"
|
version = "1.26.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiohttp" },
|
{ name = "aiohttp" },
|
||||||
@@ -2252,11 +2252,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlparse"
|
name = "sqlparse"
|
||||||
version = "0.6.0"
|
version = "0.5.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/5f/d3/3f06a1006f2261d1342aefb3c71eed02f5d4ca5bdbecd86ebc12ad38306e/sqlparse-0.6.0.tar.gz", hash = "sha256:113c35c75365ab9cc9c7231d68c6428fb11c085fc8e9eb1ad659b7ddbf6cd2b9", size = 178477, upload-time = "2026-08-13T19:16:06.396Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/50/f00935da0ec7cbf325f8dc4f772ae46fbc7b672dd62876e73f0a94adda57/sqlparse-0.6.0-py3-none-any.whl", hash = "sha256:b861c0288ce2fa56209a9a6412d2e066ac664b3873b89c26c9d8415e8e32996f", size = 50070, upload-time = "2026-08-13T19:16:04.062Z" },
|
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
|
"@fontsource-variable/atkinson-hyperlegible-next": "5.2.6",
|
||||||
"@fontsource-variable/lexend": "5.2.11",
|
"@fontsource-variable/lexend": "5.2.11",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "meet",
|
"name": "meet",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "panda codegen && vite",
|
"dev": "panda codegen && vite",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ChatProvider = () => {
|
|||||||
resetChatStore()
|
resetChatStore()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Trigger the message notification (temporary)
|
// Tigger the message notification (temporary)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TEMPORARY: This is a brittle workaround that relies on message count tracking
|
// TEMPORARY: This is a brittle workaround that relies on message count tracking
|
||||||
// due to recent LiveKit useChat changes breaking the previous implementation
|
// due to recent LiveKit useChat changes breaking the previous implementation
|
||||||
|
|||||||
+2
-2
@@ -44,7 +44,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
|||||||
videoElement?: HTMLVideoElement
|
videoElement?: HTMLVideoElement
|
||||||
videoElementLoaded?: boolean
|
videoElementLoaded?: boolean
|
||||||
|
|
||||||
// Canvas containing the video processing result, of which we extract as stream.
|
// Canvas containg the video processing result, of which we extract as stream.
|
||||||
outputCanvas?: HTMLCanvasElement
|
outputCanvas?: HTMLCanvasElement
|
||||||
outputCanvasCtx?: CanvasRenderingContext2D
|
outputCanvasCtx?: CanvasRenderingContext2D
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export class BackgroundCustomProcessor implements BackgroundProcessorInterface {
|
|||||||
segmentationMaskCanvas?: HTMLCanvasElement
|
segmentationMaskCanvas?: HTMLCanvasElement
|
||||||
segmentationMaskCanvasCtx?: CanvasRenderingContext2D
|
segmentationMaskCanvasCtx?: CanvasRenderingContext2D
|
||||||
|
|
||||||
// Mask containing the inference result.
|
// Mask containg the inference result.
|
||||||
segmentationMask?: ImageData
|
segmentationMask?: ImageData
|
||||||
|
|
||||||
// The resized image of the video source.
|
// The resized image of the video source.
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@html-to/text-cli": "0.6.0",
|
"@html-to/text-cli": "0.6.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mail_mjml",
|
"name": "mail_mjml",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"description": "An util to generate html and text django's templates from mjml templates",
|
"description": "An util to generate html and text django's templates from mjml templates",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./library",
|
"./library",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk",
|
"name": "sdk",
|
||||||
"version": "1.27.0",
|
"version": "1.26.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "summary"
|
name = "summary"
|
||||||
version = "1.27.0"
|
version = "1.26.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi[standard]>=0.105.0",
|
"fastapi[standard]>=0.105.0",
|
||||||
"uvicorn>=0.24.0",
|
"uvicorn>=0.24.0",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Analytics classes."""
|
"""Analytics classes."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import socket
|
||||||
import time
|
import time
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
@@ -43,6 +44,11 @@ class Analytics:
|
|||||||
if self.is_disabled:
|
if self.is_disabled:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# We add hostname to help track down the source of events
|
||||||
|
properties = properties or {}
|
||||||
|
if not properties.get("hostname"):
|
||||||
|
properties = {**properties, "hostname": socket.gethostname()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._client.capture(
|
self._client.capture(
|
||||||
event_name, distinct_id=distinct_id, properties=properties
|
event_name, distinct_id=distinct_id, properties=properties
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class RecordingMetadata(BaseModel):
|
|||||||
|
|
||||||
cloud_storage_url: Url = Field(
|
cloud_storage_url: Url = Field(
|
||||||
title="Cloud Storage URL",
|
title="Cloud Storage URL",
|
||||||
description="The URL of the metadata file for speaker assignment.",
|
description="The URL of the metadata file for speaker assignement.",
|
||||||
)
|
)
|
||||||
started_at: AwareDatetime = Field(title="Start time of the recording to transcribe")
|
started_at: AwareDatetime = Field(title="Start time of the recording to transcribe")
|
||||||
ended_at: AwareDatetime = Field(title="End time of the recording to transcribe")
|
ended_at: AwareDatetime = Field(title="End time of the recording to transcribe")
|
||||||
|
|||||||
Reference in New Issue
Block a user