From 213656fc2e2d63ab742b59da85f2145967f08891 Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Thu, 12 Jun 2025 15:10:37 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB(docker)=20spl?= =?UTF-8?q?it=20frontend=20to=20another=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit aims at improving the user experience: - Use a dedicated `Dockerfile` for the frontend - Run the backend and frontend in "watch"/dev mode in Docker - Do not start all Docker instances for small tasks --- .github/workflows/docker-hub.yml | 3 +- .github/workflows/people.yml | 6 +- CHANGELOG.md | 4 + Dockerfile | 55 ------ Makefile | 47 ++--- README.md | 8 +- bin/_config.sh | 17 ++ bin/pylint | 2 +- docker-compose.yml | 162 +++++++++++++----- docker/auth/realm.json | 2 +- env.d/development/common.dist | 1 + env.d/development/common.e2e.dist | 6 +- .../locale/en_US/LC_MESSAGES/django.mo | Bin 476 -> 476 bytes .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 16435 -> 17671 bytes src/backend/pyproject.toml | 2 +- src/frontend/Dockerfile | 63 +++++++ src/frontend/apps/desk/conf/default.conf | 1 + .../__tests__/app-desk/mail-domain.spec.ts | 1 + .../app-desk/mail-domains-add.spec.ts | 2 + .../__tests__/app-desk/teams-create.spec.ts | 1 + src/frontend/apps/e2e/playwright.config.ts | 6 +- 21 files changed, 257 insertions(+), 132 deletions(-) create mode 100644 src/frontend/Dockerfile diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index f149a29..c65a3af 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -40,7 +40,7 @@ jobs: name: Run trivy scan (frontend) uses: numerique-gouv/action-trivy-cache@main with: - docker-build-args: '--target frontend-production -f Dockerfile' + docker-build-args: '--target frontend-production -f src/frontend/Dockerfile' docker-image-name: 'docker.io/lasuite/people-frontend:${{ github.sha }}' build-and-push-backend: @@ -105,6 +105,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . + file: ./src/frontend/Dockerfile target: frontend-production build-args: DOCKER_USER=${{ env.DOCKER_USER }}:-1000 push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/people.yml b/.github/workflows/people.yml index 3f174a4..fcec617 100644 --- a/.github/workflows/people.yml +++ b/.github/workflows/people.yml @@ -167,6 +167,8 @@ jobs: COMPOSE_DOCKER_CLI_BUILD: 1 run: | docker compose build --pull --build-arg BUILDKIT_INLINE_CACHE=1 + make update-keycloak-realm-app + make add-dev-rsa-private-key-to-env make run - name: Apply DRF migrations @@ -177,10 +179,6 @@ jobs: run: | make demo FLUSH_ARGS='--no-input' - - name: Setup Dimail DB - run: | - make dimail-setup-db - - name: Run e2e tests run: cd src/frontend/ && yarn e2e:test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 685dc61..a1972de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to - ✨(resource-server) add SCIM /Me endpoint #895 +### Changed + +- 🧑‍💻(docker) split frontend to another file #924 + ## [1.17.0] - 2025-06-11 ### Added diff --git a/Dockerfile b/Dockerfile index e755fcc..08137d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,61 +10,6 @@ RUN python -m pip install --upgrade pip setuptools RUN apk update && \ apk upgrade -### ---- Front-end dependencies image ---- -FROM node:20 AS frontend-deps - -WORKDIR /deps - -COPY ./src/frontend/package.json ./package.json -COPY ./src/frontend/yarn.lock ./yarn.lock -COPY ./src/frontend/apps/desk/package.json ./apps/desk/package.json -COPY ./src/frontend/packages/i18n/package.json ./packages/i18n/package.json -COPY ./src/frontend/packages/eslint-config-people/package.json ./packages/eslint-config-people/package.json - -RUN yarn --frozen-lockfile - -### ---- Front-end builder dev image ---- -FROM node:20 AS frontend-builder-dev - -WORKDIR /builder - -COPY --from=frontend-deps /deps/node_modules ./node_modules -COPY ./src/frontend . - -WORKDIR ./apps/desk - -### ---- Front-end builder image ---- -FROM frontend-builder-dev AS frontend-builder - -RUN yarn build - -# ---- Front-end image ---- -FROM nginxinc/nginx-unprivileged:1.27-alpine AS frontend-production - -USER root - -RUN apk update && apk upgrade libssl3 libcrypto3 libxml2 - -USER nginx - -# Un-privileged user running the application -ARG DOCKER_USER -USER ${DOCKER_USER} - -COPY --from=frontend-builder \ - /builder/apps/desk/out \ - /usr/share/nginx/html - -COPY ./src/frontend/apps/desk/conf/default.conf /etc/nginx/conf.d - -# Copy entrypoint -COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint - -ENTRYPOINT [ "/usr/local/bin/entrypoint" ] - -CMD ["nginx", "-g", "daemon off;"] - - # ---- Back-end builder image ---- FROM base AS back-builder diff --git a/Makefile b/Makefile index 16f6435..9059650 100644 --- a/Makefile +++ b/Makefile @@ -41,11 +41,9 @@ DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID) COMPOSE = DOCKER_USER=$(DOCKER_USER) docker compose COMPOSE_EXEC = $(COMPOSE) exec COMPOSE_EXEC_APP = $(COMPOSE_EXEC) app-dev -COMPOSE_RUN = $(COMPOSE) run --rm +COMPOSE_RUN = $(COMPOSE) run --rm --no-deps COMPOSE_RUN_APP = $(COMPOSE_RUN) app-dev COMPOSE_RUN_CROWDIN = $(COMPOSE_RUN) crowdin crowdin -WAIT_DB = @$(COMPOSE_RUN) dockerize -wait tcp://$(DB_HOST):$(DB_PORT) -timeout 60s -WAIT_KC_DB = $(COMPOSE_RUN) dockerize -wait tcp://kc_postgresql:5432 -timeout 60s # -- Backend MANAGE = $(COMPOSE_RUN_APP) python manage.py @@ -78,19 +76,33 @@ create-env-files: \ env.d/development/kc_postgresql .PHONY: create-env-files +add-dev-rsa-private-key-to-env: ## Add a generated RSA private key to the env file + @echo "Generating RSA private key PEM for development..." + @mkdir -p env.d/development/rsa + @openssl genrsa -out env.d/development/rsa/private.pem 2048 + @echo -n "\nOAUTH2_PROVIDER_OIDC_RSA_PRIVATE_KEY=\"" >> env.d/development/common + @openssl rsa -in env.d/development/rsa/private.pem -outform PEM >> env.d/development/common + @echo "\"" >> env.d/development/common + @rm -rf env.d/development/rsa +.PHONY: add-dev-rsa-private-key-to-env + +update-keycloak-realm-app: ## Create the Keycloak realm for the project + @echo "$(BOLD)Creating Keycloak realm for 'app'$(RESET)" + @sed -i 's|http://app-dev:8000|http://app:8000|g' ./docker/auth/realm.json +.PHONY: update-keycloak-realm-app + bootstrap: ## Prepare Docker images for the project and install frontend dependencies bootstrap: \ data/media \ data/static \ create-env-files \ build \ - run \ + run-dev \ migrate \ back-i18n-compile \ mails-install \ mails-build \ - dimail-setup-db \ - install-front-desk + dimail-setup-db .PHONY: bootstrap # -- Docker/compose @@ -106,19 +118,14 @@ logs: ## display app-dev logs (follow mode) @$(COMPOSE) logs -f app-dev .PHONY: logs -run: ## start the wsgi (production) and development server - @$(COMPOSE) up --force-recreate -d nginx - @$(COMPOSE) up --force-recreate -d app-dev - @$(COMPOSE) up --force-recreate -d celery-dev - @$(COMPOSE) up --force-recreate -d celery-beat-dev - @$(COMPOSE) up --force-recreate -d flower-dev - @$(COMPOSE) up --force-recreate -d keycloak - @$(COMPOSE) up -d dimail - @echo "Wait for postgresql to be up..." - @$(WAIT_KC_DB) - @$(WAIT_DB) +run: ## start the wsgi (production) and servers with production Docker images + @$(COMPOSE) up --force-recreate --detach app frontend celery celery-beat nginx maildev .PHONY: run +run-dev: ## start the servers in development mode (watch) Docker images + @$(COMPOSE) up --force-recreate --detach app-dev frontend-dev celery-dev celery-beat-dev nginx maildev +.PHONY: run-dev + status: ## an alias for "docker compose ps" @$(COMPOSE) ps .PHONY: status @@ -187,22 +194,16 @@ test-coverage: ## compute, display and save test coverage makemigrations: ## run django makemigrations for the people project. @echo "$(BOLD)Running makemigrations$(RESET)" - @$(COMPOSE) up -d postgresql - @$(WAIT_DB) @$(MANAGE) makemigrations $(ARGS) .PHONY: makemigrations migrate: ## run django migrations for the people project. @echo "$(BOLD)Running migrations$(RESET)" - @$(COMPOSE) up -d postgresql - @$(WAIT_DB) @$(MANAGE) migrate $(ARGS) .PHONY: migrate showmigrations: ## run django showmigrations for the people project. @echo "$(BOLD)Running showmigrations$(RESET)" - @$(COMPOSE) up -d postgresql - @$(WAIT_DB) @$(MANAGE) showmigrations $(ARGS) .PHONY: showmigrations diff --git a/README.md b/README.md index 290e50a..6a71b1d 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The easiest way to start working on the project is to use GNU Make: $ make bootstrap ``` -This command builds the `app` container, installs dependencies, performs +This command builds the `app-dev` container, installs dependencies, performs database migrations and compile translations. It's a good idea to use this command each time you are pulling code from the project repository to avoid dependency-related or migration-related issues. @@ -46,6 +46,12 @@ Note that if you need to run them afterward, you can use the eponym Make rule: $ make run ``` +or if you want to run them in development mode (with live reloading): + +```bash +$ make run-dev +``` + You can check all available Make rules using: ```bash diff --git a/bin/_config.sh b/bin/_config.sh index 73ceb05..d03974d 100644 --- a/bin/_config.sh +++ b/bin/_config.sh @@ -64,6 +64,23 @@ function _dc_run() { _docker_compose run --rm $user_args "$@" } +# _dc_run_no_deps: wrap docker compose run command without dependencies +# +# usage: _dc_run_no_deps [options] [ARGS...] +# +# options: docker compose run command options +# ARGS : docker compose run command arguments +function _dc_run_no_deps() { + _set_user + + user_args="--user=$USER_ID" + if [ -z $USER_ID ]; then + user_args="" + fi + + _docker_compose run --no-deps --rm $user_args "$@" +} + # _dc_exec: wrap docker compose exec command # # usage: _dc_exec [options] [ARGS...] diff --git a/bin/pylint b/bin/pylint index 8053c7c..2c9adf8 100755 --- a/bin/pylint +++ b/bin/pylint @@ -35,4 +35,4 @@ fi # Fix docker vs local path when project sources are mounted as a volume read -ra paths <<< "$(echo "${paths[@]}" | sed "s|src/backend/||g")" -_dc_run app-dev pylint "${paths[@]}" "${args[@]}" +_dc_run_no_deps app-dev pylint "${paths[@]}" "${args[@]}" diff --git a/docker-compose.yml b/docker-compose.yml index 9e7bf9e..64a8ee6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,11 @@ services: - env.d/development/postgresql ports: - "15432:5432" + healthcheck: + test: [ "CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}" ] + interval: 1s + timeout: 2s + retries: 300 redis: image: redis:5 @@ -23,6 +28,7 @@ services: DOCKER_USER: ${DOCKER_USER:-1000} user: ${DOCKER_USER:-1000} image: people:backend-development + pull_policy: never environment: - PYLINTHOME=/app/.pylint.d - DJANGO_CONFIGURATION=Development @@ -37,11 +43,69 @@ services: - ./data/media:/data/media - ./data/static:/data/static depends_on: - - dimail - - postgresql - - maildev - - redis - + postgresql: + condition: service_healthy + restart: true + dimail: + condition: service_started + maildev: + condition: service_started + redis: + condition: service_started + + frontend-dev: + user: "${DOCKER_USER:-1000}" + build: + context: . + dockerfile: ./src/frontend/Dockerfile + target: frontend-dev + image: people:frontend-development + pull_policy: never + volumes: + - ./src/frontend:/home/frontend + - /home/frontend/node_modules + - /home/frontend/apps/desk/node_modules + ports: + - "3000:3000" + + app: + build: + context: . + target: backend-production + args: + DOCKER_USER: ${DOCKER_USER:-1000} + user: ${DOCKER_USER:-1000} + image: people:backend-production + environment: + - DJANGO_CONFIGURATION=Development + env_file: + - env.d/development/common + - env.d/development/france + - env.d/development/postgresql + ports: + - "8071:8000" + volumes: + - ./data/media:/data/media + depends_on: + postgresql: + condition: service_healthy + restart: true + redis: + condition: service_started + + frontend: + user: "${DOCKER_USER:-1000}" + build: + context: . + dockerfile: ./src/frontend/Dockerfile + target: frontend-production + args: + API_ORIGIN: "http://localhost:8071" + image: people:frontend-production + pull_policy: build + ports: + - "3000:3000" + celery-dev: user: ${DOCKER_USER:-1000} image: people:backend-development @@ -56,7 +120,11 @@ services: - ./data/media:/data/media - ./data/static:/data/static depends_on: - - app-dev + postgresql: + condition: service_healthy + restart: true + app-dev: + condition: service_started celery-beat-dev: user: ${DOCKER_USER:-1000} @@ -72,26 +140,9 @@ services: - ./data/media:/data/media - ./data/static:/data/static depends_on: - - app-dev - - app: - build: - context: . - target: backend-production - args: - DOCKER_USER: ${DOCKER_USER:-1000} - user: ${DOCKER_USER:-1000} - image: people:backend-production - environment: - - DJANGO_CONFIGURATION=Demo - env_file: - - env.d/development/common - - env.d/development/postgresql - volumes: - - ./data/media:/data/media - depends_on: - - postgresql - - redis + postgresql: + condition: service_healthy + restart: true celery: user: ${DOCKER_USER:-1000} @@ -103,7 +154,29 @@ services: - env.d/development/common - env.d/development/postgresql depends_on: - - app + postgresql: + condition: service_healthy + restart: true + app: + condition: service_started + + celery-beat: + user: ${DOCKER_USER:-1000} + image: people:backend-production + command: ["celery", "-A", "people.celery_app", "beat", "-l", "INFO"] + environment: + - DJANGO_CONFIGURATION=Demo + env_file: + - env.d/development/common + - env.d/development/postgresql + volumes: + - ./src/backend:/app + - ./data/media:/data/media + - ./data/static:/data/static + depends_on: + postgresql: + condition: service_healthy + restart: true nginx: image: nginx:1.25 @@ -112,12 +185,9 @@ services: volumes: - ./docker/files/etc/nginx/conf.d:/etc/nginx/conf.d:ro depends_on: - - app - - keycloak - - dockerize: - image: jwilder/dockerize - platform: linux/x86_64 + keycloak: + condition: service_healthy + restart: true crowdin: image: crowdin/cli:4.6.1 @@ -137,11 +207,16 @@ services: - ".:/app" kc_postgresql: - image: postgres:14.3 - ports: - - "5433:5432" - env_file: - - env.d/development/kc_postgresql + image: postgres:14.3 + ports: + - "5433:5432" + env_file: + - env.d/development/kc_postgresql + healthcheck: + test: [ "CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}" ] + interval: 1s + timeout: 2s + retries: 300 keycloak: image: quay.io/keycloak/keycloak:20.0.1 @@ -158,6 +233,8 @@ services: - --hostname-admin-url=http://localhost:8083/ - --hostname-strict=false - --hostname-strict-https=false + - --health-enabled=true + - --metrics-enabled=true environment: KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin @@ -168,10 +245,17 @@ services: KC_DB_USERNAME: people KC_DB_SCHEMA: public PROXY_ADDRESS_FORWARDING: 'true' + healthcheck: + test: [ "CMD", "curl", "--head", "-fsS", "http://localhost:8080/health/ready" ] + interval: 1s + timeout: 2s + retries: 300 ports: - "8080:8080" depends_on: - - kc_postgresql + kc_postgresql: + condition: service_healthy + restart: true dimail: entrypoint: /opt/dimail-api/start-dev.sh diff --git a/docker/auth/realm.json b/docker/auth/realm.json index 4af531b..4edb493 100644 --- a/docker/auth/realm.json +++ b/docker/auth/realm.json @@ -26,7 +26,7 @@ "oauth2DeviceCodeLifespan": 600, "oauth2DevicePollingInterval": 5, "enabled": true, - "sslRequired": "external", + "sslRequired": "none", "registrationAllowed": true, "registrationEmailAsUsername": false, "rememberMe": true, diff --git a/env.d/development/common.dist b/env.d/development/common.dist index dbaeb91..6e013c8 100644 --- a/env.d/development/common.dist +++ b/env.d/development/common.dist @@ -3,6 +3,7 @@ DJANGO_ALLOWED_HOSTS=* DJANGO_SECRET_KEY=ThisIsAnExampleKeyForDevPurposeOnly DJANGO_SETTINGS_MODULE=people.settings DJANGO_SUPERUSER_PASSWORD=admin +DJANGO_CORS_ALLOWED_ORIGINS=http://localhost:3000 # Python PYTHONPATH=/app diff --git a/env.d/development/common.e2e.dist b/env.d/development/common.e2e.dist index 5cb7075..5aa81a0 100644 --- a/env.d/development/common.e2e.dist +++ b/env.d/development/common.e2e.dist @@ -2,7 +2,9 @@ SUSTAINED_THROTTLE_RATES="200/hour" BURST_THROTTLE_RATES="200/minute" -OAUTH2_PROVIDER_OIDC_ENABLED = True -OAUTH2_PROVIDER_VALIDATOR_CLASS: "mailbox_oauth2.validators.ProConnectValidator" +OIDC_ORGANIZATION_REGISTRATION_ID_FIELD="siret" + +OAUTH2_PROVIDER_OIDC_ENABLED=True +OAUTH2_PROVIDER_VALIDATOR_CLASS="mailbox_oauth2.validators.ProConnectValidator" INSTALLED_PLUGINS=plugins.la_suite diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.mo b/src/backend/locale/en_US/LC_MESSAGES/django.mo index f874a49c529e1bd112507d45765aa3e93b795f33..19c49051c3d829d18885421812c43ffc1e9f229d 100644 GIT binary patch delta 21 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 delta 3958 zcmYk-4OEr&0mt$GMP3v{KuF#apAdOd5=|~1b~pl;rbZtyl-J0uWJ!!MA7WcPACx~O8k36`uoSQ394wh)%mQq|GJFn` z@NMK4a~^B(GS0+2E@t3rw9tu5j0u=sROWNympBox;bQz6D{xVAL=UQg-$gE&Qy7Q8 zv;6>DIKGUrxHKiwz*^LOgII{W@e%wHZsq+ZZ))TV&tfVU4x(Og%szh-^?+0M`O`R^ z;}1|h{Sr$tmM^zsCHCVX)b+G9V>V$fCg5(IhWjy{_nYrgnT)?cz34Zn2K*i;;YX+* zeTuxxT*EAkyTzDn%tdC++=2RjE$T(}s0Q4Rnu1PTfy1bgK7)ZKDu1MsiL+^zrhO%9 zs2WiZ96(LUPTQYj9moI1bX>tlG`6A|z6qHH(~IiZPEv437D6I^(aFk8z?5 z-@#G5jwkRKE2avav`*JYZ~>mhVvJ=Nv^eLX8q|ypco=)|Q&hv+7$z+~CsyKiREOWl zX8gIwyvqp<@!wEA7)O2KKgi&ibXJ~PUV^L}Q;oOdI@E61iE7v=w%}j!ZmedZ)Z$)T zjOTDICgw1@xG_Lw7L`%dRGddWuy97?LFx-zNzk(Xk&rpl`zZiv+m^a=VFq5fhh%HnD7NCZD8S2IBP(9yhU-zOOJcxSX z4%8wWLcKVM{F$SC=zDJ?%gJ0oHTWuOaej#@+W(W-8+uVT>OuK90n1SjtVCU3X`gRI zHJ};QfDL%WVa$W5q1JqsLjlgiR;gjJW1}|Y8evBUc1Ou9rwxY-??L-|P!#nT< z4&gQ2j)TRK>;FQn5iJN!T^3HnQrktSU9ub*6w{0v$$r!}9zqxXY&PTHPh|#6UJrT^ zx8SIKoXIfg0dp}An^3!<9}{ptsv(C^Q+EPm@f2!Xzk{qd^D$~fV%f8aScLIdc{}5; z2QBA>7F`1}+Qy06$4{cZ@DaA*Rb;eH!`#UEZAfR#6R4p*j!pPe)RatStF&S>T6i3F z-#OIcybz$Go{yu3ESeRghD<~4?;_+k!`z7);tpJn-FO<0<2f8;T$kV)7R*W$n~Z_*v8OW2l~fAJv2Nn2Hxs4gDN7 z6^Z=5$^z6B)Zj8~!$mlPwfH`6(f-d~7-{hkwsYZKydO)s`(AtsAHcsLZ8giPjQIxo zQQPSv_Fx7F*|-PO@m18EpF}nM60X2_hDjsUz(>CJ|I<`-;~PjP%pXwOW%AO<98X2{ z*g`$H5H%ImsKr%>`bpl3+C9U_GBGcpcF`E>{wNMKwF#)_=U}4te;yV6pp@Y>tVAu! z28_ZMycIXvZbz;1gQ&%N7~}C*sE)jcTD1Sf3Fu(_^}-m`E=fRL&%l6&G@pvT*oqnW zIEG(<>hWRw{F|sAp2cLmfO^3d`}#Oe;W(~3a()`BBUz{y=OaIq;RXCKc4__}qoUG4 zHV`&Kc)m1bD*B=tH$wK4?~oulK(vtdkWJ*~lEZh^BB3(e~XBq@a7CNoS95pB_Lk-Nz==8(o0H+iniiG^}n9V z%jBCRlPn}E`$!tOjj)r#WtpvbgiI$ph_4E>Y3{pYR;4 z!+4_YrP4~;$qZ6J7L(^l8BsYJ;s2la*|mFZ{n$^olBdZ=qEe)f5^Gjrho{@=?kNgJ zCtVK4B~K64CJ#75<0&&8p}N$29Fv?qeyhXjclkn}r1eDwUr%2e%*;3${37G)^rnHH z_J_Ql9=FeBb$UJBR)?EMbp@|x?sE);c4a*}A;)sIw|n|{gxkkepL27UtHZ4KxxALU zr_&SsM{;(sCM7MHmXjGu%~=y2{3-V5*1%wQdVKjn^#p@5%TA+jSBv~ zFekL6C?P5onBDIPEhstYNU2rJEO&RW$Lpu1w6bFEXHlt*E^oKn=W}~{e5Q^U`K-p^ z(X#SjcKLW{;ruFxbyL&5*4PoN*Xi}Uy;g_I?nCI|ia9Y!^)9R3)7|Umtjp>u2wq*7 zTI_SS``z2deqieLVxH~ocKY?A$US|2cNhKiyZXGr9mVrQBUSrif>)}Gf@iBg44tdl O<%nu(3~gEQisOHQC)d{i diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index a53eaa1..42b4bb9 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "django-configurations==2.5.1", "django-cors-headers==4.7.0", "django-countries==7.6.1", + "django-extensions==4.1", "django-lasuite==0.0.9", "django-oauth-toolkit==3.0.1", "django-parler==2.3", @@ -70,7 +71,6 @@ dependencies = [ [project.optional-dependencies] dev = [ - "django-extensions==4.1", "drf-spectacular-sidecar==2025.6.1", "ipdb==0.13.13", "ipython==9.3.0", diff --git a/src/frontend/Dockerfile b/src/frontend/Dockerfile new file mode 100644 index 0000000..f596706 --- /dev/null +++ b/src/frontend/Dockerfile @@ -0,0 +1,63 @@ +FROM node:22-alpine AS frontend-deps + +# Upgrade system packages to install security updates +RUN apk update && \ + apk upgrade && \ + rm -rf /var/cache/apk/* + +WORKDIR /home/frontend/ + +COPY ./src/frontend/package.json ./package.json +COPY ./src/frontend/yarn.lock ./yarn.lock +COPY ./src/frontend/apps/desk/package.json ./apps/desk/package.json +COPY ./src/frontend/packages/i18n/package.json ./packages/i18n/package.json +COPY ./src/frontend/packages/eslint-config-people/package.json ./packages/eslint-config-people/package.json + +RUN yarn install --frozen-lockfile + +COPY .dockerignore ./.dockerignore +COPY ./src/frontend/.prettierrc.js ./.prettierrc.js +COPY ./src/frontend/packages/eslint-config-people ./packages/eslint-config-people +COPY ./src/frontend/apps/desk ./apps/desk + +### ---- Front-end builder image ---- +FROM frontend-deps AS frontend-base + +WORKDIR /home/frontend/apps/desk + +### ---- Front-end builder dev image ---- +FROM frontend-deps AS frontend-dev + +WORKDIR /home/frontend/apps/desk + +EXPOSE 3000 + +CMD [ "yarn", "dev"] + + +FROM frontend-base AS frontend-builder + +WORKDIR /home/frontend/apps/desk + +ARG API_ORIGIN +ENV NEXT_PUBLIC_API_ORIGIN=${API_ORIGIN} + +RUN yarn build + +# ---- Front-end image ---- +FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production + +# Un-privileged user running the application +ARG DOCKER_USER +USER ${DOCKER_USER} + +COPY --from=frontend-builder \ + /home/frontend/apps/desk/out \ + /usr/share/nginx/html + +COPY ./src/frontend/apps/desk/conf/default.conf /etc/nginx/conf.d +COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint + +ENTRYPOINT [ "/usr/local/bin/entrypoint" ] + +CMD ["nginx", "-g", "daemon off;"] diff --git a/src/frontend/apps/desk/conf/default.conf b/src/frontend/apps/desk/conf/default.conf index f620d50..4e6764a 100644 --- a/src/frontend/apps/desk/conf/default.conf +++ b/src/frontend/apps/desk/conf/default.conf @@ -1,4 +1,5 @@ server { + listen 3000; listen 8080; server_name localhost; server_tokens off; diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts index 8ecfede..7d7408f 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domain.spec.ts @@ -97,6 +97,7 @@ test.describe('Mail domain', () => { }); await page.goto('/mail-domains/unknown-domain.fr'); + await page.waitForURL('/404/'); await expect( page.getByText( 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', diff --git a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts index 97cb12a..f505ede 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/mail-domains-add.spec.ts @@ -101,6 +101,8 @@ test.describe('Add Mail Domains', () => { test('checks 404 on mail-domains/[slug] page', async ({ page }) => { await page.goto('/mail-domains/unknown-domain'); + await page.waitForURL('/404/'); + await expect( page.getByText( 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', diff --git a/src/frontend/apps/e2e/__tests__/app-desk/teams-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-desk/teams-create.spec.ts index bc799d5..80fd73d 100644 --- a/src/frontend/apps/e2e/__tests__/app-desk/teams-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-desk/teams-create.spec.ts @@ -83,6 +83,7 @@ test.describe('Teams Create', () => { test('checks 404 on teams/[id] page', async ({ page }) => { await page.goto('/teams/some-unknown-team'); + await page.waitForURL('/404/'); await expect( page.getByText( 'It seems that the page you are looking for does not exist or cannot be displayed correctly.', diff --git a/src/frontend/apps/e2e/playwright.config.ts b/src/frontend/apps/e2e/playwright.config.ts index 82732f8..6d1e7c0 100644 --- a/src/frontend/apps/e2e/playwright.config.ts +++ b/src/frontend/apps/e2e/playwright.config.ts @@ -32,12 +32,10 @@ export default defineConfig({ }, webServer: { - command: `cd ../.. && yarn app:${ - process.env.CI ? 'start -p ' : 'dev --port ' - } ${PORT}`, + command: !process.env.CI ? `cd ../.. && yarn app:dev --port ${PORT}` : '', url: baseURL, timeout: 120 * 1000, - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, }, /* Configure projects for major browsers */