Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion ENVIRONMENT_VARIABLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ For a working starting point, see [`server/.env.sample`](./server/.env.sample) (
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BASE_URL` | Public URL of the application (e.g. `https://projects.example.com`). Used to build absolute links (emails, webhooks, OIDC redirect/logout URIs) and to derive the base path/protocol. |
| `DATABASE_URL` | PostgreSQL connection string (e.g. `postgresql://user:password@host:5432/db`). |
| `SECRET_KEY` | Secret used to sign session cookies. Use a long, random value in production. |
| `SECRET_KEY` | Secret used to sign session cookies. Use a long, random value in production. When running multiple replicas, they must all share the **same** value (see [Horizontal scaling](#horizontal-scaling-multiple-instances-optional)). |

### Core / general

Expand Down Expand Up @@ -63,6 +63,18 @@ Only needed to store attachments/avatars/backgrounds on an S3-compatible bucket
| `S3_BUCKET` | Bucket name. |
| `S3_FORCE_PATH_STYLE` | Set to `true` for endpoints that require path-style requests (e.g. MinIO). |

### Horizontal scaling (multiple instances), optional

Only needed when running **more than one** instance/process behind a load balancer. By default the app keeps sessions in memory and broadcasts realtime socket events per-process, so a second instance would not receive the live updates emitted by the first. Setting `REDIS_URL` shares both sessions and socket.io broadcasts through Redis (via `@sailshq/connect-redis` and `@sailshq/socket.io-redis`), which is required for the realtime collaboration to work across instances. Only applied in production (`NODE_ENV=production`).

All replicas must sit behind a load balancer that forwards to each instance's app port (`1337`) and share the **same `SECRET_KEY`**: session ID cookies are signed with it, so a cookie issued by one instance is only accepted by the others when the secret matches. They must likewise share the same `DATABASE_URL` and `REDIS_URL`. (How the balancer reaches that port — a published host port vs. the container directly — depends on your deployment; see [`docker-compose.yml`](./docker-compose.yml) for the Docker topology.)

To scale out you must **also** move file storage off local disk to [S3](#object-storage-s3-optional) — otherwise attachments/avatars/backgrounds uploaded on one instance are not visible from the others.

| Variable | Default | Description |
| ----------- | ------- | ----------------------------------------------------------------------------------------------------- |
| `REDIS_URL` | unset | Redis connection string (e.g. `redis://user:password@host:6379/0`). Enables shared sessions + sockets. |

Comment thread
coderabbitai[bot] marked this conversation as resolved.
### OpenID Connect (OIDC)

| Variable | Default | Description |
Expand Down
27 changes: 27 additions & 0 deletions client/patches/@gouvfr-lasuite+ui-kit+0.19.10.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
diff --git a/node_modules/@gouvfr-lasuite/ui-kit/dist/index.js b/node_modules/@gouvfr-lasuite/ui-kit/dist/index.js
index f13985b..bcd7389 100644
--- a/node_modules/@gouvfr-lasuite/ui-kit/dist/index.js
+++ b/node_modules/@gouvfr-lasuite/ui-kit/dist/index.js
@@ -287,9 +287,9 @@ function k1() {
return Va || (Va = 1, process.env.NODE_ENV === "production" ? mn.exports = A1() : mn.exports = T1()), mn.exports;
}
var g = k1();
-const I1 = { share: { copyLink: "Copy link", ok: "OK", shareButton: "Share", modalTitle: "Share folder", modalAriaLabel: "Share modal", access: { delete: "Remove access" }, cannot_view: { message: "You can view this item but you need additional access to view its members or modify the settings." }, invitations: { title: "Pending invitations" }, members: { title_plural: "Shared between {count} people", title_singular: "Shared between {count} person", load_more: "Show more " }, item: { add: "Add" }, search: { placeholder: "Search user", group_name: "Search user result" }, user: { no_result: "No result", placeholder: "Search for a user to invite" }, linkSettings: { title: "Link settings", reach: { choices: { public: { title: "Public", description: "Anyone with the link can access the document" }, authenticated: { title: "Authenticated", description: "Only authenticated users can access the document" }, restricted: { title: "Private", description: "Only users of the space can access the document" } } }, role: { choices: { reader: { title: "Reader" }, editor: { title: "Editor" } } } } }, laGaufre: { label: "Digital LaSuite services", closeLabel: "Close the menu", viewMoreLabel: "View more", viewLessLabel: "View less", loadingText: "Loading…", newWindowLabelSuffix: " (new window)", headerLabel: "About" }, userMenu: { term_of_service: "Terms of service", manage_account: "Manage account", open: "Open user menu", close: "Close user menu", accountSettings: "Account settings", logout: "Logout", dialogTitle: "User menu" }, treeView: { viewMore: "Load more elements" }, footer: { logo: { alt: "Logo of the French government" } }, onboarding: { skip: "Skip", next: "Next", previous: "Previous", complete: "Understood", stepLabel: "Step {current} of {total}: {title}", currentStepSuffix: " (current)", contentRegionLabel: "Step {current} of {total} content" } }, O1 = {
+const I1 = { share: { copyLink: "Copy link", ok: "OK", shareButton: "Share", modalTitle: "Share folder", modalAriaLabel: "Share modal", access: { delete: "Remove access" }, cannot_view: { message: "You can view this item but you need additional access to view its members or modify the settings." }, invitations: { title: "Pending invitations" }, members: { title_plural: "Shared between {count} people", title_singular: "Shared between {count} person", load_more: "Show more " }, item: { add: "Add" }, search: { placeholder: "Search user", group_name: "Search user result" }, user: { no_result: "No result, please use the whole email address to expand the search to all platform users", placeholder: "Search for a user to invite" }, linkSettings: { title: "Link settings", reach: { choices: { public: { title: "Public", description: "Anyone with the link can access the document" }, authenticated: { title: "Authenticated", description: "Only authenticated users can access the document" }, restricted: { title: "Private", description: "Only users of the space can access the document" } } }, role: { choices: { reader: { title: "Reader" }, editor: { title: "Editor" } } } } }, laGaufre: { label: "Digital LaSuite services", closeLabel: "Close the menu", viewMoreLabel: "View more", viewLessLabel: "View less", loadingText: "Loading…", newWindowLabelSuffix: " (new window)", headerLabel: "About" }, userMenu: { term_of_service: "Terms of service", manage_account: "Manage account", open: "Open user menu", close: "Close user menu", accountSettings: "Account settings", logout: "Logout", dialogTitle: "User menu" }, treeView: { viewMore: "Load more elements" }, footer: { logo: { alt: "Logo of the French government" } }, onboarding: { skip: "Skip", next: "Next", previous: "Previous", complete: "Understood", stepLabel: "Step {current} of {total}: {title}", currentStepSuffix: " (current)", contentRegionLabel: "Step {current} of {total} content" } }, O1 = {
components: I1
-}, N1 = { share: { copyLink: "Copier le lien", ok: "OK", modalTitle: "Partage", modalAriaLabel: "Modale de partage", shareButton: "Partager", access: { delete: "Retirer l'accès" }, cannot_view: { message: "Vous pouvez voir cet élément mais vous avez besoin d'un accès supplémentaire pour voir ses membres ou modifier les paramètres." }, invitations: { title: "Invitations en attente" }, members: { title_plural: "Partagé entre {count} personnes", title_singular: "Partagé entre {count} personne", load_more: "Afficher plus" }, item: { add: "Ajouter" }, search: { placeholder: "Rechercher un utilisateur", group_name: "Résultat de la recherche d'utilisateur" }, user: { no_result: "Aucun résultat", placeholder: "Rechercher un utilisateur à inviter" }, linkSettings: { title: "Paramètres du lien", reach: { choices: { public: { title: "Public", description: "N'importe qui avec le lien peut accéder au document" }, restricted: { title: "Privé", description: "Seules les personnes avec accès peuvent l'ouvrir à l'aide du lien" }, authenticated: { title: "Authenticated", description: "Seuls les utilisateurs authentifiés peuvent accéder au document" } } }, role: { choices: { reader: { title: "Lecture seule" }, editor: { title: "Édition" } } } } }, laGaufre: { label: "Services de la Suite numérique", closeLabel: "Fermer le menu", loadingText: "Chargement…", viewMoreLabel: "Voir plus", viewLessLabel: "Voir moins", newWindowLabelSuffix: " (nouvelle fenêtre)", headerLabel: "À propos" }, userMenu: { term_of_service: "Conditions d'utilisation", open: "Ouvrir le menu utilisateur", close: "Fermer le menu utilisateur", accountSettings: "Paramètres du compte", logout: "Déconnexion", dialogTitle: "Menu utilisateur", manage_account: "Gérer le compte" }, treeView: { viewMore: "Charger plus d'éléments" }, footer: { logo: { alt: "Logo du gouvernement français" } }, onboarding: { skip: "Passer", next: "Suivant", previous: "Précédent", complete: "Compris", stepLabel: "Étape {current} sur {total} : {title}", currentStepSuffix: " (actuelle)", contentRegionLabel: "Contenu de l'étape {current} sur {total}" } }, B1 = {
+}, N1 = { share: { copyLink: "Copier le lien", ok: "OK", modalTitle: "Partage", modalAriaLabel: "Modale de partage", shareButton: "Partager", access: { delete: "Retirer l'accès" }, cannot_view: { message: "Vous pouvez voir cet élément mais vous avez besoin d'un accès supplémentaire pour voir ses membres ou modifier les paramètres." }, invitations: { title: "Invitations en attente" }, members: { title_plural: "Partagé entre {count} personnes", title_singular: "Partagé entre {count} personne", load_more: "Afficher plus" }, item: { add: "Ajouter" }, search: { placeholder: "Rechercher un utilisateur", group_name: "Résultat de la recherche d'utilisateur" }, user: { no_result: "Aucun résultat, veuillez utiliser l'adresse email complète pour élargir la recherche à tous les utilisateurs de la plateforme", placeholder: "Rechercher un utilisateur à inviter" }, linkSettings: { title: "Paramètres du lien", reach: { choices: { public: { title: "Public", description: "N'importe qui avec le lien peut accéder au document" }, restricted: { title: "Privé", description: "Seules les personnes avec accès peuvent l'ouvrir à l'aide du lien" }, authenticated: { title: "Authenticated", description: "Seuls les utilisateurs authentifiés peuvent accéder au document" } } }, role: { choices: { reader: { title: "Lecture seule" }, editor: { title: "Édition" } } } } }, laGaufre: { label: "Services de la Suite numérique", closeLabel: "Fermer le menu", loadingText: "Chargement…", viewMoreLabel: "Voir plus", viewLessLabel: "Voir moins", newWindowLabelSuffix: " (nouvelle fenêtre)", headerLabel: "À propos" }, userMenu: { term_of_service: "Conditions d'utilisation", open: "Ouvrir le menu utilisateur", close: "Fermer le menu utilisateur", accountSettings: "Paramètres du compte", logout: "Déconnexion", dialogTitle: "Menu utilisateur", manage_account: "Gérer le compte" }, treeView: { viewMore: "Charger plus d'éléments" }, footer: { logo: { alt: "Logo du gouvernement français" } }, onboarding: { skip: "Passer", next: "Suivant", previous: "Précédent", complete: "Compris", stepLabel: "Étape {current} sur {total} : {title}", currentStepSuffix: " (actuelle)", contentRegionLabel: "Contenu de l'étape {current} sur {total}" } }, B1 = {
components: N1
};
function Qi(t) {
@@ -21568,9 +21568,7 @@ const La = ({
(X) => !I.includes(X)
);
let V = m(E !== "" ? "components.share.user.no_result" : "components.share.user.placeholder");
- const Q = ((X) => !!X.match(
- /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z\-0-9]{2,}))$/
- ))(E ?? "") && !(R != null && R.some((X) => X.email === E)), te = {
+ const Q = false, te = {
id: E,
full_name: "",
email: E
Loading
Loading