From f1a5a4716604e3129e971837a67ece625b4323e6 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:19:40 +0100 Subject: [PATCH 01/45] Split backend container into prod and dev --- backend/Dockerfile | 25 +++++++++++++++++++------ docker-compose.yml | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 85a54bc0..60aaba1c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,11 +1,24 @@ -FROM python:3.11 +FROM python:3.12-slim-trixie + +# Set environment variables. +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Install dependencies. +RUN apt update +RUN pip install gunicorn -WORKDIR /usr/src/app/backend COPY requirements.txt . -RUN pip install -U pip pip-tools && pip-sync +RUN pip install -r requirements.txt --no-cache-dir +# Set working directory. +WORKDIR /usr/src/app/backend + +# Copy project files. COPY . . -CMD python manage.py check && \ - python manage.py migrate && \ - python manage.py runserver --settings glue --pythonpath .. 0.0.0.0:8000 +# Create a directory for Gunicorn logs (production). +RUN mkdir -p /usr/src/app/logs + +# Expose port. +EXPOSE 8000 diff --git a/docker-compose.yml b/docker-compose.yml index c81f8e5c..6ebec3ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,13 +14,17 @@ services: - ./backend/create_db.sql:/docker-entrypoint-initdb.d/langpro.sql ports: - 127.0.0.1:5433:5432 - backend: - build: - context: ./backend + + backend-prod: &backend-prod + container_name: la-backend + profiles: ["prod"] + build: ./backend + restart: unless-stopped environment: - PGHOST: postgres - LANGPRO_CONTAINER: 'http://langpro:80' - LANGPRO_FRONTEND: 'http://frontend:4200' + - PGHOST=postgres + - LANGPRO_CONTAINER=http://langpro:80 + - LANGPRO_FRONTEND=http://localhost:4200 + - DJANGO_DEBUG=0 depends_on: postgres: condition: service_healthy @@ -28,6 +32,27 @@ services: test: 'curl -f localhost:8000/admin/' volumes: - ./:/usr/src/app + - ./logs/django:/usr/src/app/logs + expose: + - 8000 + command: > + gunicorn langpro_annotator.wsgi:application + -w 4 + -b 0.0.0.0:8000 + --timeout 600 + --access-logfile /usr/src/app/logs/access_log + --error-logfile /usr/src/app/logs/error_log + --capture-output + + backend-dev: + <<: *backend-prod + restart: no + profiles: ["dev"] + environment: + - PGHOST=postgres + - LANGPRO_CONTAINER=http://langpro:80 + - LANGPRO_FRONTEND=http://localhost:4200 + - DJANGO_DEBUG=1 ports: - 127.0.0.1:8000:8000 frontend: From 57fc4bc6249836b6efec3b7bd2949b3372c8b185 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:23:04 +0100 Subject: [PATCH 02/45] Add container names to remaining containers Other containers can use this to refer to whatever container variant (dev or prod) turns out to be running. --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 6ebec3ec..40f16831 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ include: services: postgres: + container_name: la-postgres image: postgres:15 environment: - POSTGRES_PASSWORD=postgres @@ -56,6 +57,7 @@ services: ports: - 127.0.0.1:8000:8000 frontend: + container_name: la-frontend build: context: ./frontend healthcheck: From 7c748d162f3d7280d2774dc63e51f11f715d099c Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:24:08 +0100 Subject: [PATCH 03/45] Add image names to backend services Not necessary, but less unwieldy than 'langpro-annotator-langpro-backend-prod'. --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 40f16831..b38ddd05 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: backend-prod: &backend-prod container_name: la-backend + image: la-backend-prod profiles: ["prod"] build: ./backend restart: unless-stopped @@ -47,6 +48,7 @@ services: backend-dev: <<: *backend-prod + image: la-backend-dev restart: no profiles: ["dev"] environment: From 60be35d77d947a9f76a13f70ec1a08821e6ad073 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:24:34 +0100 Subject: [PATCH 04/45] Add logs to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 21cabf95..497c49d0 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ frontend/src/environments/version.ts /backend/fracas.xml /backend/snli_1.0_dev.txt /backend/snli_1.0_test.txt + +# Docker container logs +logs/ From 952c4399629062acaa51389bb27337e1874d25bc Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:25:06 +0100 Subject: [PATCH 05/45] Update proxy.conf.docker.json with new values This will be replaced in the near future. --- proxy.conf.docker.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proxy.conf.docker.json b/proxy.conf.docker.json index 4f67c4aa..99de5119 100644 --- a/proxy.conf.docker.json +++ b/proxy.conf.docker.json @@ -1,18 +1,18 @@ { "/api": { - "target": "http://backend:8000", + "target": "http://la-backend:8000", "secure": false }, "/users": { - "target": "http://backend:8000", + "target": "http://la-backend:8000", "secure": false }, "/admin": { - "target": "http://backend:8000", + "target": "http://la-backend:8000", "secure": false }, "/static": { - "target": "http://backend:8000", + "target": "http://la-backend:8000", "secure": false } } From e511b3b6d47fac451925b98004464c74ad28c286 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:28:12 +0100 Subject: [PATCH 06/45] Update psycopg2 to use the binary package --- backend/requirements.in | 2 +- backend/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/requirements.in b/backend/requirements.in index 3637029a..c3fce476 100644 --- a/backend/requirements.in +++ b/backend/requirements.in @@ -2,7 +2,7 @@ Django>=4.0.1,<5 djangorestframework django-livereload-server django-revproxy>=0.10.0 -psycopg2 +psycopg2-binary pytest pytest-django pytest-xdist diff --git a/backend/requirements.txt b/backend/requirements.txt index b5b9c018..664e980e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -48,7 +48,7 @@ packaging==24.2 # via pytest pluggy==1.5.0 # via pytest -psycopg2==2.9.10 +psycopg2-binary==2.9.10 # via -r requirements.in pytest==8.3.5 # via From 45c12922c166e126b95040ee7e2f6041c6b559be Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 16:58:52 +0100 Subject: [PATCH 07/45] Add Nginx configuration --- docker-compose.yml | 9 +++++++++ nginx.conf | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index b38ddd05..4b5998a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,15 @@ include: - ./langpro-container/compose.yaml services: + nginx: + container_name: la-nginx + restart: unless-stopped + image: nginx:latest + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + ports: + - 127.0.0.1:5000:80 + postgres: container_name: la-postgres image: postgres:15 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..d8b327ca --- /dev/null +++ b/nginx.conf @@ -0,0 +1,45 @@ +events { worker_connections 1024; } + +http { + include /etc/nginx/mime.types; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Port $server_port; + + server { + listen 80; + + location /api { + proxy_pass http://la-backend:8000/api; + proxy_read_timeout 30s; + proxy_connect_timeout 30s; + } + + location /users { + proxy_pass http://la-backend:8000/users; + proxy_read_timeout 30s; + proxy_connect_timeout 30s; + } + + location /admin { + proxy_pass http://la-backend:8000/admin; + proxy_read_timeout 30s; + proxy_connect_timeout 30s; + } + + location /static { + proxy_pass http://la-backend:8000/static; + proxy_read_timeout 30s; + proxy_connect_timeout 30s; + } + + location / { + proxy_pass http://la-frontend:4200; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + } +} From 45c2f61b50565eab88c3a1163883c57e7cd500d1 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 26 Mar 2026 17:00:33 +0100 Subject: [PATCH 08/45] Install dependencies during build time --- docker-compose.yml | 12 +++++++++++- frontend/Dockerfile | 6 ------ frontend/Dockerfile.dev | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) delete mode 100644 frontend/Dockerfile create mode 100644 frontend/Dockerfile.dev diff --git a/docker-compose.yml b/docker-compose.yml index 4b5998a3..6a5b67fa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,15 +67,25 @@ services: - DJANGO_DEBUG=1 ports: - 127.0.0.1:8000:8000 - frontend: + + + frontend-dev: + image: la-frontend-dev container_name: la-frontend + restart: no + profiles: ["dev"] build: context: ./frontend + dockerfile: Dockerfile.dev healthcheck: test: 'curl -f localhost:4200' + expose: + - 4200 + - 9876 volumes: - ./:/usr/src/app - frontend-node-modules:/usr/src/app/frontend/node_modules + command: ng serve --host 0.0.0.0 --disable-host-check ports: - 127.0.0.1:4200:4200 - 127.0.0.1:9876:9876 diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 950c2dce..00000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:20 - -WORKDIR /usr/src/app/frontend -COPY . . - -CMD yarn && yarn ng serve --host 0.0.0.0 --disable-host-check --proxy-config ../proxy.conf.docker.json diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev new file mode 100644 index 00000000..6d23acb4 --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,14 @@ +FROM node:24.14-slim + +# Set the working directory. +WORKDIR /usr/src/app/frontend + +# Copy package.json and yarn.lock. +COPY package.json yarn.lock ./ + +# Install dependencies. +RUN yarn global add @angular/cli@19 +RUN yarn install --frozen-lockfile + +# Expose the port for the development server. +EXPOSE 4200 From 70073656ea949bb4f676c363d5adfeb8623d7ef3 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 10:32:52 +0100 Subject: [PATCH 09/45] Only copy frontend folder to frontend container --- docker-compose.yml | 6 +++--- frontend/Dockerfile.dev | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 6a5b67fa..b4b419c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,9 +83,9 @@ services: - 4200 - 9876 volumes: - - ./:/usr/src/app - - frontend-node-modules:/usr/src/app/frontend/node_modules - command: ng serve --host 0.0.0.0 --disable-host-check + - ./frontend:/usr/src/app + - frontend-node-modules:/usr/src/app/node_modules + command: ng serve --host 0.0.0.0 --disable-host-check --poll 200 ports: - 127.0.0.1:4200:4200 - 127.0.0.1:9876:9876 diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index 6d23acb4..f2e369df 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -1,7 +1,7 @@ FROM node:24.14-slim # Set the working directory. -WORKDIR /usr/src/app/frontend +WORKDIR /usr/src/app # Copy package.json and yarn.lock. COPY package.json yarn.lock ./ From 00a810dcf001f46d1aaeb0d9ebd6b8789ecdc45e Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 10:34:06 +0100 Subject: [PATCH 10/45] Update restart policy to unless-stopped on postgres container --- docker-compose.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b4b419c7..1554afcc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,11 +14,13 @@ services: postgres: container_name: la-postgres image: postgres:15 + restart: unless-stopped environment: - - POSTGRES_PASSWORD=postgres + - POSTGRES_USER=$POSTGRES_USER + - POSTGRES_PASSWORD=$POSTGRES_PASSWORD + - POSTGRES_DB=$POSTGRES_DB healthcheck: test: 'psql -c "\l" postgres postgres' - restart: always volumes: - postgres-data:/var/lib/postgresql/data - ./backend/create_db.sql:/docker-entrypoint-initdb.d/langpro.sql From 360a57647b58f9a58f7702bf108f79c602bbfcee Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 10:35:18 +0100 Subject: [PATCH 11/45] Only mount backend folder to backend container --- backend/Dockerfile | 2 +- docker-compose.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 60aaba1c..892e1126 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,7 +12,7 @@ COPY requirements.txt . RUN pip install -r requirements.txt --no-cache-dir # Set working directory. -WORKDIR /usr/src/app/backend +WORKDIR /usr/src/app # Copy project files. COPY . . diff --git a/docker-compose.yml b/docker-compose.yml index 1554afcc..1c07678c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,11 +43,11 @@ services: condition: service_healthy healthcheck: test: 'curl -f localhost:8000/admin/' - volumes: - - ./:/usr/src/app - - ./logs/django:/usr/src/app/logs expose: - 8000 + volumes: + - ./backend:/usr/src/app + - ./logs/django:/usr/src/app/logs command: > gunicorn langpro_annotator.wsgi:application -w 4 From e5b0fc371ef2fd531470697e2d75a565ae905fdf Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:44:31 +0100 Subject: [PATCH 12/45] Move backend dev server start command to compose file --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1c07678c..89c087b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,7 +69,7 @@ services: - DJANGO_DEBUG=1 ports: - 127.0.0.1:8000:8000 - + command: sh -c "python manage.py check && python manage.py migrate && python manage.py runserver 0.0.0.0:8000" frontend-dev: image: la-frontend-dev From 8673a59551b577b47cf6155b31a01d334cab0da7 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:49:15 +0100 Subject: [PATCH 13/45] Add production frontend container --- docker-compose.yml | 13 ++++++++++++ frontend/.dockerignore | 3 +++ frontend/Dockerfile.prod | 43 ++++++++++++++++++++++++++++++++++++++++ frontend/nginx.conf | 18 +++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile.prod create mode 100644 frontend/nginx.conf diff --git a/docker-compose.yml b/docker-compose.yml index 89c087b0..26b61951 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -92,6 +92,19 @@ services: - 127.0.0.1:4200:4200 - 127.0.0.1:9876:9876 + frontend-prod: + image: la-frontend-prod + container_name: la-frontend + restart: unless-stopped + profiles: ["prod"] + build: + context: ./frontend + dockerfile: Dockerfile.prod + args: + APP_VERSION: ${APP_VERSION:-1.0.0} + healthcheck: + test: 'curl -f localhost:4200' + volumes: postgres-data: frontend-node-modules: diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..df5c8a36 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,3 @@ +node_modules +dist +.angular diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod new file mode 100644 index 00000000..368885b1 --- /dev/null +++ b/frontend/Dockerfile.prod @@ -0,0 +1,43 @@ +FROM node:24.14-slim AS builder + +# Accept version as build argument +ARG APP_VERSION=0.0.0 +ARG SOURCE_URL=unknown + +# Install git for pre-build.js +RUN apt-get update +RUN apt-get install -y git + +# Set the working directory. +WORKDIR /usr/src/app + +# Copy app dependencies +COPY package.json yarn.lock ./ + +# Install app dependencies +RUN yarn install + +# Copy app source +COPY . /usr/src/app + +# Set version as environment variable for build scripts +ENV APP_VERSION=${APP_VERSION} +ENV SOURCE_URL=${SOURCE_URL} + +# Pre-build +RUN npm run prebuild + +# Build app +RUN npm run build -- --output-path=./dist/out --configuration production + +# Setup NGINX to serve the app. +FROM nginx:alpine + +# Copy the built app to the NGINX server. +COPY --from=builder /usr/src/app/dist/out/browser /usr/share/nginx/html + +# Copy the NGINX configuration file. +COPY ./nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port for the NGINX server. +EXPOSE 4200 diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 00000000..f20696fe --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,18 @@ +server { + include /etc/nginx/mime.types; + + listen 4200; + + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + gzip_comp_level 6; + gzip_min_length 256; + gzip_vary on; + + # Serve the application + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html =404; + } +} From c7f38aad5bbcbe69ab91fd87b9616dcf96d960b5 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:50:14 +0100 Subject: [PATCH 14/45] Do not create localized build (for now) --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 2d74adc0..3b6f4b58 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,7 +2,7 @@ "name": "langpro-annotator", "scripts": { "start": "yarn serve", - "build": "ng build --base-href=/static/ --localize", + "build": "ng build", "watch": "ng build --watch", "test": "ng test --watch=true", "serve:ssr:langpro-annotator": "node dist/langpro-annotator/server/server.mjs", From 2e82d7b57f1ab1e74d48948025f997b4d23fabc0 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:51:19 +0100 Subject: [PATCH 15/45] Remove unused expose statements and env variables --- docker-compose.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 26b61951..8e42b6ed 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,15 +36,12 @@ services: environment: - PGHOST=postgres - LANGPRO_CONTAINER=http://langpro:80 - - LANGPRO_FRONTEND=http://localhost:4200 - DJANGO_DEBUG=0 depends_on: postgres: condition: service_healthy healthcheck: test: 'curl -f localhost:8000/admin/' - expose: - - 8000 volumes: - ./backend:/usr/src/app - ./logs/django:/usr/src/app/logs @@ -65,7 +62,6 @@ services: environment: - PGHOST=postgres - LANGPRO_CONTAINER=http://langpro:80 - - LANGPRO_FRONTEND=http://localhost:4200 - DJANGO_DEBUG=1 ports: - 127.0.0.1:8000:8000 From b015206873b14964edf741d10ba81ee8a269ed7a Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:51:36 +0100 Subject: [PATCH 16/45] Use .env variables in settings.py --- backend/langpro_annotator/settings.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/langpro_annotator/settings.py b/backend/langpro_annotator/settings.py index 4bad28f2..601a9225 100644 --- a/backend/langpro_annotator/settings.py +++ b/backend/langpro_annotator/settings.py @@ -19,13 +19,14 @@ # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'kxreeb3bds$oibo7ex#f3bi5r+d(1x5zljo-#ms=i2%ih-!pvn' +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "django-insecure-1234567890") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ['localhost', '127.0.0.1'] +DEBUG = int(os.getenv("DJANGO_DEBUG", 0)) == 1 +ALLOWED_HOSTS = ["la-backend"] +if DEBUG: + ALLOWED_HOSTS.append("localhost") # Application definition From 253986ba994812991aa1b6f9e0d86b80e16535b7 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 13:52:06 +0100 Subject: [PATCH 17/45] Use appVersion in build-pre.js. Fail gracefully if git repo is not initialized. --- frontend/build/build-pre.js | 166 +++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 79 deletions(-) diff --git a/frontend/build/build-pre.js b/frontend/build/build-pre.js index b7aca361..21090032 100644 --- a/frontend/build/build-pre.js +++ b/frontend/build/build-pre.js @@ -1,79 +1,87 @@ -const path = require('path'); -const colors = require('colors/safe'); -const fs = require('fs'); -const appVersion = require('../../package.json').version; -const { exec } = require('child_process'); - -console.log(colors.cyan('\nRunning pre-build tasks')); - -async function getHash() { - return new Promise((resolve, reject) => { - exec('git rev-parse HEAD', (error, stdout, stderr) => { - if (error) { - reject(`error: ${error.message}`); - return; - } - if (stderr) { - reject(`stderr: ${stderr}`); - return; - } - - resolve(stdout.trim()); - }); - }); -} - -async function getRemoteUrl() { - return new Promise((resolve, reject) => { - exec('git config --get remote.origin.url', (error, stdout, stderr) => { - if (error) { - reject(`error: ${error.message}`); - return; - } - if (stderr) { - reject(`stderr: ${stderr}`); - return; - } - - // format is either: - // git@github.com:CentreForDigitalHumanities/langpro-annotator.git - // or https://github.com/CentreForDigitalHumanities/langpro-annotator.git - // or https://USERNAME:SECRET@github.com/CentreForDigitalHumanities/langpro-annotator.git/ - - // remove https:// - let sourceUrl = stdout.replace(/^https?:\/\//, '').trim(); - // remove git@ or USERNAME:SECRET@ - sourceUrl = sourceUrl.replace(/^[^@]+@/, '').trim(); - // replace : with / - sourceUrl = sourceUrl.replace(':', '/'); - // remove .git/ - sourceUrl = sourceUrl.replace(/\.git\/?\n?$/, ''); - resolve('https://' + sourceUrl); - }); - }); -} - -Promise.all([getHash(), getRemoteUrl()]).then(([hash, remoteUrl]) => { - writeVersion(hash, remoteUrl); -}).catch((error) => { - console.log(`${colors.red('Could not update version: ')} ${error}`); -}); - -function writeVersion(hash, remoteUrl) { - const versionFilePath = path.join(__dirname + '/../src/environments/version.ts'); - const src = `export const version = '${appVersion}'; -export const buildTime = '${new Date()}'; -export const sourceUrl = '${remoteUrl}/tree/${hash}'; -`; - - // ensure version module pulls value from package.json - fs.writeFile(versionFilePath, src, { flat: 'w' }, function (err) { - if (err) { - return console.log(colors.red(err)); - } - - console.log(colors.green(`Updating application version ${colors.yellow(appVersion)}`)); - console.log(`${colors.green('Writing version module to ')}${colors.yellow(versionFilePath)}\n`); - console.log(src); - }); -} +const path = require('path'); +const colors = require('colors/safe'); +const fs = require('fs'); +const appVersion = process.env.APP_VERSION || '0.0.0'; +const { exec } = require('child_process'); + +console.log(colors.cyan('\nRunning pre-build tasks')); + +async function getHash() { + return new Promise((resolve, reject) => { + exec('git rev-parse HEAD', (error, stdout, stderr) => { + if (error) { + reject(`error: ${error.message}`); + return; + } + if (stderr) { + reject(`stderr: ${stderr}`); + return; + } + + resolve(stdout.trim()); + }); + }); +} + +async function getRemoteUrl() { + return new Promise((resolve, reject) => { + exec('git config --get remote.origin.url', (error, stdout, stderr) => { + if (error) { + reject(`error: ${error.message}`); + return; + } + if (stderr) { + reject(`stderr: ${stderr}`); + return; + } + + // format is either: + // git@github.com:CentreForDigitalHumanities/langpro-annotator.git + // or https://github.com/CentreForDigitalHumanities/langpro-annotator.git + // or https://USERNAME:SECRET@github.com/CentreForDigitalHumanities/langpro-annotator.git/ + + // remove https:// + let sourceUrl = stdout.replace(/^https?:\/\//, '').trim(); + // remove git@ or USERNAME:SECRET@ + sourceUrl = sourceUrl.replace(/^[^@]+@/, '').trim(); + // replace : with / + sourceUrl = sourceUrl.replace(':', '/'); + // remove .git/ + sourceUrl = sourceUrl.replace(/\.git\/?\n?$/, ''); + resolve('https://' + sourceUrl); + }); + }); +} + +Promise.all([ + getHash().catch(() => 'unknown'), + getRemoteUrl().catch(() => 'unknown') +]).then(([hash, remoteUrl]) => { + if (hash === 'unknown' || remoteUrl === 'unknown') { + console.log(colors.yellow('Git repository not found, using fallback values')); + } + writeVersion(hash, remoteUrl); +}).catch((error) => { + console.log(`${colors.red('Could not update version: ')} ${error}`); + // Write version with fallback values anyway + writeVersion('unknown', 'unknown'); +}); + +function writeVersion(hash, remoteUrl) { + const versionFilePath = path.join(__dirname + '/../src/environments/version.ts'); + const src = `export const version = '${appVersion}'; +export const buildTime = '${new Date()}'; +export const sourceUrl = '${remoteUrl}/tree/${hash}'; +`; + + // ensure version module pulls value from package.json + fs.writeFile(versionFilePath, src, { flat: 'w' }, function (err) { + if (err) { + return console.log(colors.red(err)); + } + + console.log(colors.green(`Updating application version ${colors.yellow(appVersion)}`)); + console.log(`${colors.green('Writing version module to ')}${colors.yellow(versionFilePath)}\n`); + console.log(src); + }); +} From fabcd308e05cf603c0f3daad5e0f79ac5825e2e0 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 14:04:42 +0100 Subject: [PATCH 18/45] Pass git info as args to prebuild script --- docker-compose.yml | 3 ++- frontend/Dockerfile.prod | 18 +++++++----------- frontend/build/build-pre.js | 27 ++++++++++++++++++++++----- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8e42b6ed..9197bb55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -97,7 +97,8 @@ services: context: ./frontend dockerfile: Dockerfile.prod args: - APP_VERSION: ${APP_VERSION:-1.0.0} + APP_VERSION: ${APP_VERSION:-0.0.0} + SOURCE_URL: ${SOURCE_URL:-https://github.com/CentreForDigitalHumanities/langpro-annotator} healthcheck: test: 'curl -f localhost:4200' diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod index 368885b1..70bdedac 100644 --- a/frontend/Dockerfile.prod +++ b/frontend/Dockerfile.prod @@ -1,33 +1,29 @@ FROM node:24.14-slim AS builder -# Accept version as build argument +# Accept version and source URL as build arguments. ARG APP_VERSION=0.0.0 ARG SOURCE_URL=unknown -# Install git for pre-build.js -RUN apt-get update -RUN apt-get install -y git - # Set the working directory. WORKDIR /usr/src/app -# Copy app dependencies +# Copy app dependencies. COPY package.json yarn.lock ./ -# Install app dependencies +# Install app dependencies. RUN yarn install -# Copy app source +# Copy app source. COPY . /usr/src/app -# Set version as environment variable for build scripts +# Set environment variables for prebuild script. ENV APP_VERSION=${APP_VERSION} ENV SOURCE_URL=${SOURCE_URL} -# Pre-build +# Pre-build. RUN npm run prebuild -# Build app +# Build app. RUN npm run build -- --output-path=./dist/out --configuration production # Setup NGINX to serve the app. diff --git a/frontend/build/build-pre.js b/frontend/build/build-pre.js index 21090032..a864cc83 100644 --- a/frontend/build/build-pre.js +++ b/frontend/build/build-pre.js @@ -1,12 +1,20 @@ const path = require('path'); const colors = require('colors/safe'); const fs = require('fs'); -const appVersion = process.env.APP_VERSION || '0.0.0'; +const appVersion = process.env.APP_VERSION; +const gitCommit = process.env.GIT_COMMIT; +const sourceUrl = process.env.SOURCE_URL; const { exec } = require('child_process'); console.log(colors.cyan('\nRunning pre-build tasks')); async function getHash() { + // Use environment variable if provided (Docker build) + if (gitCommit) { + return Promise.resolve(gitCommit); + } + + // Otherwise use git (local development) return new Promise((resolve, reject) => { exec('git rev-parse HEAD', (error, stdout, stderr) => { if (error) { @@ -24,6 +32,12 @@ async function getHash() { } async function getRemoteUrl() { + // Use environment variable if provided (Docker build) + if (sourceUrl) { + return Promise.resolve(sourceUrl); + } + + // Otherwise use git (local development) return new Promise((resolve, reject) => { exec('git config --get remote.origin.url', (error, stdout, stderr) => { if (error) { @@ -59,19 +73,22 @@ Promise.all([ ]).then(([hash, remoteUrl]) => { if (hash === 'unknown' || remoteUrl === 'unknown') { console.log(colors.yellow('Git repository not found, using fallback values')); + writeVersion(sourceUrl || 'unknown'); + return; } - writeVersion(hash, remoteUrl); + const sourceUrlWithHash = `${remoteUrl}/tree/${hash}`; + writeVersion(sourceUrlWithHash); }).catch((error) => { console.log(`${colors.red('Could not update version: ')} ${error}`); // Write version with fallback values anyway - writeVersion('unknown', 'unknown'); + writeVersion(sourceUrl || 'unknown'); }); -function writeVersion(hash, remoteUrl) { +function writeVersion(sourceUrl) { const versionFilePath = path.join(__dirname + '/../src/environments/version.ts'); const src = `export const version = '${appVersion}'; export const buildTime = '${new Date()}'; -export const sourceUrl = '${remoteUrl}/tree/${hash}'; +export const sourceUrl = '${sourceUrl}'; `; // ensure version module pulls value from package.json From 9a42984164639e56c457c7a9985a8d58d1adbb40 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:20:00 +0100 Subject: [PATCH 19/45] Add more mime types based on official Docker Angular containerization guide --- frontend/nginx.conf | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index f20696fe..4fb0dabd 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,13 +1,25 @@ server { include /etc/nginx/mime.types; - listen 4200; + listen 8080; gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - gzip_comp_level 6; - gzip_min_length 256; gzip_vary on; + gzip_min_length 256; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/x-javascript + application/json + application/xml + application/xml+rss + font/ttf + font/otf + image/svg+xml; # Serve the application location / { From bc0ec89532744169394d5f0157cb9ee36b47cd86 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:20:31 +0100 Subject: [PATCH 20/45] Update CONTRIBUTING.md and provide example file for .env --- .env.example | 5 +++++ CONTRIBUTING.md | 22 +++++++++++++++++++--- docker-compose.yml | 10 ++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..44f4c683 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +POSTGRES_USER= +POSTGRES_PASSWORD= +POSTGRES_DB= +DJANGO_SECRET_KEY= +APP_VERSION= diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9a1cf98..cec19556 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,20 +55,36 @@ If you are reading this document, you'll likely be working with the integrated p ### Quickstart with Docker +The application can be run in two modes: development mode and production mode. The former is meant for live development, while the latter is meant for simulating deployment conditions as closely as possible. + +Begin by creating a `.env` file in the project root with the following contents: + +```env +POSTGRES_USER=my-user-name +POSTGRES_PASSWORD=my-password +POSTGRES_DB=langpro +DJANGO_SECRET_KEY=my-secret-key +APP_VERSION=1.0.0 +``` + +Then update the values after the `=` as needed. The `POSTGRES_*` variables are used to configure the PostgreSQL database. The `DJANGO_SECRET_KEY` variable is used to set the Django `SECRET_KEY` setting. The `APP_VERSION` variable is used to set the version of the application, which is displayed in the frontend. + +Finally, run the command below to start the application in development mode. + ```console -docker compose up -d +docker compose --profile dev up -d ``` This will run the frontend and backend applications and watch all source files for changes. To run the backend unittests: ```console -docker compose exec backend pytest +docker compose exec backend-dev pytest ``` To run the frontend unittests: ```console -docker compose exec frontend yarn ng test --no-browsers +docker compose exec frontend-dev yarn ng test --no-browsers ``` then open http://localhost:9876 in a browser of choice. diff --git a/docker-compose.yml b/docker-compose.yml index 9197bb55..0ef3dafe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,7 +41,7 @@ services: postgres: condition: service_healthy healthcheck: - test: 'curl -f localhost:8000/admin/' + test: "curl -f localhost:8000/admin/" volumes: - ./backend:/usr/src/app - ./logs/django:/usr/src/app/logs @@ -76,13 +76,14 @@ services: context: ./frontend dockerfile: Dockerfile.dev healthcheck: - test: 'curl -f localhost:4200' + test: "curl -f localhost:4200" expose: - 4200 - 9876 volumes: - ./frontend:/usr/src/app - frontend-node-modules:/usr/src/app/node_modules + - frontend-angular-cache:/usr/src/app/.angular command: ng serve --host 0.0.0.0 --disable-host-check --poll 200 ports: - 127.0.0.1:4200:4200 @@ -98,10 +99,11 @@ services: dockerfile: Dockerfile.prod args: APP_VERSION: ${APP_VERSION:-0.0.0} - SOURCE_URL: ${SOURCE_URL:-https://github.com/CentreForDigitalHumanities/langpro-annotator} + SOURCE_URL: https://github.com/CentreForDigitalHumanities/langpro-annotator healthcheck: - test: 'curl -f localhost:4200' + test: "curl -f localhost:8080" volumes: postgres-data: frontend-node-modules: + frontend-angular-cache: From 99d8546cac749f70b3ec4162afc8338101dd94db Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:20:51 +0100 Subject: [PATCH 21/45] Add more ignore files to .dockerignore --- frontend/.dockerignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/.dockerignore b/frontend/.dockerignore index df5c8a36..7bb34ad8 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,3 +1,5 @@ node_modules dist .angular +.cache +.tmp From e2361640f3a9bd1a9b42566bdeabff0173708182 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:21:08 +0100 Subject: [PATCH 22/45] Add localhost:5000 to CSRF trusted origins --- backend/langpro_annotator/settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/langpro_annotator/settings.py b/backend/langpro_annotator/settings.py index 601a9225..b4a9a7bd 100644 --- a/backend/langpro_annotator/settings.py +++ b/backend/langpro_annotator/settings.py @@ -28,6 +28,12 @@ if DEBUG: ALLOWED_HOSTS.append("localhost") +# CSRF trusted origins for cross-origin requests +CSRF_TRUSTED_ORIGINS = [ + "http://localhost:5000", + "http://127.0.0.1:5000", +] + # Application definition ROOT_URLCONF = 'langpro_annotator.urls' From 52031827e63635dcf10ab6faabff09550214ba8b Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:21:57 +0100 Subject: [PATCH 23/45] Expand Angular Dockerfile.prod based on Docker's Angular containerization guide --- frontend/Dockerfile.prod | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/frontend/Dockerfile.prod b/frontend/Dockerfile.prod index 70bdedac..dc50215a 100644 --- a/frontend/Dockerfile.prod +++ b/frontend/Dockerfile.prod @@ -1,39 +1,39 @@ -FROM node:24.14-slim AS builder - -# Accept version and source URL as build arguments. +ARG NODE_VERSION=24.12.0-alpine +ARG NGINX_VERSION=alpine3.22 ARG APP_VERSION=0.0.0 ARG SOURCE_URL=unknown -# Set the working directory. +FROM node:${NODE_VERSION} AS builder + +# Set the working directory inside the container. WORKDIR /usr/src/app # Copy app dependencies. COPY package.json yarn.lock ./ # Install app dependencies. -RUN yarn install +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \ + yarn install --frozen-lockfile # Copy app source. COPY . /usr/src/app -# Set environment variables for prebuild script. -ENV APP_VERSION=${APP_VERSION} -ENV SOURCE_URL=${SOURCE_URL} - # Pre-build. -RUN npm run prebuild +RUN yarn prebuild # Build app. -RUN npm run build -- --output-path=./dist/out --configuration production +RUN yarn build -- --output-path=./dist/out --configuration production -# Setup NGINX to serve the app. -FROM nginx:alpine - -# Copy the built app to the NGINX server. -COPY --from=builder /usr/src/app/dist/out/browser /usr/share/nginx/html +FROM nginxinc/nginx-unprivileged:${NGINX_VERSION} AS runner # Copy the NGINX configuration file. -COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy the built app to the NGINX server with proper ownership. +COPY --chown=nginx:nginx --from=builder /usr/src/app/dist/out/browser /usr/share/nginx/html + +# Use a built-in non-root user for security best practices. +USER nginx -# Expose port for the NGINX server. +# Expose port 4200 to allow HTTP traffic. EXPOSE 4200 From a2cabe44a3f3df902cc6a6e4d2b99c8193a0cc1a Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:22:11 +0100 Subject: [PATCH 24/45] Remove unnecessary proxy.conf.json --- frontend/angular.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/angular.json b/frontend/angular.json index 36ac1c99..ded1ecf7 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -92,10 +92,7 @@ "buildTarget": "langpro-annotator:build:nl" } }, - "defaultConfiguration": "development", - "options": { - "proxyConfig": "../proxy.conf.json" - } + "defaultConfiguration": "development" }, "extract-i18n": { "builder": "ng-extract-i18n-merge:ng-extract-i18n-merge", From fc5ccfecc6751c731ae3ba43dcc144d805935b91 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:36:17 +0100 Subject: [PATCH 25/45] Revert to port 4200 for frontend prod container --- docker-compose.yml | 2 +- frontend/nginx.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0ef3dafe..93da674d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,7 +101,7 @@ services: APP_VERSION: ${APP_VERSION:-0.0.0} SOURCE_URL: https://github.com/CentreForDigitalHumanities/langpro-annotator healthcheck: - test: "curl -f localhost:8080" + test: "curl -f localhost:4200" volumes: postgres-data: diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 4fb0dabd..c4f476b8 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -1,7 +1,7 @@ server { include /etc/nginx/mime.types; - listen 8080; + listen 4200; gzip on; gzip_vary on; From 35bd17c1a59484179eb2dea637f6c6e7de1d2b4c Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:53:51 +0100 Subject: [PATCH 26/45] Use correct line-endings in build-pre.js --- frontend/build/build-pre.js | 208 ++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/frontend/build/build-pre.js b/frontend/build/build-pre.js index a864cc83..33d52715 100644 --- a/frontend/build/build-pre.js +++ b/frontend/build/build-pre.js @@ -1,104 +1,104 @@ -const path = require('path'); -const colors = require('colors/safe'); -const fs = require('fs'); -const appVersion = process.env.APP_VERSION; -const gitCommit = process.env.GIT_COMMIT; -const sourceUrl = process.env.SOURCE_URL; -const { exec } = require('child_process'); - -console.log(colors.cyan('\nRunning pre-build tasks')); - -async function getHash() { - // Use environment variable if provided (Docker build) - if (gitCommit) { - return Promise.resolve(gitCommit); - } - - // Otherwise use git (local development) - return new Promise((resolve, reject) => { - exec('git rev-parse HEAD', (error, stdout, stderr) => { - if (error) { - reject(`error: ${error.message}`); - return; - } - if (stderr) { - reject(`stderr: ${stderr}`); - return; - } - - resolve(stdout.trim()); - }); - }); -} - -async function getRemoteUrl() { - // Use environment variable if provided (Docker build) - if (sourceUrl) { - return Promise.resolve(sourceUrl); - } - - // Otherwise use git (local development) - return new Promise((resolve, reject) => { - exec('git config --get remote.origin.url', (error, stdout, stderr) => { - if (error) { - reject(`error: ${error.message}`); - return; - } - if (stderr) { - reject(`stderr: ${stderr}`); - return; - } - - // format is either: - // git@github.com:CentreForDigitalHumanities/langpro-annotator.git - // or https://github.com/CentreForDigitalHumanities/langpro-annotator.git - // or https://USERNAME:SECRET@github.com/CentreForDigitalHumanities/langpro-annotator.git/ - - // remove https:// - let sourceUrl = stdout.replace(/^https?:\/\//, '').trim(); - // remove git@ or USERNAME:SECRET@ - sourceUrl = sourceUrl.replace(/^[^@]+@/, '').trim(); - // replace : with / - sourceUrl = sourceUrl.replace(':', '/'); - // remove .git/ - sourceUrl = sourceUrl.replace(/\.git\/?\n?$/, ''); - resolve('https://' + sourceUrl); - }); - }); -} - -Promise.all([ - getHash().catch(() => 'unknown'), - getRemoteUrl().catch(() => 'unknown') -]).then(([hash, remoteUrl]) => { - if (hash === 'unknown' || remoteUrl === 'unknown') { - console.log(colors.yellow('Git repository not found, using fallback values')); - writeVersion(sourceUrl || 'unknown'); - return; - } - const sourceUrlWithHash = `${remoteUrl}/tree/${hash}`; - writeVersion(sourceUrlWithHash); -}).catch((error) => { - console.log(`${colors.red('Could not update version: ')} ${error}`); - // Write version with fallback values anyway - writeVersion(sourceUrl || 'unknown'); -}); - -function writeVersion(sourceUrl) { - const versionFilePath = path.join(__dirname + '/../src/environments/version.ts'); - const src = `export const version = '${appVersion}'; -export const buildTime = '${new Date()}'; -export const sourceUrl = '${sourceUrl}'; -`; - - // ensure version module pulls value from package.json - fs.writeFile(versionFilePath, src, { flat: 'w' }, function (err) { - if (err) { - return console.log(colors.red(err)); - } - - console.log(colors.green(`Updating application version ${colors.yellow(appVersion)}`)); - console.log(`${colors.green('Writing version module to ')}${colors.yellow(versionFilePath)}\n`); - console.log(src); - }); -} +const path = require('path'); +const colors = require('colors/safe'); +const fs = require('fs'); +const appVersion = process.env.APP_VERSION; +const gitCommit = process.env.GIT_COMMIT; +const sourceUrl = process.env.SOURCE_URL; +const { exec } = require('child_process'); + +console.log(colors.cyan('\nRunning pre-build tasks')); + +async function getHash() { + // Use environment variable if provided (Docker build) + if (gitCommit) { + return Promise.resolve(gitCommit); + } + + // Otherwise use git (local development) + return new Promise((resolve, reject) => { + exec('git rev-parse HEAD', (error, stdout, stderr) => { + if (error) { + reject(`error: ${error.message}`); + return; + } + if (stderr) { + reject(`stderr: ${stderr}`); + return; + } + + resolve(stdout.trim()); + }); + }); +} + +async function getRemoteUrl() { + // Use environment variable if provided (Docker build) + if (sourceUrl) { + return Promise.resolve(sourceUrl); + } + + // Otherwise use git (local development) + return new Promise((resolve, reject) => { + exec('git config --get remote.origin.url', (error, stdout, stderr) => { + if (error) { + reject(`error: ${error.message}`); + return; + } + if (stderr) { + reject(`stderr: ${stderr}`); + return; + } + + // format is either: + // git@github.com:CentreForDigitalHumanities/langpro-annotator.git + // or https://github.com/CentreForDigitalHumanities/langpro-annotator.git + // or https://USERNAME:SECRET@github.com/CentreForDigitalHumanities/langpro-annotator.git/ + + // remove https:// + let sourceUrl = stdout.replace(/^https?:\/\//, '').trim(); + // remove git@ or USERNAME:SECRET@ + sourceUrl = sourceUrl.replace(/^[^@]+@/, '').trim(); + // replace : with / + sourceUrl = sourceUrl.replace(':', '/'); + // remove .git/ + sourceUrl = sourceUrl.replace(/\.git\/?\n?$/, ''); + resolve('https://' + sourceUrl); + }); + }); +} + +Promise.all([ + getHash().catch(() => 'unknown'), + getRemoteUrl().catch(() => 'unknown') +]).then(([hash, remoteUrl]) => { + if (hash === 'unknown' || remoteUrl === 'unknown') { + console.log(colors.yellow('Git repository not found, using fallback values')); + writeVersion(sourceUrl || 'unknown'); + return; + } + const sourceUrlWithHash = `${remoteUrl}/tree/${hash}`; + writeVersion(sourceUrlWithHash); +}).catch((error) => { + console.log(`${colors.red('Could not update version: ')} ${error}`); + // Write version with fallback values anyway + writeVersion(sourceUrl || 'unknown'); +}); + +function writeVersion(sourceUrl) { + const versionFilePath = path.join(__dirname + '/../src/environments/version.ts'); + const src = `export const version = '${appVersion}'; +export const buildTime = '${new Date()}'; +export const sourceUrl = '${sourceUrl}'; +`; + + // ensure version module pulls value from package.json + fs.writeFile(versionFilePath, src, { flat: 'w' }, function (err) { + if (err) { + return console.log(colors.red(err)); + } + + console.log(colors.green(`Updating application version ${colors.yellow(appVersion)}`)); + console.log(`${colors.green('Writing version module to ')}${colors.yellow(versionFilePath)}\n`); + console.log(src); + }); +} From 9831d5a55688a5c3bd1b8ea2e11a048e2f382861 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 15:54:15 +0100 Subject: [PATCH 27/45] Match server version of Python (3.11) in backend Dockerfile --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 892e1126..fe15497e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-slim-trixie +FROM python:3.11-slim-trixie # Set environment variables. ENV PYTHONDONTWRITEBYTECODE=1 From 5622b83147b15567d7c2b29f532a09b55453b6de Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 17:14:25 +0100 Subject: [PATCH 28/45] Remove DB creation script from postgres service. The container will automatically create a DB with the provided config (host, user, password). --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 93da674d..d86f9bfb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,6 @@ services: test: 'psql -c "\l" postgres postgres' volumes: - postgres-data:/var/lib/postgresql/data - - ./backend/create_db.sql:/docker-entrypoint-initdb.d/langpro.sql ports: - 127.0.0.1:5433:5432 From 3c8f01c1f8e545f17b81c9e896e71490024ac616 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 27 Mar 2026 17:15:06 +0100 Subject: [PATCH 29/45] Do not mount source code in backend container; use env vars for database access --- docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d86f9bfb..75b44fb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,10 @@ services: restart: unless-stopped environment: - PGHOST=postgres + - PGUSER=$POSTGRES_USER + - PGPASSWORD=$POSTGRES_PASSWORD + - PGDATABASE=$POSTGRES_DB + - DJANGO_SECRET_KEY=$DJANGO_SECRET_KEY - LANGPRO_CONTAINER=http://langpro:80 - DJANGO_DEBUG=0 depends_on: @@ -42,7 +46,6 @@ services: healthcheck: test: "curl -f localhost:8000/admin/" volumes: - - ./backend:/usr/src/app - ./logs/django:/usr/src/app/logs command: > gunicorn langpro_annotator.wsgi:application @@ -62,6 +65,8 @@ services: - PGHOST=postgres - LANGPRO_CONTAINER=http://langpro:80 - DJANGO_DEBUG=1 + volumes: + - ./backend:/usr/src/app ports: - 127.0.0.1:8000:8000 command: sh -c "python manage.py check && python manage.py migrate && python manage.py runserver 0.0.0.0:8000" From 8013bad407340d16de58502738ae6c52ce4bdfb2 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 10:34:40 +0200 Subject: [PATCH 30/45] Use pg_isready as a healthcheck for Postgres --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 75b44fb8..920f1322 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: - POSTGRES_PASSWORD=$POSTGRES_PASSWORD - POSTGRES_DB=$POSTGRES_DB healthcheck: - test: 'psql -c "\l" postgres postgres' + test: ["CMD-SHELL", "pg_isready"] volumes: - postgres-data:/var/lib/postgresql/data ports: From 02726cd6e0a508dbbc792fae341b261f1a038b40 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 10:35:04 +0200 Subject: [PATCH 31/45] Move frontend container command back to Dockerfile.dev --- docker-compose.yml | 1 - frontend/Dockerfile.dev | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 920f1322..06e423be 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,7 +88,6 @@ services: - ./frontend:/usr/src/app - frontend-node-modules:/usr/src/app/node_modules - frontend-angular-cache:/usr/src/app/.angular - command: ng serve --host 0.0.0.0 --disable-host-check --poll 200 ports: - 127.0.0.1:4200:4200 - 127.0.0.1:9876:9876 diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index f2e369df..a9b9db37 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -12,3 +12,6 @@ RUN yarn install --frozen-lockfile # Expose the port for the development server. EXPOSE 4200 + +# Start the development server. The poll flag allows for live reloading. +CMD ["ng", "serve", "--host", "0.0.0.0", "--disable-host-check", "--poll", "200"] From 189acc749b26521dccad5d64dca693e4c6d83dc6 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 10:56:10 +0200 Subject: [PATCH 32/45] Suspend spa_url from urls.py --- backend/langpro_annotator/urls.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/langpro_annotator/urls.py b/backend/langpro_annotator/urls.py index e4808da0..0b45d0fe 100644 --- a/backend/langpro_annotator/urls.py +++ b/backend/langpro_annotator/urls.py @@ -33,10 +33,10 @@ api_router.register(r"label", LabelAnnotationView, basename="labels") -if settings.PROXY_FRONTEND: - spa_url = re_path(r"^(?P.*)$", proxy_frontend) -else: - spa_url = re_path(r"", index) +# if settings.PROXY_FRONTEND: +# spa_url = re_path(r"^(?P.*)$", proxy_frontend) +# else: +# spa_url = re_path(r"", index) urlpatterns = [ path("admin", RedirectView.as_view(url="/admin/", permanent=True)), @@ -54,5 +54,5 @@ ), path("api/i18n/", i18n), path("users/", include("user.urls")), - spa_url, # catch-all; unknown paths to be handled by a SPA + # spa_url, # catch-all; unknown paths to be handled by a SPA ] From 1fbd2333ed54736595a3831e80dcea24bb7a6f0c Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 13:35:25 +0200 Subject: [PATCH 33/45] Use correct user and database in healthcheck --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 06e423be..42a30795 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: - POSTGRES_PASSWORD=$POSTGRES_PASSWORD - POSTGRES_DB=$POSTGRES_DB healthcheck: - test: ["CMD-SHELL", "pg_isready"] + test: ["CMD-SHELL", "pg_isready", "-U", "$POSTGRES_USER", "-d", "$POSTGRES_DB"] volumes: - postgres-data:/var/lib/postgresql/data ports: From 3a37f4dfd8833d47435119c7cda6bb21ab9a8cd7 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 14:21:01 +0200 Subject: [PATCH 34/45] Update langpro-container --- langpro-container | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langpro-container b/langpro-container index 0a358141..c59fe01e 160000 --- a/langpro-container +++ b/langpro-container @@ -1 +1 @@ -Subproject commit 0a358141bf653843e91817c450f870a15553f8f9 +Subproject commit c59fe01edb8f36f3bdcb0aef8bfaa5396f88c264 From 39e551648766dc254cf9be40b9a1407aba2a0170 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 14:21:01 +0200 Subject: [PATCH 35/45] Update langpro-container --- langpro-container | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langpro-container b/langpro-container index c59fe01e..133f94b4 160000 --- a/langpro-container +++ b/langpro-container @@ -1 +1 @@ -Subproject commit c59fe01edb8f36f3bdcb0aef8bfaa5396f88c264 +Subproject commit 133f94b462999d51110c2d97a3f88ea3427e0874 From edf371f227e4e06f41a430983cadd616633fa114 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 16:22:56 +0200 Subject: [PATCH 36/45] Update LangPro container --- langpro-container | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langpro-container b/langpro-container index 133f94b4..466112e7 160000 --- a/langpro-container +++ b/langpro-container @@ -1 +1 @@ -Subproject commit 133f94b462999d51110c2d97a3f88ea3427e0874 +Subproject commit 466112e7b068cfeb2f35ec0d22cf6c7c1a5f6c7a From 6e65a730ca696c2f583a5596918d594d0506b44b Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 16:25:36 +0200 Subject: [PATCH 37/45] Reenable concomitant parse tables --- .../annotation-parse-results.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts b/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts index 8f34ff67..e96ec5af 100644 --- a/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts +++ b/frontend/src/app/annotate/annotation-parse-results/annotation-parse-results.component.ts @@ -24,9 +24,9 @@ function unfoldParseResult(parse: CCGParse): UnfoldedParseResult { ...parse, ccgTrees: [ { type: "CCG Tree", tree: ccg_tree }, - // { type: "CCG Term", tree: ccg_term }, - // { type: "Corrected CCG Term", tree: corr_term }, - // { type: "Lambda Logical Form", tree: llf } + { type: "CCG Term", tree: ccg_term }, + { type: "Corrected CCG Term", tree: corr_term }, + { type: "Lambda Logical Form", tree: llf } ] }; } From 145b0745412826217227d5cf57ce658af9be8201 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 2 Apr 2026 16:46:29 +0200 Subject: [PATCH 38/45] Back to feature/langpro-submodule in langpro-container --- langpro-container | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langpro-container b/langpro-container index 466112e7..d0a12411 160000 --- a/langpro-container +++ b/langpro-container @@ -1 +1 @@ -Subproject commit 466112e7b068cfeb2f35ec0d22cf6c7c1a5f6c7a +Subproject commit d0a12411b459316ac6d6474ca9efa3a5e8fdc023 From 044eee3d314d26d82d50457d00d9d5dc78fa368f Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 15 Apr 2026 11:23:00 +0200 Subject: [PATCH 39/45] Add CSRF token route --- backend/langpro_annotator/settings.py | 14 ++-- backend/langpro_annotator/urls.py | 2 + backend/langpro_annotator/views.py | 6 ++ frontend/src/app/app.component.ts | 94 +++++++++++++++------------ 4 files changed, 67 insertions(+), 49 deletions(-) create mode 100644 backend/langpro_annotator/views.py diff --git a/backend/langpro_annotator/settings.py b/backend/langpro_annotator/settings.py index b4a9a7bd..a67cc77a 100644 --- a/backend/langpro_annotator/settings.py +++ b/backend/langpro_annotator/settings.py @@ -24,15 +24,17 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = int(os.getenv("DJANGO_DEBUG", 0)) == 1 +# CSRF trusted origins for cross-origin requests +CSRF_TRUSTED_ORIGINS = [] + ALLOWED_HOSTS = ["la-backend"] + if DEBUG: ALLOWED_HOSTS.append("localhost") - -# CSRF trusted origins for cross-origin requests -CSRF_TRUSTED_ORIGINS = [ - "http://localhost:5000", - "http://127.0.0.1:5000", -] + CSRF_TRUSTED_ORIGINS.extend([ + "http://localhost:5000", + "http://127.0.0.1:5000", + ]) # Application definition diff --git a/backend/langpro_annotator/urls.py b/backend/langpro_annotator/urls.py index 0b45d0fe..2f5bec7f 100644 --- a/backend/langpro_annotator/urls.py +++ b/backend/langpro_annotator/urls.py @@ -22,6 +22,7 @@ from rest_framework import routers from annotation.views import LabelAnnotationView +from langpro_annotator.views import csrf_token from problem.views.problem import ProblemView from .index import index @@ -53,6 +54,7 @@ ), ), path("api/i18n/", i18n), + path("api/csrf", csrf_token), path("users/", include("user.urls")), # spa_url, # catch-all; unknown paths to be handled by a SPA ] diff --git a/backend/langpro_annotator/views.py b/backend/langpro_annotator/views.py new file mode 100644 index 00000000..000d95a7 --- /dev/null +++ b/backend/langpro_annotator/views.py @@ -0,0 +1,6 @@ +from django.views.decorators.csrf import ensure_csrf_cookie +from django.http import JsonResponse + +@ensure_csrf_cookie +def csrf_token(request): + return JsonResponse({"detail": "CSRF cookie set"}) diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 366bccbc..65f4f47d 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,43 +1,51 @@ -import { Component, Inject, afterRender } from "@angular/core"; -import { DOCUMENT } from "@angular/common"; -import { RouterOutlet } from "@angular/router"; -import { MenuComponent } from "./menu/menu.component"; -import { FooterComponent } from "./footer/footer.component"; -import { DarkModeService } from "./services/dark-mode.service"; -import { ToastContainerComponent } from "./toast-container/toast-container.component"; - -@Component({ - selector: "la-root", - standalone: true, - imports: [ - RouterOutlet, - MenuComponent, - FooterComponent, - ToastContainerComponent, - ], - templateUrl: "./app.component.html", - styleUrl: "./app.component.scss", -}) -export class AppComponent { - title = "LangPro Annotator"; - - constructor( - @Inject(DOCUMENT) private document: Document, - private darkModeService: DarkModeService - ) { - // Using the DOM API to only render on the browser instead of on the server - afterRender(() => { - const style = this.document.createElement("link"); - style.rel = "stylesheet"; - this.document.head.append(style); - - this.darkModeService.theme$.subscribe((theme) => { - this.document.documentElement.setAttribute( - "data-bs-theme", - theme - ); - style.href = `${theme}.css`; - }); - }); - } -} +import { Component, DestroyRef, Inject, afterRender, inject } from "@angular/core"; +import { DOCUMENT } from "@angular/common"; +import { RouterOutlet } from "@angular/router"; +import { MenuComponent } from "./menu/menu.component"; +import { FooterComponent } from "./footer/footer.component"; +import { DarkModeService } from "./services/dark-mode.service"; +import { ToastContainerComponent } from "./toast-container/toast-container.component"; +import { HttpClient } from "@angular/common/http"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; + +@Component({ + selector: "la-root", + standalone: true, + imports: [ + RouterOutlet, + MenuComponent, + FooterComponent, + ToastContainerComponent, + ], + templateUrl: "./app.component.html", + styleUrl: "./app.component.scss", +}) +export class AppComponent { + title = "LangPro Annotator"; + http = inject(HttpClient); + destroyRef = inject(DestroyRef); + + constructor( + @Inject(DOCUMENT) private document: Document, + private darkModeService: DarkModeService + ) { + // Using the DOM API to only render on the browser instead of on the server + afterRender(() => { + const style = this.document.createElement("link"); + style.rel = "stylesheet"; + this.document.head.append(style); + + this.darkModeService.theme$.subscribe((theme) => { + this.document.documentElement.setAttribute( + "data-bs-theme", + theme + ); + style.href = `${theme}.css`; + }); + }); + + this.http.get("/api/csrf").pipe( + takeUntilDestroyed(this.destroyRef) + ).subscribe() + } +} From b1f03d626737c9d7989ae0ddf14a7ed45915ae7e Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Wed, 15 Apr 2026 11:23:59 +0200 Subject: [PATCH 40/45] Use LANGPRO_CONTAINER env var in common_settings.py --- backend/langpro_annotator/common_settings.py | 217 ++++++++++--------- 1 file changed, 110 insertions(+), 107 deletions(-) diff --git a/backend/langpro_annotator/common_settings.py b/backend/langpro_annotator/common_settings.py index 5d6d410e..00796714 100644 --- a/backend/langpro_annotator/common_settings.py +++ b/backend/langpro_annotator/common_settings.py @@ -1,107 +1,110 @@ -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "livereload", - "django.contrib.staticfiles", - "rest_framework", - "django.contrib.sites", - "rest_framework.authtoken", - "dj_rest_auth", - "dj_rest_auth.registration", - "allauth", - "allauth.account", - # Required for deleting accounts, but not actually used, - # cf. https://github.com/iMerica/dj-rest-auth/pull/110. - "allauth.socialaccount", - "user", - "revproxy", - "problem", - "annotation", -] - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.locale.LocaleMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "allauth.account.middleware.AccountMiddleware", -] - -# Internationalization -# https://docs.djangoproject.com/en/3.0/topics/i18n/ -LANGUAGES = [ - ("en", "English"), - ("nl", "Nederlands"), -] -LANGUAGE_CODE = "en" - -TIME_ZONE = "Europe/Amsterdam" - -USE_I18N = True - -USE_TZ = True # Authentication -REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": [ - "rest_framework.authentication.TokenAuthentication", - "rest_framework.authentication.SessionAuthentication", - ] -} - -AUTH_USER_MODEL = "user.User" -ACCOUNT_EMAIL_VERIFICATION = "mandatory" -ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"] - -SITE_ID = 1 -SITE_NAME = "langpro_annotator" - -# Remove this setting in production! -EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" - -HOST = "localhost:8000" - -REST_AUTH = { - "USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer", -} - -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "verbose": { - "format": "{levelname} {asctime} {module} {message}", - "style": "{", - }, - "simple": { - "format": "{levelname} {message}", - "style": "{", - }, - }, - "handlers": { - "console": { - "level": "INFO", - "class": "logging.StreamHandler", - "formatter": "simple", - }, - }, - "loggers": { - "django": { - "handlers": ["console"], - "level": "INFO", - "propagate": False, - }, - "LangProAnnotator": { - "handlers": ["console"], - "level": "INFO", - "propagate": False, - }, - }, -} - -LANGPRO_URL = "http://localhost:8080" +import os + + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "livereload", + "django.contrib.staticfiles", + "rest_framework", + "django.contrib.sites", + "rest_framework.authtoken", + "dj_rest_auth", + "dj_rest_auth.registration", + "allauth", + "allauth.account", + # Required for deleting accounts, but not actually used, + # cf. https://github.com/iMerica/dj-rest-auth/pull/110. + "allauth.socialaccount", + "user", + "revproxy", + "problem", + "annotation", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "allauth.account.middleware.AccountMiddleware", +] + +# Internationalization +# https://docs.djangoproject.com/en/3.0/topics/i18n/ +LANGUAGES = [ + ("en", "English"), + ("nl", "Nederlands"), +] +LANGUAGE_CODE = "en" + +TIME_ZONE = "Europe/Amsterdam" + +USE_I18N = True + +USE_TZ = True # Authentication +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "rest_framework.authentication.TokenAuthentication", + "rest_framework.authentication.SessionAuthentication", + ] +} + +AUTH_USER_MODEL = "user.User" +ACCOUNT_EMAIL_VERIFICATION = "mandatory" +ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"] + +SITE_ID = 1 +SITE_NAME = "langpro_annotator" + +# Remove this setting in production! +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" + +HOST = "localhost:8000" + +REST_AUTH = { + "USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer", +} + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "{levelname} {asctime} {module} {message}", + "style": "{", + }, + "simple": { + "format": "{levelname} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + "LangProAnnotator": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + }, +} + +LANGPRO_URL = os.environ.get('LANGPRO_CONTAINER') or 'http://localhost:8080' From ec0e9e083cf8fce51d491d160fddc70b79bd209c Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 16 Apr 2026 16:04:43 +0200 Subject: [PATCH 41/45] Increase log level for backend-prod --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 42a30795..b37dc96e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,11 +47,13 @@ services: test: "curl -f localhost:8000/admin/" volumes: - ./logs/django:/usr/src/app/logs + - ./seed/:/usr/src/app/backend/problem/data command: > gunicorn langpro_annotator.wsgi:application -w 4 -b 0.0.0.0:8000 --timeout 600 + --log-level debug --access-logfile /usr/src/app/logs/access_log --error-logfile /usr/src/app/logs/error_log --capture-output From 6f513482c4ddd2ed92c8246be8d5b8c8690dadda Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Thu, 16 Apr 2026 17:03:52 +0200 Subject: [PATCH 42/45] Add z to volumes --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b37dc96e..47e37930 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,8 +46,8 @@ services: healthcheck: test: "curl -f localhost:8000/admin/" volumes: - - ./logs/django:/usr/src/app/logs - - ./seed/:/usr/src/app/backend/problem/data + - ./logs/django:/usr/src/app/logs:z + - ./seed/:/usr/src/app/backend/problem/data:z command: > gunicorn langpro_annotator.wsgi:application -w 4 From 11c39258066585cbd908d1ad49bbe649cba3bce3 Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 17 Apr 2026 09:20:32 +0200 Subject: [PATCH 43/45] Fix log file path --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 47e37930..2c39df44 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: healthcheck: test: "curl -f localhost:8000/admin/" volumes: - - ./logs/django:/usr/src/app/logs:z + - ./log/django:/usr/src/app/logs:z - ./seed/:/usr/src/app/backend/problem/data:z command: > gunicorn langpro_annotator.wsgi:application From 8afaf24d8be80997371c3c80e5c1880da5349f5d Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 17 Apr 2026 09:25:42 +0200 Subject: [PATCH 44/45] Hopefully fix postgres healthcheck --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2c39df44..1eed815e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,7 @@ services: - POSTGRES_PASSWORD=$POSTGRES_PASSWORD - POSTGRES_DB=$POSTGRES_DB healthcheck: - test: ["CMD-SHELL", "pg_isready", "-U", "$POSTGRES_USER", "-d", "$POSTGRES_DB"] + test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"] volumes: - postgres-data:/var/lib/postgresql/data ports: From 0538d9c06e6034120593683f5c257fcc4f53b52d Mon Sep 17 00:00:00 2001 From: Xander Vertegaal Date: Fri, 17 Apr 2026 09:33:41 +0200 Subject: [PATCH 45/45] Fix log directory --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1eed815e..0594fd89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -46,7 +46,7 @@ services: healthcheck: test: "curl -f localhost:8000/admin/" volumes: - - ./log/django:/usr/src/app/logs:z + - ./logs/django:/usr/src/app/logs:z - ./seed/:/usr/src/app/backend/problem/data:z command: > gunicorn langpro_annotator.wsgi:application