Paperclip CTO 4f7f72798f
Some checks failed
CI Build / Build Container (push) Successful in 5s
CI Quality / Ruff Lint (push) Failing after 6s
CI Security / Bandit + pip-audit (push) Successful in 19s
CI Tests / Django Tests (push) Failing after 32s
CI Tests / OpenAPI Schema (push) Has been skipped
TRA-437 Add course frontend and backend API
Backend (courses app):
- courses/serializers.py: Course, Module, Lesson, Page, Enrollment, PageProgress serializers
- courses/views.py: CourseListView, CourseDetailView, CourseEnrollView,
  CourseMyProgressView, MyEnrollmentsView, PageDetailView
- courses/urls.py: expose all 6 endpoints under /api/v1/courses/

Frontend:
- courses.html + courses.js: course catalog with "Meine Kurse" and "Alle Kurse"
  sections, enroll button with toast feedback, progress bars
- course-player.html + course-player.js: full-screen player with collapsible
  sidebar nav (module → lesson → page tree), page content renderer
  (text/video/embed), dwell gate, prev/next navigation, enrollment CTA,
  completion screen
- courses.css: course grid, player layout, sidebar nav, progress bar, dwell gate
- Add Kurse nav link to meetings.html, attendance.html
- Update index.html hero and workflow grid to include Kurse

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-20 00:13:21 +02:00

Training Software

This repository contains a Django-based training platform backend plus a static customer portal frontend.

Features for customer environment setup

  • Per-customer organization profile with configurable license user limits (20, 50, 100, 1000)
  • Per-customer branding via company name and logo URL
  • Static frontend that can be hosted on Nginx and configured with the backend API domain at deploy time
  • CI pipeline for tests, linting, and container build

Production deployment with nginx

docker-compose.prod.yml starts the full stack in one command:

  • nginx — public-facing web server on port 80; serves the frontend SPA and Django static files directly; proxies all backend routes to gunicorn
  • web — Django/gunicorn (prod settings, 4 workers)
  • celery / celery-beat — background task workers
  • db — PostgreSQL 16
  • redis — Redis 7

1. Prerequisites

  • Docker Engine 24+ with Compose v2

2. Environment variables

cp .env.example .env

Edit .env and set at minimum:

Variable Notes
DJANGO_SECRET_KEY Long random string
DJANGO_ALLOWED_HOSTS Comma-separated hostnames, e.g. training.example.com
DB_PASSWORD PostgreSQL password
SECURE_SSL_REDIRECT Set false for HTTP-only; set true (or omit) when TLS is terminated at nginx or an upstream proxy

3. Start the stack

docker compose -f docker-compose.prod.yml up -d --build

4. Run migrations and create admin user

docker compose -f docker-compose.prod.yml exec web python manage.py migrate
docker compose -f docker-compose.prod.yml exec web python manage.py createsuperuser

5. Verify health

curl http://<host>/healthz/

HTTPS

To enable HTTPS, add ssl_certificate and ssl_certificate_key directives to nginx/nginx.conf and expose port 443 in docker-compose.prod.yml, or place a TLS-terminating reverse proxy (Traefik, Caddy, etc.) in front and set SECURE_SSL_REDIRECT=true.

Production deployment with dedicated frontend container

Use docker-compose.proxy.yml if you want your external nginx to be only a reverse proxy while this stack runs its own frontend container.

1. Start the stack

cp .env.example .env
docker compose -f docker-compose.proxy.yml up -d --build
docker compose -f docker-compose.proxy.yml exec web python manage.py migrate
docker compose -f docker-compose.proxy.yml exec web python manage.py createsuperuser

By default, the frontend container is exposed on http://localhost:8080. Set FRONTEND_PORT in .env.proxy if needed.

2. External nginx reverse-proxy example

server {
  listen 80;
  server_name training.example.com;

  location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Local development

1. Prerequisites

  • Python 3.12
  • PostgreSQL 16+
  • Redis 7+
  • Docker (recommended for containerised dev)

2. Environment variables

Create environment variables based on .env.example.

Required minimum values:

  • DJANGO_SECRET_KEY
  • DJANGO_ALLOWED_HOSTS
  • DATABASE_URL
  • REDIS_URL
  • DJANGO_SETTINGS_MODULE (use config.settings.local for dev)

For Docker Compose (docker-compose.yml), use:

  • DATABASE_URL=postgres://training:training@db:5432/training
  • REDIS_URL=redis://redis:6379/0

3. Build and run with Docker Compose

docker compose up -d --build

4. Run migrations

docker compose exec web python manage.py migrate

5. Verify health

curl http://localhost:8000/healthz/

Local frontend in Docker (optional)

docker-compose.yml also includes a frontend service on http://localhost:8080. Backend-only services (db, redis, celery, certificate-renderer) run on an internal Docker network and are not exposed on host ports.

The landing page at / is a small health/config page. Main app UIs are:

  • http://localhost:8080/meetings.html
  • http://localhost:8080/attendance.html

Set FRONTEND_API_BASE_URL in .env:

# recommended in Docker: leave empty to use same-origin + /api proxy
FRONTEND_API_BASE_URL=
# only when API is on another domain:
# FRONTEND_API_BASE_URL=https://api.example.com

The same variable is supported by docker-compose.proxy.yml and docker-compose.prod.yml.

Organization profile API (license + branding)

Admin-only endpoint (requires admin role):

  • GET /api/v1/accounts/organizations/{org_id}/profile/
  • PATCH /api/v1/accounts/organizations/{org_id}/profile/

Fields

Field Type Description
company_name string Customer company name shown in the UI
license_user_limit integer Maximum number of active users — any positive integer (e.g. 20, 50, 100, 250, 1000)
brand_logo_url string (URL) URL of the customer's logo, displayed in the frontend header

Setting the license limit

license_user_limit accepts any positive integer — there are no fixed tiers. Set it to exactly the number of users your customer has licensed:

curl -X PATCH https://api.example.com/api/v1/accounts/organizations/<org_id>/profile/ \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "company_name": "Acme Corp",
    "license_user_limit": 250,
    "brand_logo_url": "https://cdn.example.com/acme-logo.svg"
  }'

Common values: 20 · 50 · 100 · 250 · 500 · 1000 — but any number works.

Frontend deployment on Nginx

The static frontend is under frontend/public.

1. Set backend API domain

Edit frontend/public/config.js and set:

window.APP_CONFIG = {
  API_BASE_URL: "https://api.example.com"
};

2. Serve via Nginx

Use frontend/nginx.conf as a reference server config and copy the frontend/public files to your Nginx web root.

CI for Gitea

Gitea Actions workflow file:

  • .gitea/workflows/ci.yml

It runs:

  • Ruff lint
  • Django test suite
  • Docker image build
Description
No description provided
Readme 705 KiB
Languages
Python 81.5%
JavaScript 10.4%
HTML 3.4%
CSS 3.3%
Shell 0.9%
Other 0.5%