From 860c8fc9b827a0fd8f4a9e169d9a55e6426893ad Mon Sep 17 00:00:00 2001 From: Sylvain Zimmer Date: Thu, 29 May 2025 19:53:16 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(buildpack)=20add=20PaaS=20deployment?= =?UTF-8?q?=20support,=20tested=20with=20Scalingo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add PaaS deployment scripts, tested on Scalingo --- CHANGELOG.md | 1 + Procfile | 2 + bin/buildpack_postcompile.sh | 13 ++ bin/buildpack_postfrontend.sh | 58 +++++++ bin/buildpack_start.sh | 18 +++ deploy/paas/servers.conf.erb | 112 ++++++++++++++ docs/installation/README.md | 12 +- docs/installation/scalingo.md | 262 ++++++++++++++++++++++++++++++++ src/backend/impress/settings.py | 7 +- src/backend/pyproject.toml | 1 + src/backend/uv.lock | 14 ++ src/frontend/package.json | 1 + 12 files changed, 496 insertions(+), 5 deletions(-) create mode 100644 Procfile create mode 100755 bin/buildpack_postcompile.sh create mode 100755 bin/buildpack_postfrontend.sh create mode 100755 bin/buildpack_start.sh create mode 100644 deploy/paas/servers.conf.erb create mode 100644 docs/installation/scalingo.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 86cf1aba98..c42037cf45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to ### Added - ✨(backend) support creating subdoc from file #1987 +- ✨(buildpack) add PaaS deployment support, tested with Scalingo #2293 ### Fixed diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..baed9d65ce --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: bin/buildpack_start.sh +postdeploy: python manage.py migrate diff --git a/bin/buildpack_postcompile.sh b/bin/buildpack_postcompile.sh new file mode 100755 index 0000000000..a7477ed5a0 --- /dev/null +++ b/bin/buildpack_postcompile.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -o errexit # always exit on error +set -o pipefail # don't ignore exit codes when piping output + +echo "-----> Running post-compile script" + +rm -rf docker docs env.d gitlint +rm -rf src/frontend/apps +rm -rf src/frontend/packages + +# Remove some of the larger packages required by the frontend only +rm -rf src/frontend/node_modules/@next src/frontend/node_modules/next src/frontend/node_modules/@gouvfr-lasuite diff --git a/bin/buildpack_postfrontend.sh b/bin/buildpack_postfrontend.sh new file mode 100755 index 0000000000..2c389dc916 --- /dev/null +++ b/bin/buildpack_postfrontend.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -o errexit # always exit on error +set -o pipefail # don't ignore exit codes when piping output + +echo "-----> Running post-frontend script" + +# Move the frontend build to the nginx root and clean up +mkdir -p build/ +mv src/frontend/apps/impress/out build/frontend-out + +# Custom logo +ASSETS_DIR=build/frontend-out/assets +if [ -n "$THEME_CUSTOMIZATION_LOGO_URL" ]; then + # Ensure https + [[ ! "$THEME_CUSTOMIZATION_LOGO_URL" =~ ^https:// ]] && echo "[custom-logo] ERROR: URL must use HTTPS" >&2 && exit 1 + + # Prevent SSRF + HOSTNAME=$(echo "$THEME_CUSTOMIZATION_LOGO_URL" | sed -E 's|^https://([^/:]+).*|\1|') + [[ "$HOSTNAME" =~ ^(localhost|127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|0\.0\.0\.0|\[::1\]) ]] && echo "[custom-logo] ERROR: SSRF blocked: $HOSTNAME" >&2 && exit 1 + + LOGO_FILE="${ASSETS_DIR}/icon-docs.svg" + TMP_FILE=$(mktemp "${LOGO_FILE}.XXXXXX.tmp") + + # Actual download + echo "[custom-logo] INFO: Downloading custom logo from: $THEME_CUSTOMIZATION_LOGO_URL" + curl -fsSL --tlsv1.2 -o "$TMP_FILE" "$THEME_CUSTOMIZATION_LOGO_URL" + + # Validate filesize + FILESIZE=$(stat -c%s "$TMP_FILE" 2>/dev/null || stat -f%z "$TMP_FILE") + [[ "$FILESIZE" -eq 0 ]] && echo "[custom-logo] ERROR: empty file" >&2 && exit 1 + [[ "$FILESIZE" -gt 5242880 ]] && echo "[custom-logo] ERROR: file too large (${FILESIZE}B > 5MB)" >&2 && exit 1 + + # Validate file type + IS_SVG=false + + HEADER=$(head -c 100 "$TMP_FILE" | tr -d '\0' | tr '[:upper:]' '[:lower:]') + [[ "$HEADER" =~ ^.*"&2 && exit 1 + + mv -f "$TMP_FILE" "$LOGO_FILE" + echo "[custom-logo] INFO: Custom logo downloaded successfully" +fi + +mv src/backend/* ./ +mv deploy/paas/* ./ + +# Inject custom theme JSON +if [ -n "$THEME_CUSTOMIZATION_JSON" ]; then + echo "[custom-theme] INFO: Deploying the custom theme from THEME_CUSTOMIZATION_JSON." + mkdir -p impress/configuration/theme + echo "$THEME_CUSTOMIZATION_JSON" >impress/configuration/theme/default.json + echo "[custom-theme] INFO: Custom theme deployed successfully." +fi + +echo "3.13" >.python-version diff --git a/bin/buildpack_start.sh b/bin/buildpack_start.sh new file mode 100755 index 0000000000..192636628f --- /dev/null +++ b/bin/buildpack_start.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Start the Django backend server +uvicorn --app-dir=/app --host=0.0.0.0 --timeout-graceful-shutdown=300 --limit-max-requests=20000 --lifespan=off impress.asgi:application & + +# Start the Y provider service +cd src/frontend/servers/y-provider && PORT=4444 ${NODE_BIN:-node} dist/start-server.js & + +# Start the Nginx server +bin/run & + +# if the current shell is killed, also terminate all its children +trap "pkill SIGTERM -P $$" SIGTERM + +# wait for a single child to finish, +wait -n +# then kill all the other tasks +pkill -P $$ diff --git a/deploy/paas/servers.conf.erb b/deploy/paas/servers.conf.erb new file mode 100644 index 0000000000..3dc1e3622c --- /dev/null +++ b/deploy/paas/servers.conf.erb @@ -0,0 +1,112 @@ +# ERB templated nginx configuration +# see https://doc.scalingo.com/platform/deployment/buildpacks/nginx + +upstream backend_server { + server localhost:8000 fail_timeout=0; +} + +upstream collaboration_server { + server localhost:4444 fail_timeout=0; +} + +server { + listen <%= ENV["PORT"] %>; + server_name _; + + root /app/build/frontend-out; + + error_page 404 /404.html; + + location /collaboration/api/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://collaboration_server; + } + + location /collaboration/ws/ { + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + + # Set appropriate timeout for WebSocket + proxy_read_timeout 86400; + proxy_send_timeout 86400; + + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://collaboration_server; + } + + # Django rest framework + location ^~ /api/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://backend_server; + } + + # Django admin + location ^~ /admin/ { + proxy_set_header X-Forwarded-Proto https; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_redirect off; + proxy_pass http://backend_server; + } + + # Proxy auth for media + location /media/ { + # Auth request configuration + auth_request /media-auth; + auth_request_set $authHeader $upstream_http_authorization; + auth_request_set $authDate $upstream_http_x_amz_date; + auth_request_set $authContentSha256 $upstream_http_x_amz_content_sha256; + + # Pass specific headers from the auth response + proxy_set_header Authorization $authHeader; + proxy_set_header X-Amz-Date $authDate; + proxy_set_header X-Amz-Content-SHA256 $authContentSha256; + + # Get resource from Object Storage + proxy_pass <%= ENV["AWS_S3_ENDPOINT_URL"] %>/<%= ENV["AWS_STORAGE_BUCKET_NAME"] %>/; + proxy_set_header Host <%= ENV["AWS_S3_ENDPOINT_URL"].split("://")[1] %>; + + add_header Content-Security-Policy "default-src 'none'" always; + } + + location /media-auth { + proxy_pass http://backend_server/api/v1.0/documents/media-auth/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Original-URL $request_uri; + + # Prevent the body from being passed + proxy_pass_request_body off; + proxy_set_header Content-Length ""; + proxy_set_header X-Original-Method $request_method; + } + + location / { + try_files $uri index.html $uri/ =404; + } + + location ~ "^/docs/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/?$" { + try_files $uri /docs/[id]/index.html; + } + + location = /404.html { + internal; + } + +} diff --git a/docs/installation/README.md b/docs/installation/README.md index c2e080c23e..d68e3c37f0 100644 --- a/docs/installation/README.md +++ b/docs/installation/README.md @@ -7,11 +7,15 @@ We (Docs maintainers) are only using the Kubernetes deployment method in product Please follow the instructions laid out [here](/docs/installation/kubernetes.md). ## Docker Compose -We are aware that not everyone has Kubernetes Cluster laying around 😆. -We also provide [Docker images](https://hub.docker.com/u/lasuite?page=1&search=impress) that you can deploy using Compose. -Please follow the instructions [here](/docs/installation/compose.md). +We are aware that not everyone has Kubernetes Cluster laying around 😆. +We also provide [Docker images](https://hub.docker.com/u/lasuite?page=1&search=impress) that you can deploy using Compose. +Please follow the instructions [here](/docs/installation/compose.md). ⚠️ Please keep in mind that we do not use it ourselves in production. Let us know in the issues if you run into troubles, we'll try to help. +## Scalingo +You can deploy Docs on [Scalingo](https://scalingo.com/) using a custom buildpack. This method handles both frontend and backend builds, serving them through Nginx with the collaboration server (y-provider). +Please follow the instructions [here](/docs/installation/scalingo.md). + ## Other ways to install Docs Community members have contributed several other ways to install Docs. While we owe them a big thanks 🙏, please keep in mind we (Docs maintainers) can't provide support on these installation methods as we don't use them ourselves and there are too many options out there for us to keep track of. Of course you can contact the contributors and the broader community for assistance. @@ -29,4 +33,4 @@ Some cloud providers are making it easy to deploy Docs on their infrastructure. Here is the list in alphabetical order: - Clever Cloud 🇫🇷 : [market place][https://www.clever-cloud.com/product/docs/], [technical doc](https://www.clever.cloud/developers/guides/docs/#deploy-docs) -Feel free to make a PR to add ones that are not listed above 🙏 \ No newline at end of file +Feel free to make a PR to add ones that are not listed above 🙏 diff --git a/docs/installation/scalingo.md b/docs/installation/scalingo.md new file mode 100644 index 0000000000..564ccf5d11 --- /dev/null +++ b/docs/installation/scalingo.md @@ -0,0 +1,262 @@ +# Deployment on Scalingo + +This guide explains how to deploy Docs on [Scalingo](https://scalingo.com/) using a custom buildpack. + +## Overview + +Scalingo is a Platform-as-a-Service (PaaS) that simplifies application deployment. This setup uses a custom buildpack to handle both the frontend (Next.js static export) and backend (Django) builds, serving them through Nginx. The collaboration server (y-provider) runs alongside the Django backend. + +## Prerequisites + +- A Scalingo account +- Scalingo CLI installed (optional but recommended) +- A PostgreSQL database Scalingo addon +- A Redis Scalingo addon (for caching and sessions) +- An external Identity Provider that supports OpenID Connect protocol +- An external Object Storage that implements S3 API + +## Step 1: Create Your App + +Create a new app on Scalingo using `scalingo` CLI or using the [Scalingo dashboard](https://dashboard.scalingo.com/). + +## Step 2: Provision Addons + +Add the required PostgreSQL and Redis services. + +This will set the following environment variables automatically: +- `SCALINGO_POSTGRESQL_URL` - Database connection string +- `SCALINGO_REDIS_URL` - Redis connection string + +## Step 3: Configure Environment Variables + +Set the following environment variables in your Scalingo app: + +### Buildpack Configuration + +```bash +scalingo env-set BUILDPACK_URL="https://github.com/suitenumerique/buildpack#main" +scalingo env-set LASUITE_APP_NAME="docs" +scalingo env-set LASUITE_BACKEND_DIR="src/backend/" +scalingo env-set LASUITE_FRONTEND_DIR="src/frontend/" +scalingo env-set LASUITE_NGINX_DIR="." +scalingo env-set LASUITE_SCRIPT_POSTCOMPILE="bin/buildpack_postcompile.sh" +scalingo env-set LASUITE_SCRIPT_POSTFRONTEND="bin/buildpack_postfrontend.sh" +``` + +### Database and Cache + +```bash +scalingo env-set DATABASE_URL="\$SCALINGO_POSTGRESQL_URL" +scalingo env-set REDIS_URL="\$SCALINGO_REDIS_URL" +``` + +### Django Settings + +```bash +scalingo env-set DJANGO_SETTINGS_MODULE="impress.settings" +scalingo env-set DJANGO_CONFIGURATION="Production" +scalingo env-set DJANGO_SECRET_KEY="" +scalingo env-set DJANGO_ALLOWED_HOSTS="my-docs-app.osc-fr1.scalingo.io" +``` + +### OIDC Authentication + +Configure your OIDC provider (e.g., Keycloak, Authentik): + +```bash +scalingo env-set OIDC_RP_CLIENT_ID="docs-client-id" +scalingo env-set OIDC_RP_CLIENT_SECRET="" +scalingo env-set OIDC_RP_SIGN_ALGO="RS256" +scalingo env-set OIDC_OP_BASE_URL="https://auth.yourdomain.com/realms/docs" +``` + +Or configure individual endpoints if your provider doesn't support discovery: + +```bash +scalingo env-set OIDC_OP_AUTHORIZATION_ENDPOINT="https://auth.yourdomain.com/authorize" +scalingo env-set OIDC_OP_TOKEN_ENDPOINT="https://auth.yourdomain.com/token" +scalingo env-set OIDC_OP_USER_ENDPOINT="https://auth.yourdomain.com/userinfo" +scalingo env-set OIDC_OP_JWKS_ENDPOINT="https://auth.yourdomain.com/.well-known/jwks.json" +scalingo env-set OIDC_OP_LOGOUT_ENDPOINT="https://auth.yourdomain.com/logout" +``` + +### S3 Media Storage + +To store uploaded media files in an S3-compatible object storage: + +```bash +scalingo env-set AWS_S3_ENDPOINT_URL="https://s3.amazonaws.com" +scalingo env-set AWS_S3_ACCESS_KEY_ID="" +scalingo env-set AWS_S3_SECRET_ACCESS_KEY="" +scalingo env-set AWS_STORAGE_BUCKET_NAME="docs-media" +scalingo env-set AWS_S3_REGION_NAME="eu-west-1" +``` + +### Email Configuration (Optional) + +For email notifications see [https://doc.scalingo.com/platform/app/sending-emails](https://doc.scalingo.com/platform/app/sending-emails): + +```bash +scalingo env-set DJANGO_EMAIL_HOST="smtp.example.org" +scalingo env-set DJANGO_EMAIL_PORT="587" +scalingo env-set DJANGO_EMAIL_HOST_USER="" +scalingo env-set DJANGO_EMAIL_HOST_PASSWORD="" +scalingo env-set DJANGO_EMAIL_USE_TLS="True" +scalingo env-set DJANGO_EMAIL_FROM="docs@yourdomain.com" +``` + +## Step 4: Deploy + +Deploy your application: + +```bash +git push scalingo main +``` + +The buildpack will automatically: +1. Build the frontend (Next.js static export) +2. Build the backend (Django) +3. Run the post-compile script (cleanup unused files to reduce slug size) +4. Run the post-frontend script (move assets, inject theme, prepare for deployment) +5. Start uvicorn, the y-provider collaboration server, and Nginx +6. Run Django migrations + +## Step 5: Create superuser + +After the first deployment, create an admin user: + +```bash +scalingo run python manage.py createsuperuser +``` + +## Custom Domain (Optional) + +To use a custom domain: + +1. Add the domain in Scalingo dashboard +2. Update `DJANGO_ALLOWED_HOSTS` with your custom domain +3. Configure your DNS to point to Scalingo + +```bash +scalingo domains-add docs.yourdomain.com +scalingo env-set DJANGO_ALLOWED_HOSTS="docs.yourdomain.com,my-docs-app.osc-fr1.scalingo.io" +``` + +## Theme Customization + +Docs supports theme customization via environment variables. The theme controls the appearance of the header, footer, waffle (La Suite services widget), favicon, and more. + +### Custom Logo (Optional) + +To replace the default Docs logo with your own, set the `THEME_CUSTOMIZATION_LOGO_URL` environment variable with an HTTPS URL pointing to an SVG file (max 5MB): + +```bash +scalingo env-set THEME_CUSTOMIZATION_LOGO_URL="https://cdn.yourdomain.com/logo.svg" +``` + +The logo is validated during build: +- Must use HTTPS +- Must be a valid SVG file +- Must not exceed 5MB +- SSRF protection is applied + +### Custom Theme (Optional) + +To customize the theme (footer links, waffle, translations, etc.), set the `THEME_CUSTOMIZATION_JSON` environment variable with a JSON object. The buildpack merges your custom JSON with the default theme, so you only need to specify the parts you want to override. + +> **Important:** The `THEME_CUSTOMIZATION_JSON` value must be valid JSON. Ensure it is properly escaped when setting as an environment variable. + +```bash +scalingo env-set THEME_CUSTOMIZATION_JSON='{"footer":{"default":{"externalLinks":[{"label":"GitHub","href":"https://github.com/your-org/"},{"label":"Your Org","href":"https://yourdomain.com"}],"legalLinks":[{"label":"Legal Notice","href":"https://docs.yourdomain.com/legal/"},{"label":"Privacy Policy","href":"https://docs.yourdomain.com/privacy/"}],"bottomInformation":{"label":"Unless otherwise stated, all content on this site is under","link":{"label":"licence etalab-2.0","href":"https://github.com/etalab/licence-ouverte/blob/master/LO.md"}}},"en":{"bottomInformation":{"label":"Unless otherwise stated, all content on this site is under","link":{"label":"licence MIT","href":"https://github.com/your-org/license"}}},"fr":{"bottomInformation":{"label":"Sauf mention contraire, tout le contenu de ce site est sous","link":{"label":"licence etalab-2.0","href":"https://github.com/etalab/licence-ouverte/blob/master/LO.md"}}}},"waffle":{"apiUrl":"https://your-api.example.com/api/v1.0/lagaufre/services/","widgetPath":"https://static.example.com/widgets/"},"header":{"logo":{},"icon":{"src":"/assets/icon-docs.svg","style":{"width":"32px","height":"auto"},"alt":"Your Org Logo","withTitle":true}},"home":{"with-proconnect":false,"icon-banner":{"src":"/assets/icon-docs.svg","style":{"width":"64px","height":"auto"},"alt":"Your Org Logo"}},"favicon":{"light":{"href":"/assets/favicon-light.png","type":"image/png"},"dark":{"href":"/assets/favicon-dark.png","type":"image/png"}}}' +``` + +#### Available Theme Sections and Configuration + +For detailed information on all available theme sections, waffle configuration modes, and customization options, see the [Customization Guide](../customization.md). + +#### Theme Cache + +The theme is cached in Redis for 24 hours by default. If you update `THEME_CUSTOMIZATION_JSON` and don't see changes, clear the Redis cache: + +```bash +scalingo run python -c "from django.core.cache import cache; cache.clear()" +``` + +Or set `THEME_CUSTOMIZATION_CACHE_TIMEOUT` to a shorter duration: + +```bash +scalingo env-set THEME_CUSTOMIZATION_CACHE_TIMEOUT=60 +``` + +> **Note:** Changing `THEME_CUSTOMIZATION_CACHE_TIMEOUT` does not clear existing cached values in Redis. After changing this setting, clear the cache manually: `scalingo run python -c "from django.core.cache import cache; cache.clear()"` + +## Troubleshooting + +### Check Logs + +```bash +scalingo logs --tail +``` + +### Common Issues + +1. **Build fails**: Check that all required environment variables are set +2. **Database connection error**: Verify `DATABASE_URL` is correctly set to `$SCALINGO_POSTGRESQL_URL` +3. **Static files not served**: Ensure the buildpack post-frontend script ran successfully +4. **OIDC errors**: Verify your OIDC provider configuration and callback URLs +5. **Theme not updating**: Clear Redis cache with `scalingo run python -c "from django.core.cache import cache; cache.clear()"` +6. **Collaboration not working**: Verify the y-provider server is running and the WebSocket URL is configured + +### Useful Commands + +```bash +# Open a console +scalingo run bash + +# Restart the app +scalingo restart + +# Scale containers +scalingo scale web:2 + +# One-off command +scalingo run python manage.py shell + +# Check environment variables +scalingo env + +# View app status +scalingo status +``` + +## Architecture + +On Scalingo, the application runs as follows: + +### Build Phase + +1. The buildpack compiles the frontend (Next.js static export) +2. The buildpack compiles the backend (Python dependencies) +3. `bin/buildpack_postcompile.sh` runs to clean up unused files and reduce slug size +4. `bin/buildpack_postfrontend.sh` moves the frontend build to `build/frontend-out`, downloads custom logos, injects the custom theme, and prepares the deployment structure + +### Runtime + +The `bin/buildpack_start.sh` script starts three processes: + +- **Nginx** serves static files and proxies requests to the backend +- **uvicorn** runs the Django ASGI application on port 8000 +- **y-provider** runs the collaboration WebSocket server on port 4444 + +Nginx routes: +- `/api/` and `/admin/` → Django backend (port 8000) +- `/collaboration/api/` and `/collaboration/ws/` → y-provider (port 4444) +- `/media/` → S3 object storage (with auth proxy) +- `/` → Static frontend files + +## Additional Resources + +- [Scalingo Documentation](https://doc.scalingo.com/) +- [Docs Environment Variables](../env.md) +- [Theme Customization](../customization.md) +- [Django Configurations Documentation](https://django-configurations.readthedocs.io/) diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 28cec2190b..a2f4554a3d 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -16,6 +16,7 @@ from django.utils.translation import gettext_lazy as _ +import dj_database_url import sentry_sdk from configurations import Configuration, values from corsheaders.defaults import default_headers @@ -83,7 +84,11 @@ class Base(Configuration): # Database DATABASES = { - "default": { + "default": dj_database_url.config() + if values.DatabaseURLValue( + None, environ_name="DATABASE_URL", environ_prefix=None + ) + else { "ENGINE": values.Value( "django.db.backends.postgresql", environ_name="DB_ENGINE", diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index dbb9f619da..1e9f75371c 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ "boto3==1.42.93", "Brotli==1.2.0", "celery[redis]==5.5.3", + "dj-database-url==3.1.2", "django-configurations==2.5.1", "django-cors-headers==4.9.0", "django-countries==8.2.0", diff --git a/src/backend/uv.lock b/src/backend/uv.lock index 1b0366e606..2f06baef0b 100644 --- a/src/backend/uv.lock +++ b/src/backend/uv.lock @@ -392,6 +392,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "dj-database-url" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/f6/00b625e9d371b980aa261011d0dc906a16444cb688f94215e0dc86996eb5/dj_database_url-3.1.2.tar.gz", hash = "sha256:63c20e4bbaa51690dfd4c8d189521f6bf6bc9da9fcdb23d95d2ee8ee87f9ec62", size = 11490, upload-time = "2026-02-19T15:30:23.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/a9/57c66006373381f1d3e5bd94216f1d371228a89f443d3030e010f73dd198/dj_database_url-3.1.2-py3-none-any.whl", hash = "sha256:544e015fee3efa5127a1eb1cca465f4ace578265b3671fe61d0ed7dbafb5ec8a", size = 8953, upload-time = "2026-02-19T15:30:39.37Z" }, +] + [[package]] name = "django" version = "5.2.14" @@ -878,6 +890,7 @@ dependencies = [ { name = "boto3" }, { name = "brotli" }, { name = "celery", extra = ["redis"] }, + { name = "dj-database-url" }, { name = "django" }, { name = "django-configurations" }, { name = "django-cors-headers" }, @@ -946,6 +959,7 @@ requires-dist = [ { name = "boto3", specifier = "==1.42.93" }, { name = "brotli", specifier = "==1.2.0" }, { name = "celery", extras = ["redis"], specifier = "==5.5.3" }, + { name = "dj-database-url", specifier = "==3.1.2" }, { name = "django", specifier = "<6.0.0" }, { name = "django-configurations", specifier = "==2.5.1" }, { name = "django-cors-headers", specifier = "==4.9.0" }, diff --git a/src/frontend/package.json b/src/frontend/package.json index 5515652c00..eade844c92 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -22,6 +22,7 @@ "app:build": "yarn APP_IMPRESS run build", "app:test": "yarn APP_IMPRESS run test", "ci:build": "yarn APP_IMPRESS run build:ci", + "build": "yarn APP_IMPRESS run build && yarn COLLABORATION_SERVER run build", "e2e:test": "yarn APP_E2E run test", "lint": "yarn APP_IMPRESS run lint && yarn APP_E2E run lint && yarn workspace eslint-plugin-docs run lint && yarn I18N run lint && yarn COLLABORATION_SERVER run lint", "i18n:extract": "yarn I18N run extract-translation",