Paperclip CTO 9e3c28b191
Some checks failed
CI / lint (push) Failing after 6s
CI / test (push) Failing after 10s
CI / build-container (push) Has been skipped
feat(TRA-360): enforce license_user_limit when assigning users to an org
Before creating a UserRoleBinding for a specific org_id, check whether
the org's OrganizationProfile.license_user_limit has been reached. New
users attempting to join a full org receive HTTP 403 with a descriptive
message. Re-assigning a user already in the org (role change) is
unaffected, as is assignment to an org with no OrganizationProfile.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:54:22 +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

Backend deployment

1. Prerequisites

  • Python 3.12
  • PostgreSQL 16+
  • Redis 7+
  • Docker (recommended for containerized deploy)

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.prod in production)

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://<api-domain>/healthz/

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 310 KiB
Languages
Python 96.6%
Shell 1.3%
HTML 0.9%
TypeScript 0.5%
JavaScript 0.3%
Other 0.4%