Compare commits

...

27 Commits

Author SHA1 Message Date
Emmanuel Pelletier 195e701fc4 🔖(minor) bump release to 0.1.1 2024-07-22 15:57:57 +02:00
lebaudantoine 88a5717022 ♻️(backend) simplify queryset while listing rooms
Recent refactoring simplified the DB models.
Thus, filtering rooms is now way simpler,
I updated the subsequent queryset.
2024-07-22 14:15:49 +02:00
lebaudantoine e17d42ebe3 🔥(backend) remove todo items
The Pylint job was failing due to those TODO items. In our make lint
command sequence, Pylint runs first. If it fails, Ruff won't run,
which is quite inconvenient.

I've extracted those TODOs into an issue for further review.
2024-07-22 14:15:49 +02:00
lebaudantoine ae95a00301 (backend) add 'username' query param when retrieving a room
Quick and dirty approach. It works, that's essential.
Frontend can pass a desired username for the user. This would
be the name displayed in the room to other participants.
Usernames don't need to be unique, but user identities do

If no username is passed, API will fall back to a default username.
Why? This serves as a security mechanism. If the API is called
incorrectly by a client, it maintains the previous behavior.
2024-07-22 14:15:49 +02:00
Emmanuel Pelletier faff1c1228 ♻️(frontend) make the Conference component not know about routing
Makes more sense that way: only the Room _route_ knows about route
params, not the internal component used.
2024-07-21 17:42:53 +02:00
Emmanuel Pelletier e04b8d081f 🐛(frontend) have a suspense fallback
the app crashed on screen changes, I don't quite get why right now… but
at least this goes around the issue…
2024-07-21 17:39:08 +02:00
Emmanuel Pelletier efb5ac5834 🌐(frontend) sort extrated locales to help prevent conflicts
when adding keys by hand, we didn't really know where to add them so
that the i18n:extract command would not move them afterwards. Feels like
this will help.
I guess a CI thing checking if the locales file dont change after a push
would be helpful
2024-07-21 17:26:26 +02:00
Emmanuel Pelletier 0cf4960969 ♻️(rooms) room id gen: write more es6-like code
- this feels a bit less boilerplaty to read
- puting the characters whitelist outside the function to prevent
creating the var each time (yes, this of super great importance)
2024-07-21 17:18:29 +02:00
Emmanuel Pelletier f11bcea3a2 🔒️(frontend) valide ':roomId' path using a regex
Enhanced security by ensuring users are redirected to a 404 error page
if they
pass an incorrect roomId path, either intentionally or unintentionally.
This is
a critical security mechanism that should be included in our MVP.

Let's discuss extracting hardcoded elements, such as lengths or
the separator, into proper constants to improve code maintainability.
I was concerned that this might make the code harder to read, it could
enhance
clarity and reusability in the long term.

I prefer exposing the roomIdRegex from the same location where we
generate IDs.
However, this increases the responsibility of that file. Lmk if you have
any
suggestion for a better organization.

Additionally, the current 404 error page displays a 'Page not found'
message for
invalid room IDs. Should we update this message to 'Invalid room name'
to
provide more context to the user?
2024-07-21 17:18:29 +02:00
lebaudantoine d8c8ac0811 🚸(frontend) generate shorter room IDs making URLs easier to share
UUID-v4 room IDs are long and uninviting. Shorter, custom room IDs
can enhance UX by making URLs easier to share and remember.

While UUID-v4s are typically used in database systems for their low
collision probability, for ephemeral room IDs, the collision risk of e+14
combinations is acceptable.

This aligns room IDs with Google Meet format.

Even if the 'slugify' function is not used anymore, I kept it.
Lmk if you prefer removing it @manuhabitela
2024-07-21 17:18:29 +02:00
Emmanuel Pelletier 9fd1af2302 💄(keyboard) better handling of focus ring
some focus rings were shown even when we only used the mouse. this
globally fixes that
2024-07-21 16:48:27 +02:00
Emmanuel Pelletier e6c292ecd7 🌐(frontend) add a language selector in the header
this will be better in an options page later i think, as we don't pass
our life changing language and we already have a language detector at
load.

this adds a PopoverList primitive to easily create buttons triggering
popovers containing list of actionable items.
2024-07-21 16:48:27 +02:00
Emmanuel Pelletier d6b5e9a50c (frontend) add a Popover primitive
easily use buttons toggling styled RAC popovers
2024-07-21 16:48:27 +02:00
Emmanuel Pelletier 789bce5092 ♻️(frontend) put homepage in its own feature
makes more sense i guess, maybe
2024-07-20 20:23:57 +02:00
Emmanuel Pelletier f888fc1717 🌐(crowdin) make crowdin work with frontend translations
- upload local translation files on push
- make crowdin create a pull request when new translations are made
through the crowdin website (webhook configured on crowdin-end)
2024-07-20 20:23:57 +02:00
Emmanuel Pelletier 545877febb 💚(crowdin) update to latest secrets to fix CROWDIN_BASE_PATH issue
the base path is actually not a secret so we'd rather have it outside
secrets and see it easily
2024-07-20 20:23:57 +02:00
Emmanuel Pelletier d2dba511e2 🌐(frontend) init i18next
- dynamically load locale files for smaller footprint
- have a namespace for each feature. At first I'd figured I'd put each
namespace in its correct feature folder but it's kinda cumbersome to
manage if we want to link that to i18n management services like crowdin…
2024-07-20 20:23:57 +02:00
antoine lebaud 84c2986c01 ✏️(makefile) fix "crowin" typo
Fixed a typo and ensured all instances of "crowdin"
are capitalized for consistent naming.
2024-07-20 20:23:57 +02:00
antoine lebaud 44e5cd6ef3 💚(CI) fix crowdin steps
Updated CI to use "npm" instead of yarn for the frontend project based
on @manuhabitela's recommendations. Also updated the dependencies-related CI
steps that were previously missed.
2024-07-20 20:23:57 +02:00
Jacques ROUSSEL 7510d0fc2b 🔧(helm) configuration
Change configuration to use livekit-preprod.beta.numerique.gouv.fr
instead of the docker test vm
2024-07-19 15:35:55 +02:00
Jacques ROUSSEL f50426b11a 🔧(helm) fix helm chart
Fix helm secret to be abble to use titl on dev
2024-07-18 16:11:56 +02:00
lebaudantoine b604235c35 📝(release) document releasing new version
Heavily inspired from openfun handbook instructions,
https://handbook.openfun.fr/git

Document how release a new version. Might be a common
documentation shared with Impress, Regie and Meet projects.
Let's discuss it.

It's quite important that anyone should know how to release,
as we plan to release every week an enhanced version.
2024-07-18 16:03:19 +02:00
lebaudantoine 6e20d5385f ♻️(frontend) introduce a logoutUrl function
Wrap the logout URL in a function for consistency with '/authenticate'.
2024-07-17 16:51:24 +02:00
lebaudantoine 1c046abf5f ✏️(frontend) minor typo detected on webstorm
No big deal, just a little nit-pick. Nothing personal!
My IDE is THE nit-picker.
2024-07-17 16:51:24 +02:00
lebaudantoine 3718851435 ♻️(frontend) refactor hardcoded '/authenticate' API calls
Use the function introduce by @manuhabitela, authUrl.
It reduces code duplication.
2024-07-17 16:51:24 +02:00
Jacques ROUSSEL c390499394 🔧(helm) fix helm chart
Add md5sum on secret in order to automatically deploy new pods when
secret change
2024-07-17 15:50:18 +02:00
Jacques ROUSSEL 980d3c19d8 🔧(helm) upgrade sops secrets
Upgrade submodule reference
2024-07-17 15:50:18 +02:00
63 changed files with 2200 additions and 256 deletions
+33
View File
@@ -0,0 +1,33 @@
name: Download Crowdin translations
on:
workflow_dispatch:
types: [file-fully-translated]
permissions:
contents: write
pull-requests: write
jobs:
crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download Crowdin files
uses: crowdin/github-action@v2
with:
upload_sources: false
upload_translations: false
download_translations: true
localization_branch_name: l10n_crowdin_translations
create_pull_request: true
pull_request_title: "New Crowdin translations"
pull_request_body: "New Crowdin pull request with translations"
pull_request_base_branch_name: "main"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
CROWDIN_BASE_PATH: ${{ github.workspace }}
+14 -12
View File
@@ -208,22 +208,24 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: "18.x"
cache: "yarn"
cache-dependency-path: src/frontend/yarn.lock
cache: "npm"
cache-dependency-path: src/frontend/package-lock.json
- name: Install dependencies
run: cd src/frontend/ && yarn install --frozen-lockfile
run: cd src/frontend/ && npm ci
- name: Extract the frontend translation
run: make frontend-i18n-extract
- name: Upload files to Crowdin
run: |
docker run \
--rm \
-e CROWDIN_API_TOKEN=$CROWDIN_API_TOKEN \
-e CROWDIN_PROJECT_ID=$CROWDIN_PROJECT_ID \
-e CROWDIN_BASE_PATH=$CROWDIN_BASE_PATH \
-v "${{ github.workspace }}:/app" \
crowdin/cli:3.16.0 \
crowdin upload sources -c /app/crowdin/config.yml
uses: crowdin/github-action@v2
with:
config: crowdin/config.yml
upload_sources: true
upload_translations: true
download_translations: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_BASE_PATH: ${{ github.workspace }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+10 -14
View File
@@ -217,7 +217,7 @@ env.d/development/kc_postgresql:
env.d/development/crowdin:
cp -n env.d/development/crowdin.dist env.d/development/crowdin
crowdin-download: ## Download translated message from crowdin
crowdin-download: ## Download translated message from Crowdin
@$(COMPOSE_RUN_CROWDIN) download -c crowdin/config.yml
.PHONY: crowdin-download
@@ -225,14 +225,17 @@ crowdin-download-sources: ## Download sources from Crowdin
@$(COMPOSE_RUN_CROWDIN) download sources -c crowdin/config.yml
.PHONY: crowdin-download-sources
crowdin-upload: ## Upload source translations to crowdin
crowdin-upload: ## Upload source translations to Crowdin
@$(COMPOSE_RUN_CROWDIN) upload sources -c crowdin/config.yml
.PHONY: crowdin-upload
crowdin-upload-translations: ## Upload translations to Crowdin
@$(COMPOSE_RUN_CROWDIN) upload translations -c crowdin/config.yml
.PHONY: crowdin-upload-translations
i18n-compile: ## compile all translations
i18n-compile: \
back-i18n-compile \
frontend-i18n-compile
back-i18n-compile
.PHONY: i18n-compile
i18n-generate: ## create the .pot files and extract frontend messages
@@ -295,23 +298,16 @@ help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(GREEN)%-30s$(RESET) %s\n", $$1, $$2}'
.PHONY: help
# FIXME : adapt this command
frontend-i18n-extract: ## Extract the frontend translation inside a json to be used for crowdin
cd $(PATH_FRONT) && yarn i18n:extract
frontend-i18n-extract: ## Check the frontend code and generate missing translations keys in translation files
cd $(PATH_FRONT) && npm run i18n:extract
.PHONY: frontend-i18n-extract
# FIXME : adapt this command
frontend-i18n-generate: ## Generate the frontend json files used for crowdin
frontend-i18n-generate: ## Generate the frontend json files used for Crowdin
frontend-i18n-generate: \
crowdin-download-sources \
frontend-i18n-extract
.PHONY: frontend-i18n-generate
# FIXME : adapt this command
frontend-i18n-compile: ## Format the crowin json files used deploy to the apps
cd $(PATH_FRONT) && yarn i18n:deploy
.PHONY: frontend-i18n-compile
# -- K8S
build-k8s-cluster: ## build the kubernetes cluster using kind
./bin/start-kind.sh
+15 -14
View File
@@ -1,7 +1,7 @@
#
# Your crowdin's credentials
#
api_token_env: CROWDIN_API_TOKEN
api_token_env: CROWDIN_PERSONAL_TOKEN
project_id_env: CROWDIN_PROJECT_ID
base_path_env: CROWDIN_BASE_PATH
@@ -14,16 +14,17 @@ preserve_hierarchy: true
#
# Files configuration
#
files: [
{
source : "/backend/locale/django.pot",
dest: "/backend-meet.pot",
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
},
{
source: "/frontend/packages/i18n/locales/impress/translations-crowdin.json",
dest: "/frontend-impress.json",
translation: "/frontend/packages/i18n/locales/impress/%two_letters_code%/translations.json",
skip_untranslated_strings: true,
},
]
files:
[
{
source: "src/backend/locale/django.pot",
dest: "/backend-meet.pot",
translation: "src/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po",
},
{
source: "src/frontend/src/locales/fr/**/*",
translation: "src/frontend/src/locales/%two_letters_code%/**/%original_file_name%",
dest: "/%original_file_name%",
skip_untranslated_strings: true,
},
]
+1 -1
View File
@@ -100,7 +100,7 @@ services:
image: jwilder/dockerize
crowdin:
image: crowdin/cli:3.16.0
image: crowdin/cli:4.0.0
volumes:
- ".:/app"
env_file:
+39
View File
@@ -0,0 +1,39 @@
# Releasing new version
Whenever we are cooking a new release (e.g. `4.18.1`) we should follow a standard procedure described below:
1. Create a new branch named: `release/4.18.1`.
2. Bump the release number in the appropriate file, i.e. for backend in the `pyproject.toml` and `package.json` for node-based projects (tips: find and replace all occurrences of the previous version number to ensure consistency across files).
3. Update the project's `Changelog` following the [keepachangelog](https://keepachangelog.com/en/0.3.0/) recommendations.
4. Update Docker image tag in Helm values files located at `src/helm/env.d` for both `preprod` and `production` environments:
```yaml
image:
repository: lasuite/meet-backend
pullPolicy: Always
tag: "v4.18.1" # Replace with the latest Docker image tag.
```
5. Commit your changes with a structured message:
- Add a title including the version of the release and respecting the above described format using the 🔖 release emoji.
- Paste in the body all changes from the changelog concerned by this release, removing only the Markdown tags and making sure that lines are shorter than 74 characters.
```
🔖(minor) bump release to 4.18.0
Added:
- Implement base CLI commands (list, extract, fetch & push) for supported backends
- Support for ElasticSearch database backend
Changed:
- Replace LDP storage backend by FS storage backend`
```
6. Open a pull request.
7. Wait for an approval from your peers.
8. Merge your pull request.
9. Checkout and pull changes from the `main` branch to ensure you have the latest updates.
10. Tag & push your commit: `git tag v4.18.1 && git push origin --tags`
11. Manually release your version on GitHub.
12. Ensure that the CI Docker job has successfully pushed the newly built Docker images to Docker Hub with the appropriate tags.
13. To deploy using ArgoCD, you need to update the `production` or `pre-production` tags. ArgoCD will automatically detect and deploy the new tag.
+2 -2
View File
@@ -1,3 +1,3 @@
CROWDIN_API_TOKEN=Your-Api-Token
CROWDIN_PERSONAL_TOKEN=Your-Api-Token
CROWDIN_PROJECT_ID=Your-Project-Id
CROWDIN_BASE_PATH=/app/src
CROWDIN_BASE_PATH=/app
+1 -1
Submodule secrets updated: 35ec1ef9fe...9da011f5c2
+5 -3
View File
@@ -122,14 +122,16 @@ class RoomSerializer(serializers.ModelSerializer):
if role is not None or instance.is_public:
slug = f"{instance.id!s}".replace("-", "")
username = request.GET.get("username", None)
output["livekit"] = {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": slug,
"token": utils.generate_token(room=slug, user=request.user),
"token": utils.generate_token(
room=slug, user=request.user, username=username
),
}
output["is_administrable"] = is_admin
# todo - pass properly livekit configuration
return output
+5 -5
View File
@@ -188,12 +188,15 @@ class RoomViewSet(
if not settings.ALLOW_UNREGISTERED_ROOMS:
raise
slug = slugify(self.kwargs["pk"])
username = request.query_params.get("username", None)
data = {
"id": None,
"livekit": {
"url": settings.LIVEKIT_CONFIGURATION["url"],
"room": slug,
"token": utils.generate_token(room=slug, user=request.user),
"token": utils.generate_token(
room=slug, user=request.user, username=username
),
},
}
else:
@@ -206,11 +209,8 @@ class RoomViewSet(
user = self.request.user
if user.is_authenticated:
# todo - simplify this queryset
queryset = (
self.filter_queryset(self.get_queryset())
.filter(Q(users=user))
.distinct()
self.filter_queryset(self.get_queryset()).filter(users=user).distinct()
)
else:
queryset = self.get_queryset().none()
@@ -111,7 +111,9 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed(mock_token):
},
}
mock_token.assert_called_once_with(room="unregistered-room", user=AnonymousUser())
mock_token.assert_called_once_with(
room="unregistered-room", user=AnonymousUser(), username=None
)
@override_settings(ALLOW_UNREGISTERED_ROOMS=True)
@@ -141,7 +143,9 @@ def test_api_rooms_retrieve_anonymous_unregistered_allowed_not_normalized(mock_t
},
}
mock_token.assert_called_once_with(room="reunion", user=AnonymousUser())
mock_token.assert_called_once_with(
room="reunion", user=AnonymousUser(), username=None
)
@override_settings(ALLOW_UNREGISTERED_ROOMS=False)
@@ -229,7 +233,7 @@ def test_api_rooms_retrieve_authenticated_public(mock_token):
"slug": room.slug,
}
mock_token.assert_called_once_with(room=expected_name, user=user)
mock_token.assert_called_once_with(room=expected_name, user=user, username=None)
def test_api_rooms_retrieve_authenticated():
@@ -327,7 +331,7 @@ def test_api_rooms_retrieve_members(mock_token, django_assert_num_queries):
"slug": room.slug,
}
mock_token.assert_called_once_with(room=expected_name, user=user)
mock_token.assert_called_once_with(room=expected_name, user=user, username=None)
@mock.patch("core.utils.generate_token", return_value="foo")
@@ -400,4 +404,4 @@ def test_api_rooms_retrieve_administrators(mock_token, django_assert_num_queries
"slug": room.slug,
}
mock_token.assert_called_once_with(room=expected_name, user=user)
mock_token.assert_called_once_with(room=expected_name, user=user, username=None)
+9 -6
View File
@@ -1,7 +1,7 @@
"""
Utils functions used in the core app
"""
import string
from typing import Optional
from uuid import uuid4
from django.conf import settings
@@ -9,18 +9,19 @@ from django.conf import settings
from livekit.api import AccessToken, VideoGrants
def generate_token(room: string, user) -> str:
def generate_token(room: str, user, username: Optional[str] = None) -> str:
"""Generate a Livekit access token for a user in a specific room.
Args:
room (str): The name of the room.
user (User): The user which request the access token.
username (Optional[str]): The username to be displayed in the room.
If none, a default value will be used.
Returns:
str: The LiveKit JWT access token.
"""
# todo - define the video grants properly based on user and room.
video_grants = VideoGrants(
room=room,
room_join=True,
@@ -38,10 +39,12 @@ def generate_token(room: string, user) -> str:
).with_grants(video_grants)
if user.is_anonymous:
# todo - allow passing a proper name for not logged-in user
token.with_identity(str(uuid4()))
default_username = "Anonymous"
else:
# todo - use user's fullname instead of its email for the displayed name
token.with_identity(user.sub).with_name(f"{user!s}")
token.with_identity(user.sub)
default_username = str(user)
token.with_name(username or default_username)
return token.to_jwt()
+1 -1
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "meet"
version = "0.1.0"
version = "0.1.1"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
+8
View File
@@ -0,0 +1,8 @@
{
"defaultNamespace": "global",
"input": ["src/**/*.{ts,tsx}", "!src/styled-system/**/*", "!src/**/*.d.ts"],
"output": "src/locales/$LOCALE/$NAMESPACE.json",
"createOldCatalogs": false,
"locales": ["en", "fr", "de"],
"sort": true
}
+1506 -41
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -1,23 +1,30 @@
{
"name": "meet",
"private": true,
"version": "0.1.0",
"version": "0.1.1",
"type": "module",
"scripts": {
"dev": "panda codegen && vite",
"build": "panda codegen && tsc -b && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"i18n:extract": "npx i18next -c i18next-parser.config.json"
},
"dependencies": {
"@livekit/components-react": "2.3.3",
"@livekit/components-styles": "1.0.12",
"@pandacss/preset-panda": "0.41.0",
"@tanstack/react-query": "5.49.2",
"hoofd": "1.7.1",
"i18next": "23.12.1",
"i18next-browser-languagedetector": "8.0.0",
"i18next-parser": "9.0.0",
"i18next-resources-to-backend": "1.2.1",
"livekit-client": "2.3.1",
"react": "18.2.0",
"react-aria-components": "1.2.1",
"react-dom": "18.2.0",
"react-i18next": "14.1.3",
"wouter": "3.3.0"
},
"devDependencies": {
+2 -1
View File
@@ -146,6 +146,7 @@ const config: Config = {
2: { value: '2' },
},
radii: {
4: { value: '0.25rem' },
6: { value: '0.375rem' },
8: { value: '0.5rem' },
16: { value: '1rem' },
@@ -187,7 +188,7 @@ const config: Config = {
text: { value: '{colors.white}' },
warm: { value: '{colors.blue.300}' },
subtle: { value: '{colors.blue.100}' },
'subtle-text': { value: '{colors.sky.700}' },
'subtle-text': { value: '{colors.blue.700}' },
},
danger: {
DEFAULT: { value: '{colors.red.600}' },
+17 -8
View File
@@ -1,23 +1,32 @@
import '@livekit/components-styles'
import '@/styles/index.css'
import { Suspense } from 'react'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { useLang } from 'hoofd'
import { Route, Switch } from 'wouter'
import { Home } from './routes/Home'
import { Screen } from './layout/Screen'
import { HomeRoute } from '@/features/home'
import { RoomRoute, roomIdRegex } from '@/features/rooms'
import { NotFound } from './routes/NotFound'
import { RoomRoute } from '@/features/rooms'
import './i18n/init'
const queryClient = new QueryClient()
function App() {
const { i18n } = useTranslation()
useLang(i18n.language)
return (
<QueryClientProvider client={queryClient}>
<Switch>
<Route path="/" component={Home} />
<Route path="/:roomId" component={RoomRoute} />
<Route component={NotFound} />
</Switch>
<ReactQueryDevtools initialIsOpen={false} />
<Suspense fallback={<Screen />}>
<Switch>
<Route path="/" component={HomeRoute} />
<Route path={roomIdRegex} component={RoomRoute} />
<Route component={NotFound} />
</Switch>
<ReactQueryDevtools initialIsOpen={false} />
</Suspense>
</QueryClientProvider>
)
}
@@ -3,7 +3,7 @@ import { fetchApi } from '@/api/fetchApi'
import { type ApiUser } from './ApiUser'
/**
* fetch the logged in user from the api.
* fetch the logged-in user from the api.
*
* If the user is not logged in, the api returns a 401 error.
* Here our wrapper just returns false in that case, without triggering an error:
+1
View File
@@ -1,2 +1,3 @@
export { useUser } from './api/useUser'
export { authUrl } from './utils/authUrl'
export { logoutUrl } from './utils/logoutUrl'
@@ -0,0 +1,5 @@
import { apiUrl } from '@/api/apiUrl'
export const logoutUrl = () => {
return apiUrl('/logout')
}
+2
View File
@@ -0,0 +1,2 @@
export { navigateToHome } from './navigation/navigateToHome'
export { Home as HomeRoute } from './routes/Home'
@@ -1,39 +1,35 @@
import { useTranslation } from 'react-i18next'
import { A, Button, Italic, P, Div, H, Box } from '@/primitives'
import { useUser } from '@/features/auth'
import { apiUrl } from '@/api/apiUrl'
import { authUrl, useUser } from '@/features/auth'
import { navigateToNewRoom } from '@/features/rooms'
import { Screen } from '@/layout/Screen'
export const Home = () => {
const { t } = useTranslation('home')
const { isLoggedIn } = useUser()
return (
<Screen>
<Box asScreen>
<H lvl={1}>Welcome in Meet</H>
<P>What do you want to do? You can either:</P>
<Box type="screen">
<H lvl={1}>{t('heading')}</H>
<P>{t('intro')}</P>
<Div marginBottom="gutter">
<Box variant="subtle" size="sm">
{isLoggedIn ? (
<Button variant="primary" onPress={() => navigateToNewRoom()}>
Create a conference call
{t('createMeeting')}
</Button>
) : (
<p>
<A href={apiUrl('/authenticate')}>
Login to create a conference call
</A>
<A href={authUrl()}>{t('login')}</A>
</p>
)}
</Box>
</Div>
<P>
<Italic>Or</Italic>
<Italic>{t('or')}</Italic>
</P>
<Box variant="subtle" size="sm">
<p>
copy a meeting URL in your browser address bar to join an existing
conference call
</p>
<p>{t('copyMeetingUrl')}</p>
</Box>
</Box>
</Screen>
@@ -1,4 +1,3 @@
import { useParams } from 'wouter'
import { useQuery } from '@tanstack/react-query'
import {
LiveKitRoom,
@@ -7,15 +6,16 @@ import {
} from '@livekit/components-react'
import { keys } from '@/api/queryKeys'
import { QueryAware } from '@/layout/QueryAware'
import { navigateToHome } from '@/navigation/navigateToHome'
import { navigateToHome } from '@/features/home'
import { fetchRoom } from '../api/fetchRoom'
export const Conference = ({
roomId,
userConfig,
}: {
roomId: string
userConfig: LocalUserChoices
}) => {
const { roomId } = useParams()
const { status, data } = useQuery({
queryKey: [keys.room, roomId, userConfig.username],
queryFn: () =>
@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next'
import { Box } from '@/layout/Box'
import { PreJoin, type LocalUserChoices } from '@livekit/components-react'
@@ -6,12 +7,11 @@ export const Join = ({
}: {
onSubmit: (choices: LocalUserChoices) => void
}) => {
const { t } = useTranslation('rooms')
return (
<Box title="Verify your settings before joining" withBackButton>
<PreJoin
persistUserChoices
onSubmit={onSubmit}
/>
<Box title={t('join.heading')} withBackButton>
<PreJoin persistUserChoices onSubmit={onSubmit} />
</Box>
)
}
+1
View File
@@ -1,2 +1,3 @@
export { navigateToNewRoom } from './navigation/navigateToNewRoom'
export { Room as RoomRoute } from './routes/Room'
export { roomIdRegex } from './utils/generateRoomId'
@@ -1,15 +1,21 @@
import { type LocalUserChoices } from '@livekit/components-react'
import { useState } from 'react'
import { useParams } from 'wouter'
import { Conference } from '../components/Conference'
import { Join } from '../components/Join'
import { Screen } from '@/layout/Screen'
import { ErrorScreen } from '@/layout/ErrorScreen'
export const Room = () => {
const [userConfig, setUserConfig] = useState<LocalUserChoices | null>(null)
const { roomId } = useParams()
if (!roomId) {
return <ErrorScreen />
}
return (
<Screen>
{userConfig ? (
<Conference userConfig={userConfig} />
<Conference roomId={roomId} userConfig={userConfig} />
) : (
<Join onSubmit={setUserConfig} />
)}
@@ -1,5 +1,16 @@
import { slugify } from '@/utils/slugify'
// Google Meet uses only letters in a room identifier
const ROOM_ID_ALLOWED_CHARACTERS = 'abcdefghijklmnopqrstuvwxyz'
export const generateRoomId = () => {
return slugify(crypto.randomUUID())
}
const getRandomChar = () =>
ROOM_ID_ALLOWED_CHARACTERS[
Math.floor(Math.random() * ROOM_ID_ALLOWED_CHARACTERS.length)
]
const generateSegment = (length: number): string =>
Array.from(Array(length), getRandomChar).join('')
// Generates a unique room identifier following the Google Meet format
export const generateRoomId = () =>
[generateSegment(3), generateSegment(4), generateSegment(3)].join('-')
export const roomIdRegex = /^[/](?<roomId>[a-z]{3}-[a-z]{4}-[a-z]{3})$/
@@ -0,0 +1,28 @@
import { useTranslation } from 'react-i18next'
import { Button, Popover, PopoverList } from '@/primitives'
import { useLanguageLabels } from './useLanguageLabels'
export const LanguageSelector = () => {
const { t, i18n } = useTranslation()
const { languagesList, currentLanguage } = useLanguageLabels()
return (
<Popover aria-label={t('languageSelector.popoverLabel')}>
<Button
aria-label={t('languageSelector.buttonLabel', {
currentLanguage,
})}
size="sm"
variant="primary"
outline
>
{i18n.language}
</Button>
<PopoverList
items={languagesList}
onAction={(lang) => {
i18n.changeLanguage(lang)
}}
/>
</Popover>
)
}
+23
View File
@@ -0,0 +1,23 @@
import i18n from 'i18next'
import resourcesToBackend from 'i18next-resources-to-backend'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
const i18nDefaultNamespace = 'global'
i18n.setDefaultNamespace(i18nDefaultNamespace)
i18n
.use(
resourcesToBackend((language: string, namespace: string) => {
return import(`../locales/${language}/${namespace}.json`)
})
)
.use(initReactI18next)
.use(LanguageDetector)
i18n.init({
supportedLngs: ['en', 'fr'],
fallbackLng: 'en',
ns: i18nDefaultNamespace,
interpolation: {
escapeValue: false,
},
})
@@ -0,0 +1,20 @@
import { useTranslation } from 'react-i18next'
const langageLabels: Record<string, string> = {
en: 'English',
fr: 'Français',
de: 'Deutsch',
}
export const useLanguageLabels = () => {
const { i18n } = useTranslation()
// cimode is a testing value from i18next, don't include it
const supportedLanguages = (i18n.options.supportedLngs || []).filter(
(lang) => lang !== 'cimode'
)
const languagesList = supportedLanguages.map((lang) => ({
value: lang,
label: langageLabels[lang],
}))
return { languagesList, currentLanguage: langageLabels[i18n.language] }
}
+4 -2
View File
@@ -1,4 +1,5 @@
import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { Box as BoxDiv, H, Link } from '@/primitives'
export type BoxProps = {
@@ -12,14 +13,15 @@ export const Box = ({
title = '',
withBackButton = false,
}: BoxProps) => {
const { t } = useTranslation()
return (
<BoxDiv asScreen>
<BoxDiv type="screen">
{!!title && <H lvl={1}>{title}</H>}
{children}
{!!withBackButton && (
<p>
<Link to="/" size="small">
Back to homescreen
{t('backToHome')}
</Link>
</p>
)}
+3 -3
View File
@@ -1,7 +1,7 @@
import { BoxScreen } from './BoxScreen'
import { useTranslation } from 'react-i18next'
export const ErrorScreen = () => {
return (
<BoxScreen title="An error occured while loading the page" withBackButton />
)
const { t } = useTranslation()
return <BoxScreen title={t('error.heading')} withBackButton />
}
+3 -6
View File
@@ -1,10 +1,7 @@
import { BoxScreen } from './BoxScreen'
import { useTranslation } from 'react-i18next'
export const ForbiddenScreen = () => {
return (
<BoxScreen
title="You don't have the permission to view this page"
withBackButton
/>
)
const { t } = useTranslation()
return <BoxScreen title={t('forbidden.heading')} withBackButton />
}
+25 -23
View File
@@ -1,10 +1,14 @@
import { Link } from 'wouter'
import { css } from '@/styled-system/css'
import { flex } from '@/styled-system/patterns'
import { apiUrl } from '@/api/apiUrl'
import { Stack } from '@/styled-system/jsx'
import { useTranslation } from 'react-i18next'
import { LanguageSelector } from '@/i18n/LanguageSelector'
import { A, Badge, Text } from '@/primitives'
import { useUser } from '@/features/auth/api/useUser'
import { authUrl, logoutUrl, useUser } from '@/features/auth'
export const Header = () => {
const { t } = useTranslation()
const { user, isLoggedIn } = useUser()
return (
<div
@@ -19,29 +23,27 @@ export const Header = () => {
boxShadow: 'box',
})}
>
<header
className={flex({
justify: 'space-between',
align: 'center',
})}
>
<div>
<Stack direction="row" justify="space-between" align="center">
<header>
<Text bold variant="h1" margin={false}>
Meet
<Link to="/">{t('app')}</Link>
</Text>
</div>
<div>
{isLoggedIn === false && <A href={apiUrl('/authenticate')}>Login</A>}
{!!user && (
<p className={flex({ gap: 1, align: 'center' })}>
<Badge>{user.email}</Badge>
<A href={apiUrl('/logout')} size="small">
Logout
</A>
</p>
)}
</div>
</header>
</header>
<nav>
<Stack gap={1} direction="row" align="center">
<LanguageSelector />
{isLoggedIn === false && <A href={authUrl()}>{t('login')}</A>}
{!!user && (
<p className={flex({ gap: 1, align: 'center' })}>
<Badge>{user.email}</Badge>
<A href={logoutUrl()} size="small">
{t('logout')}
</A>
</p>
)}
</Stack>
</nav>
</Stack>
</div>
)
}
+3 -1
View File
@@ -1,8 +1,10 @@
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { BoxScreen } from './BoxScreen'
import { Screen } from './Screen'
export const LoadingScreen = ({ asBox = false }: { asBox?: boolean }) => {
const { t } = useTranslation()
// show the loading screen only after a little while to prevent flash of texts
const [show, setShow] = useState(false)
useEffect(() => {
@@ -15,7 +17,7 @@ export const LoadingScreen = ({ asBox = false }: { asBox?: boolean }) => {
const Container = asBox ? BoxScreen : Screen
return (
<Container>
<p>Loading</p>
<p>{t('loading')}</p>
</Container>
)
}
+3 -1
View File
@@ -1,5 +1,7 @@
import { useTranslation } from 'react-i18next'
import { BoxScreen } from './BoxScreen'
export const NotFoundScreen = () => {
return <BoxScreen title="Page not found" withBackButton />
const { t } = useTranslation()
return <BoxScreen title={t('notFound.heading')} withBackButton />
}
+1 -1
View File
@@ -2,7 +2,7 @@ import type { ReactNode } from 'react'
import { css } from '@/styled-system/css'
import { Header } from './Header'
export const Screen = ({ children }: { children: ReactNode }) => {
export const Screen = ({ children }: { children?: ReactNode }) => {
return (
<div
className={css({
+20
View File
@@ -0,0 +1,20 @@
{
"app": "Meet",
"backToHome": "",
"error": {
"heading": ""
},
"forbidden": {
"heading": ""
},
"languageSelector": {
"buttonLabel": "",
"popoverLabel": ""
},
"loading": "",
"login": "Anmelden",
"logout": "",
"notFound": {
"heading": ""
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"copyMeetingUrl": "",
"createMeeting": "",
"heading": "",
"intro": "",
"login": "",
"or": ""
}
+5
View File
@@ -0,0 +1,5 @@
{
"join": {
"heading": ""
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"app": "Meet",
"backToHome": "Back to homescreen",
"error": {
"heading": "An error occured while loading the page"
},
"forbidden": {
"heading": "You don't have the permission to view this page"
},
"languageSelector": {
"buttonLabel": "Change language (currently {{currentLanguage}})",
"popoverLabel": "Choose language"
},
"loading": "Loading…",
"login": "Login",
"logout": "Logout",
"notFound": {
"heading": ""
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"copyMeetingUrl": "copy a meeting URL in your browser address bar to join an existing conference call",
"createMeeting": "Create a conference call",
"heading": "Welcome in Meet",
"intro": "What do you want to do? You can either:",
"login": "Login to create a conference call",
"or": "Or"
}
+5
View File
@@ -0,0 +1,5 @@
{
"join": {
"heading": "Verify your settings before joining"
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"app": "Meet",
"backToHome": "Retour à l'accueil",
"error": {
"heading": "Une erreur est survenue lors du chargement de la page"
},
"forbidden": {
"heading": "Accès interdit"
},
"languageSelector": {
"buttonLabel": "Changer de langue (actuellement {{currentLanguage}})",
"popoverLabel": "Choix de la langue"
},
"loading": "Chargement…",
"login": "Se connecter",
"logout": "Se déconnecter",
"notFound": {
"heading": "Page introuvable"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"copyMeetingUrl": "copier une URL de conférence dans votre barre d'adresse pour rejoindre une conférence existante",
"createMeeting": "Créer une conférence",
"heading": "Bienvenue dans Meet",
"intro": "Que voulez vous faire ? Vous pouvez :",
"login": "Vous connecter pour créer une conférence",
"or": "Ou"
}
+5
View File
@@ -0,0 +1,5 @@
{
"join": {
"heading": "Vérifiez vos paramètres"
}
}
+2 -2
View File
@@ -5,8 +5,8 @@ const link = cva({
base: {
textDecoration: 'underline',
textUnderlineOffset: '2',
transition: 'all 200ms',
cursor: 'pointer',
borderRadius: 2,
'_ra-hover': {
textDecoration: 'none',
},
@@ -26,7 +26,7 @@ const link = cva({
export type AProps = LinkProps & RecipeVariantProps<typeof link>
/**
* anchor component styled with underline
* anchor component styled with underline. Used mostly for external links. Use Link for internal links
*/
export const A = ({ size, ...props }: AProps) => {
return <Link {...props} className={link({ size })} />
+6 -3
View File
@@ -9,14 +9,18 @@ const box = cva({
flex: 1,
},
variants: {
asScreen: {
true: {
type: {
screen: {
margin: 'auto',
width: '38rem',
maxWidth: '100%',
marginTop: '6rem',
textAlign: 'center',
},
popover: {
padding: 'boxPadding.xs',
minWidth: '10rem',
},
},
variant: {
default: {
@@ -42,7 +46,6 @@ const box = cva({
},
},
defaultVariants: {
asScreen: false,
variant: 'default',
size: 'default',
},
+45 -20
View File
@@ -9,44 +9,69 @@ import { cva, type RecipeVariantProps } from '@/styled-system/css'
const button = cva({
base: {
display: 'inline-block',
paddingX: '1',
paddingY: '0.625',
transition: 'all 200ms',
borderRadius: 8,
transition: 'background 200ms',
cursor: 'pointer',
border: '1px solid transparent',
color: 'colorPalette.text',
backgroundColor: 'colorPalette',
'_ra-hover': {
backgroundColor: 'colorPalette.hover',
},
'_ra-pressed': {
backgroundColor: 'colorPalette.active',
},
},
variants: {
size: {
default: {
borderRadius: 8,
paddingX: '1',
paddingY: '0.625',
},
sm: {
borderRadius: 4,
paddingX: '0.5',
paddingY: '0.25',
},
},
variant: {
default: {
color: 'control.text',
backgroundColor: 'control',
'_ra-hover': {
backgroundColor: 'control.hover',
},
'_ra-pressed': {
backgroundColor: 'control.active',
},
colorPalette: 'control',
},
primary: {
color: 'primary.text',
backgroundColor: 'primary',
colorPalette: 'primary',
},
},
outline: {
true: {
color: 'colorPalette',
backgroundColor: 'transparent!',
borderColor: 'currentcolor!',
'_ra-hover': {
backgroundColor: 'primary.hover',
backgroundColor: 'colorPalette.subtle!',
},
'_ra-pressed': {
backgroundColor: 'primary.active',
backgroundColor: 'colorPalette.subtle!',
},
},
},
},
defaultVariants: {
size: 'default',
variant: 'default',
outline: false,
},
})
type ButtonProps = RecipeVariantProps<typeof button> &
(RACButtonsProps | LinkProps)
export type ButtonProps = RecipeVariantProps<typeof button> & RACButtonsProps
export const Button = (props: ButtonProps) => {
type LinkButtonProps = RecipeVariantProps<typeof button> & LinkProps
type ButtonOrLinkProps = ButtonProps | LinkButtonProps
export const Button = (props: ButtonOrLinkProps) => {
const [variantProps, componentProps] = button.splitVariantProps(props)
if ((props as LinkProps).href !== undefined) {
if ((props as LinkButtonProps).href !== undefined) {
return <Link className={button(variantProps)} {...componentProps} />
}
return (
+68
View File
@@ -0,0 +1,68 @@
import { ReactNode } from 'react'
import {
DialogProps,
DialogTrigger,
Popover as RACPopover,
Dialog,
OverlayArrow,
} from 'react-aria-components'
import { styled } from '@/styled-system/jsx'
import { Box } from './Box'
const StyledPopover = styled(RACPopover, {
base: {
'&[data-placement="bottom"]': {
marginTop: 0.25,
},
},
})
const StyledOverlayArrow = styled(OverlayArrow, {
base: {
display: 'block',
fill: 'box.bg',
stroke: 'box.border',
strokeWidth: 1,
'&[data-placement="bottom"] svg': {
transform: 'rotate(180deg) translateY(-1px)',
},
},
})
/**
* a Popover is a tuple of a trigger component (most usually a Button) that toggles some interactive content in a tooltip around the trigger
*/
export const Popover = ({
children,
...dialogProps
}: {
children: [
trigger: ReactNode,
popoverContent:
| (({ close }: { close: () => void }) => ReactNode)
| ReactNode,
]
} & DialogProps) => {
const [trigger, popoverContent] = children
return (
<DialogTrigger>
{trigger}
<StyledPopover>
<StyledOverlayArrow>
<svg width={12} height={12} viewBox="0 0 12 12">
<path d="M0 0 L6 6 L12 0" />
</svg>
</StyledOverlayArrow>
<Dialog {...dialogProps}>
{({ close }) => (
<Box size="sm" type="popover">
{typeof popoverContent === 'function'
? popoverContent({ close })
: popoverContent}
</Box>
)}
</Dialog>
</StyledPopover>
</DialogTrigger>
)
}
@@ -0,0 +1,61 @@
import { ReactNode, useContext } from 'react'
import {
ButtonProps,
Button,
OverlayTriggerStateContext,
} from 'react-aria-components'
import { styled } from '@/styled-system/jsx'
const ListItem = styled(Button, {
base: {
paddingY: 0.125,
paddingX: 0.5,
textAlign: 'left',
width: 'full',
borderRadius: 4,
cursor: 'pointer',
color: 'primary',
'_ra-hover': {
color: 'primary.subtle-text',
backgroundColor: 'primary.subtle',
},
},
})
/**
* render a Button primitive that shows a popover showing a list of pressable items
*/
export const PopoverList = <T extends string | number = string>({
onAction,
closeOnAction = true,
items = [],
}: {
closeOnAction?: boolean
onAction: (key: T) => void
items: Array<string | { value: T; label: ReactNode }>
} & ButtonProps) => {
const popoverState = useContext(OverlayTriggerStateContext)!
return (
<ul>
{items.map((item) => {
const value = typeof item === 'string' ? item : item.value
const label = typeof item === 'string' ? item : item.label
return (
<li>
<ListItem
key={value}
onPress={() => {
onAction(value as T)
if (closeOnAction) {
popoverState.close()
}
}}
>
{label}
</ListItem>
</li>
)
})}
</ul>
)
}
+2
View File
@@ -9,4 +9,6 @@ export { Hr } from './Hr'
export { Italic } from './Italic'
export { Link } from './Link'
export { P } from './P'
export { Popover } from './Popover'
export { PopoverList } from './PopoverList'
export { Text } from './Text'
+12
View File
@@ -6,3 +6,15 @@ body,
#root {
height: 100%;
}
* {
outline: none;
}
[data-rac][data-focus-visible],
:is(a, button, input, select, textarea):not([data-rac]):focus-visible {
outline: 2px solid black;
outline-offset: 1px;
outline-color: Highlight;
outline-color: -webkit-focus-ring-color;
}
+45 -41
View File
@@ -1,10 +1,14 @@
djangoSecretKey: ENC[AES256_GCM,data:TulkgSxtL5YSXuEbmM0vOrSezj+Qod7TAReBs3zoZak782Dr56stTweiSjz5ngtRNh8=,iv:VciiRV5UPvGiNk9yHaw1uT0GEzglQ/Xn2UUxjiycZas=,tag:2LvoFqy6jpSBV679yagA5Q==,type:str]
djangoSecretKey: ENC[AES256_GCM,data:dqeL5W5WaTir+tzUAIbivT1DE0zv1IVsq1/nTU7LRKHO2PUefwsyGAy37eYIeC89q9I=,iv:ODTaWFvvYLHNYRkIX//wY2knKD2uIwf7Mo4UhUBEX9c=,tag:MnE3wLdLcK79XUwXV99w4w==,type:str]
oidc:
clientId: ENC[AES256_GCM,data:7cQzLyVTOqy0OZoL5LEHkBspgqLGzrz02YWma64yzhB+PEj1,iv:vQ49PuVGmms2Y2LIa6uUon0rGO+v0368NuHmpFmqwiE=,tag:39baRMIrS+bvUVpSikgdHg==,type:str]
clientSecret: ENC[AES256_GCM,data:e+FMROduwljv1hJp88WtyOFsYcS6r30D8NB5Eg9lrxVLPZlhXE8KOYDFUhqUCEWb5ZvIk5m90V3lngN1nJJHGA==,iv:kY0rWPFoOFHRpOXQIsaipqjBAnaITfNdPVkU9OtJghk=,tag:cP4cDSOl+W8qMMhXpV+vgA==,type:str]
clientId: ENC[AES256_GCM,data:X+Qj//JXn9kfVszbe9BNbL8rQ53AJq0Cluq3nG58OyXlv5/y,iv:/Na7zvcsIu5OxJw1uFwiksh8tXfzG9It26jW41E0edQ=,tag:oph41uyMwj3uZMnZPI64ZQ==,type:str]
clientSecret: ENC[AES256_GCM,data:2WV+i0pd4wBXiwG8ak+yVROGsmwSaM0X0rEJjFeNeGYtUsHozd5XrurQu6FAB1tQnzTRgRgAU0ob0MqbsMUYhg==,iv:OJZRG+OUD0Fi2HZ0w3Rn+SMFkebMrsQtKvymJkicF28=,tag:PJYzhVvM0gH0xAGrSSPAIQ==,type:str]
#ENC[AES256_GCM,data:iZjWgYcHtDeK/c1TrfVVsZaJRmuc1+2XEJnLEOYw1wXMsUUvgswEc5PP/4UmirhbSulwtuMRhFt74sIogMxPl2etMAoCqo3ziKRuu3FrmgSUD/bdXQRl+VQ0nJ3NnWNcKqf+GS8=,iv:5hUaA4hkABLvXoKVE4TNZevOdUCaCy3zYkGajHpmZ5k=,tag:ikiSAyIly8NAgVA0olASRw==,type:comment]
livekit:
keys:
devkey: ENC[AES256_GCM,data:qlvVa2A1,iv:MNxLlo6Hl3/O/p2JqBLjoe3fmSfhvyAZdv34yKHAHRs=,tag:4rO+fpeweGcyhg93kCN0BA==,type:str]
devkey: ENC[AES256_GCM,data:deDMTTO9,iv:ZCloCUPRJ7PG2KcGsnhVj3lRDfCmJowwp8PVg4fdTmg=,tag:zCh8lQT29kQbY7LiXHNEiA==,type:str]
livekitApi:
key: ENC[AES256_GCM,data:fh0zJSUE,iv:yR5siulW5vlO/Ew8qtYFNNtyODOPm3d3NL4SVCzzVcM=,tag:hLtEuonLJoi0eFJmY0RYEg==,type:str]
secret: ENC[AES256_GCM,data:81SOHzA6,iv:is7QdB/T58IP41SrPyDdFHtyX57tphb9Gpf/aQ7NnTY=,tag:1E03R+EFYmLtxFoIDcVEHg==,type:str]
sops:
kms: []
gcp_kms: []
@@ -14,68 +18,68 @@ sops:
- recipient: age15fyxdwmg5mvldtqqus87xspuws2u0cpvwheehrtvkexj4tnsqqysw6re2x
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5LzE5S3h6c3g3WXlpYVRS
V3lwZWl4YVVudncwMWxwL09KQ1IrWUFmZFVnCmRMcXM1ZUtISVBtcU1xU0pVVDVx
NlBYbDVRbUlNQzU4VS9vZEE1U3p5V1EKLS0tIGhDVldDa1crS2hkY2JkQTMxRDVC
SDZXcThPRGJKaXV5aWlheG1NdTY4d1kK5kqtaAlzs8rTYTggpWC0OOk/TOKxxQId
ec3ZLFN8g/JZiakPola/+u4hsIxkV+y0fUs2CwlDFJnEBwdkNPpBQw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwZG8rTjAzcVNtVHdLZmJI
Mm4vemNrV1dDUExEZ1R1UGJmWjN3aWw1VXc0CjF1MVhhUTV6ckl1OEFNSjBJUCta
MTE3QU04RDJKMWlWcHhDSG1NTmZyTkEKLS0tIHh5UHRqckUxZWZLUDl4d3FDdHJs
Y0VMc0llMytmMWNUOW56d3ltTHpwZ2cKEHkn6wuHNeMTk+E6nEMpEJZ6wpdXSi3k
FkzXRa6SudAgA4R6K9EieHKPdiNvi0IsCJOhLpiNQtENu9poF5ozqQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age16hnlml8yv4ynwy0seer57g8qww075crd0g7nsundz3pj4wk7m3vqftszg7
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMcU02c2RTcWxIb2gxVVBl
N004cXRIT0FOa0JKeTNaSytsa1BBdTdvaTFZCkFzN0tNcEcvZUpjaHdvR0NBUDNp
M1pRckcyK2IxSnZid2o3bG5hNXl3YjgKLS0tIDNJSHp2YmovZzgwUEU5ZWRkQUtu
ZEFxQ0EzVEJCenVxeVFOMWlrMEd6eTQK8RRV0lbOkJaRrHK1yTTQv8Kly2Bccatu
I3ED05o1CaWP+qPYSDmaChzewAW1J2C0AU9k8dpEd94jRIve3nhQMw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAva3lFTTM2ZUJpcnozNmJt
ZHAvRndkSjRCVEZYN29wMWFZVmV5ekNiYW5FCkI3RWUvVTdVZ2dJeWVSY085SW5G
UWNOempRQlFCUUlmL0RIR1ZZZVI5cDQKLS0tIEpwZkVodENLM1VnQ1o1TmhRSFFi
ZEF5MUNVdHRKcW9aK0M2M0ZVNDBxbjgKw8Wp06PcoStrO6ppsOR5zWjXbYrP64Dv
XAEHQMa7vyvuu12YIa/fpyDM4HrsrPq3OWudxuWS6p0X8xmPYrXm6g==
-----END AGE ENCRYPTED FILE-----
- recipient: age1plkp8td6zzfcavjusmsfrlk54t9vn8jjxm8zaz7cmnr7kzl2nfnsd54hwg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTcWl1alFtSkIyM1BvSnAw
YlAxM2RjOXdzUVRDOTUrUVZIWWFQSmd4dWdBCjQ4SUQ1eXdpSmdzMlM3UnlTTy9D
clBOcFFWOWtaZU9OU1JoRXNCbXJ5SVUKLS0tIG1NbUZKajQ5dno3Z2d6NzJtbUNQ
WXRDM1dEeXB5SU52UmVVaFUxTUVLbkkKS3oAFk1CYB03vbXm9Xb4P24/Xui0LM+h
++ykAOWuHjZ0mHbvPXOeS6VFXchdVWtQiHYW6eo3ya524Pq3C3l34Q==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1OU9mZHZFanpTQjE3aGly
VVZDWFh5ZmRHV1YwWUtsZVd6R090SnkxVVNVCmM1UWwwaFlqTHlhSGFRblZBbVhl
aEhtWWp1N1lVTDNqOFJBL0swVFl3Y3cKLS0tIG5ZTHAveFdLdGhQeUc2Tzh1d0dw
UzRhNGNTZFR3U3BLNFhCdFp6aU5uZVEKL4K5jFjPfMp/fMA8+nQerj6PE5zvGeHW
1SuHwnDiglKmksj8Gy+7spwLQCmo11JCnW2gXKktVIe5XOyhortq3Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age12g6f5fse25tgrwweleh4jls3qs52hey2edh759smulwmk5lnzadslu2cp3
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWaGtjcUdZWHFSNGttM1Z3
VFd3SWQ2OFZBRlc3NEJTMWZUa3BPOW9uMGo0CnZyOHRWazdEVzg0VUdEMVdFVXRy
TDFRWW9QYkVzeVoyRVVJNVNIVVRaUHMKLS0tIEFZU1l6THp5bElSMnlCUVN1VitJ
RnhnaElLMjNhUUpkQURQZm82ZTJxOVEKXCvgYbPMsYWT13TpFTZevGX3ZBKb2/Yd
CaBO0I4Ij2ykyK2X3qqJRAPI1cTKROTVv5GCvilxEZWcq0lLwhfNWQ==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4Z1lXM0tDRzJnMytDZHBV
ZlpCcTg0U3RGeEVzN2dBMkFYa0FBMFVKMWw4Cm5KeWMvdFBRNHFJWFNSS3NVWlNQ
dnhkc2F2MFVTMmVHT2U2STdUWWcvNzAKLS0tIHkzQXltQW5TWHVrektQU1hES1ll
UnVld1laTWRmTlkzWHRKRjJmWVJhbU0KANxoSnhDbxsja+Eo8MVCGv6iThNmlo/m
y0RXVNVqNlqTreT0F/SKmP74W3lF30nwsfrOywMQyu75k8p5MGwUuQ==
-----END AGE ENCRYPTED FILE-----
- recipient: age1tl80n23wq6zxegupwn70ew0yp225ua5v4dk800x7g2w6pvlxz46qk592pa
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzbkUrMitDdlJzVUVucFIz
Q2drWDAxcVFhWE5SSDdhVW9wM09oZjI1K1JVClE1MkpYejZxM0dxSTBFWkt0WjR1
Smo5TkEvNzByaDZ0dmMvS25kMUV4QmMKLS0tIEtkaTMyNmw5LzNESVhKTHZKL0E5
WnBhdWRZd1N2a1VBdVpuR2JCYUhINk0KaREjyKJ2ieF72TSuFX5as8odtKQacspa
S/Bsu2YI2YnCKyuL2BOHVT0ETZXyv29a70yxLAWFgnZNEj/MYXzLFw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwUm9oT3RYbGZXaW5CM20r
QlYyeGZjMG05MFNzdndCU2dUemZqdXF5Um1FCkZWdVlubW1MZ3NwM3V2S3dQRFA1
cHVvOXBMMGtMdkF4T2s3Wk9wYkJUZUEKLS0tIFBRM3FWYTRrVDNwZStwQWlpbEYx
dXA5WE5udnhhMVdLTk1jMzJuU3VWaTQKaxTpyqi5fjmOFR4qOxm+wSqWDxb/96QI
rHGUI+CqMVKZ6w1fMH0uanvCuw7Q9rbmsKB+DsjARMLFGqxzP/psUA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1qy04neuzwpasmvljqrcvhwnf0kz5cpyteze38c8avp0czewskasszv9pyw
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4U0kzTmw0N3hMK0lxVFZX
MzRuT2k4WGpxZzZyWmV1dUxvMmhqdHhNekJJCnQ3YXpSVUhoYllKc0FBdStIZ2hK
N0o5UWNTMXk3OHlCVldSREJkNTJSR1EKLS0tIEx3NzBjMi9Pa3NsN24yekltaW1h
RnVweWw1L0RmTDVURGZoTEg5czkyem8KA/VyWUoqAO4+2MJeIAwtKo5GVOyUK7Mq
IDUlHULo5kKZ/DglNwcoErV7MzWFunjXCIseXBPieMrZQ1vC5OJrvA==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwMmVOaHV1Unl4UmUrVXEy
REJIYWZhQmt3R0R5RFRveTl4Z0JaZlpKNmd3Ckl0dkFUaG90TE5odVh0YVprMVZj
T2tXS0dSRGVVczdhb1dlVlV0L3JZUncKLS0tIGo2RDBPZXNzU2dQQlJhY0NBS2ps
RW92OFVTS3d2L3UxOXNrVUFSVFI5TXMKn3pdHbXxBccG6Q+gWPVQK/5wiIKkzdhm
HiNzezStrkFHf/lsFS2LNgYkfMMzBQ4rJj7oQkrD1Z8j6qRld3zzLg==
-----END AGE ENCRYPTED FILE-----
- recipient: age18fgn6j2vwwswqcpv9xpcehq8mrf9zs2sglwkamp3tzwx8d9jq9jsrskrk9
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJQ1Z2UlozTzJsQlBlbnhS
UzJiZFZwK2lXUTVnQVBEQUIxOVU0QzVDUjE4CllhMWc0Nzh1Z3FOTU1ESkE1bW5B
TFFPUjJRUGxiMnBtUktoYk1LamQ0bmMKLS0tIGdPbWFBdDNUUmpIdnN4dCtmamc2
MDJiYzhvNlUvQTlsbUdYdmJkYjNaN3MKdccBEm9dj2Cs3km527bx3w2d3b/rJOi2
QzgHJKaRS3rdCRkXj/cxW0rVgI8twvVj/WV1Into1qKRcAKI6f5zfw==
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzeWN4ZE5Ya3RPV2VKRFdv
UGQ4QTJUZFovSXpQREhNWXdQbktVUHFQaUhjCmM1dXNxbGdlVjBWNjc5L2E5QlBy
NjI0S251cjZvYU1nQ2swWXVDdjdhSG8KLS0tIHdFNXptN1VBNytFNW40MUsxTGFM
WlVnc3VLY1ZReDFxUVlqWU9rM0xMQ00Kq/ckdP1N4BDBo0pSH9pp36skIacwNm3d
utiuCgG16Hqe6YDb3agx81BocuSJ9oMOdpegoztnkkBDDP0F+e/VuQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-07-12T08:47:29Z"
mac: ENC[AES256_GCM,data:slPiXUVmoLGwg3VbsFNAENY84+bSCDQset6cEtbf7LJjJx30/Jqrdg0VXOv/aFmIJsb5mLaPUlylLt36MTw8IYxRPKYY5Gj3yXWRz2SaSX7QKZAba0k5z9MvGy0FjCDlSVe0Xm+SXUoA37t6NFKHgz54Bh2+CLX1H8i7EbZA1ak=,iv:pyjiNZBvq/4u5kiXAzlqu1C4qbupjCZez5Z9vRXLnIg=,tag:EXzeWMfPJGNy4uKXmCdAzA==,type:str]
lastmodified: "2024-07-18T13:08:42Z"
mac: ENC[AES256_GCM,data:a/uHyw9V/SMIePV9nPf+wJgPg+YDYLJGYy7NMLBrBgCXtBWHHonSNjzdmtjix1bW2y+cU0gMqodrtqR1cJGBmXr4NRY7NJqgLWE9rEdYfG7BnfqsWmvAaTIrSs7QMZWkEic7ys/bXoA5BZoau3olhVqIO2A/iyBtoMU9Hv7hPlo=,iv:gaqSCUbN7cxWPNrFPDTl7xNxpOZL6GY/swD/MDCiRqk=,tag:Oz0f/DyD3KGV/9Rprj/1Xw==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1
@@ -1,7 +1,7 @@
image:
repository: lasuite/meet-backend
pullPolicy: Always
tag: "v0.1.0"
tag: "v0.1.1"
backend:
migrateJobAnnotations:
@@ -92,7 +92,7 @@ backend:
secretKeyRef:
name: backend
key: LIVEKIT_API_KEY
LIVEKIT_API_URL: https://livekit-docker-1.beta.numerique.gouv.fr
LIVEKIT_API_URL: https://livekit-preprod.beta.numerique.gouv.fr
createsuperuser:
command:
@@ -106,7 +106,7 @@ frontend:
image:
repository: lasuite/meet-frontend
pullPolicy: Always
tag: "v0.1.0"
tag: "v0.1.1"
ingress:
enabled: true
@@ -1,7 +1,7 @@
image:
repository: lasuite/meet-backend
pullPolicy: Always
tag: "v0.1.0"
tag: "v0.1.1"
backend:
migrateJobAnnotations:
@@ -92,7 +92,7 @@ backend:
secretKeyRef:
name: backend
key: LIVEKIT_API_KEY
LIVEKIT_API_URL: https://livekit-docker-1.beta.numerique.gouv.fr
LIVEKIT_API_URL: https://livekit-preprod.beta.numerique.gouv.fr
createsuperuser:
command:
@@ -106,7 +106,7 @@ frontend:
image:
repository: lasuite/meet-frontend
pullPolicy: Always
tag: "v0.1.0"
tag: "v0.1.1"
ingress:
enabled: true
@@ -92,7 +92,7 @@ backend:
secretKeyRef:
name: backend
key: LIVEKIT_API_KEY
LIVEKIT_API_URL: https://livekit-docker-1.beta.numerique.gouv.fr
LIVEKIT_API_URL: https://livekit-preprod.beta.numerique.gouv.fr
createsuperuser:
command:
@@ -19,6 +19,7 @@ spec:
{{- with .Values.backend.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
checksum/config: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
labels:
{{- include "meet.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
@@ -2,6 +2,7 @@ apiVersion: v1
kind: Secret
metadata:
name: backend
namespace: {{ .Release.Namespace | quote }}
stringData:
DJANGO_SUPERUSER_EMAIL: {{ .Values.djangoSuperUserEmail }}
DJANGO_SUPERUSER_PASSWORD: {{ .Values.djangoSuperUserPass }}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "0.1.0",
"version": "0.1.1",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "meet-openapi-client-ts",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"description": "Tool to generate Typescript API client for the Meet application.",
"scripts": {