## Summary - Closes #21 — new Django `finance` app with Stripe as payment provider - Subscription price defaults to **$10 USD / month** via `SUBSCRIPTION_PRICE_AMOUNT_CENTS = 1000` in `settings.py` (env-overridable) - Persists **Invoice** and **Payment** rows; both registered in Django admin (with payment inline on invoices) - Checkout Session API redirects users to Stripe hosted payment; webhook verifies signatures and upserts ledger idempotently ## API - `POST /api/finance/checkout/` — JWT auth → `{ checkout_url, session_id }` - `GET /api/finance/invoices/` / `GET /api/finance/payments/` — own records - `POST /api/finance/webhooks/stripe/` — Stripe signature-verified webhook ## Config Documented in `.env.example` / `.env.prod.example`: `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`, optional `STRIPE_PRICE_ID`, `FRONTEND_BASE_URL` ## Test plan - [x] `uv run python manage.py test finance` (17 tests) - [ ] Set Stripe test keys locally; create checkout session; complete payment in Stripe test mode - [ ] Confirm Invoice/Payment appear in `/admin/` - [ ] Point Stripe webhook to `/api/finance/webhooks/stripe/` and verify `checkout.session.completed` / `invoice.paid`Reviewed-on: #23
6.3 KiB
Chat Backend
Django + Channels API for AIML Operations chat (chatbackend.aimloperations.com).
Packaging via uv; production deploy via server-infra.
Companion frontend: chat_web_app
(node-static, not Docker).
Ticket: chat_backend#6
Layout
chat_backend/ ← repo root (Dockerfile, compose, pyproject, .gitea)
├── llm_be/ ← Django project root (manage.py)
│ ├── manage.py
│ ├── llm_be/ ← settings, urls, asgi/wsgi
│ └── chat_backend/ ← app (models, consumers, services, storage)
├── scripts/
│ ├── docker-entrypoint.sh
│ └── validate-env.sh
├── docker-compose.yml ← local/CI (bundled Postgres)
└── docker-compose.prod.yml ← server-infra (external DATABASE_URL)
Local development
Prerequisites
- Python 3.12+
- uv
- Docker + Docker Compose (optional, recommended)
- Ollama reachable at
OLLAMA_BASE_URLfor LLM features
uv (host)
cp .env.example .env
uv sync
cd llm_be
uv run python manage.py migrate
uv run python manage.py runserver 0.0.0.0:8003
Without DATABASE_URL / DB_HOST, settings fall back to SQLite (llm_be/db.sqlite3).
Tests:
cd llm_be
uv run python manage.py test
The suite is offline by default — the custom test runner
(llm_be/test_runner.py) sets SKIP_RAG_INIT=1 and a cheap password hasher, and
LLM chains are faked, so no Ollama, Chroma, SMTP or network access is needed.
Tests live in llm_be/chat_backend/tests/ (models, storage, serializers, views,
services, signals, consumers).
Non-deterministic checks against a real model server are opt-in:
cd llm_be
RUN_LIVE_OLLAMA_TESTS=1 uv run python manage.py test chat_backend.tests.test_live_ollama
Docker (dev, bundled Postgres)
docker compose up --build
App: http://localhost:8003 — Postgres via bundled db
(postgres://chat_backend:chat_backend@db:5432/chat_backend).
Compose does not read host DATABASE_URL (avoids CI/prod leaks); override
with COMPOSE_DATABASE_URL if needed.
Environment variables
| Variable | Dev default | Prod required | Notes |
|---|---|---|---|
DJANGO_ENV |
dev |
prod / beta |
|
DJANGO_SECRET_KEY |
insecure default | yes | Must be real in prod/beta |
DJANGO_DEBUG |
true when dev |
false |
|
DJANGO_ALLOWED_HOSTS |
localhost + chat hosts | yes | Comma-separated |
DJANGO_CSRF_TRUSTED_ORIGINS |
derived from hosts | optional | Full origins |
DATABASE_URL |
SQLite fallback | yes | Shared Postgres in prod |
WEB_PORT |
n/a (compose maps 8003) | 8003 |
Host port for prod compose |
OLLAMA_BASE_URL |
http://127.0.0.1:11434 |
yes | GPU host in prod: http://10.0.0.128:11434 |
OLLAMA_MODEL / OLLAMA_EMBED_MODEL |
from DEBUG |
optional | Override model names |
EMAIL_HOST_* |
empty | yes (prod/beta) | SMTP2GO |
CAPTCHA_SECRET_KEY |
empty | recommended | |
STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY / STRIPE_WEBHOOK_SECRET |
empty | yes for billing | Stripe API + webhook |
STRIPE_PRICE_ID |
empty | optional | Pre-created Price; else $10/mo from settings |
FRONTEND_BASE_URL |
http://localhost:3000 |
set in prod | Checkout success/cancel base |
CORS_ALLOWED_ORIGINS |
local + chat FE | set in prod | Frontend origin |
USE_TLS_PROXY |
false (dev) | true behind NPM | Sets SECURE_PROXY_SSL_HEADER |
GUNICORN_WORKERS / GUNICORN_BIND |
2 / 0.0.0.0:8000 |
optional | Entrypoint |
SKIP_RAG_INIT |
unset | CI/migrate often 1 |
Skip Chroma/Ollama boot work |
Assistant identity (Hesychia) lives in code:
llm_be/chat_backend/services/assistant_identity.py — prepended to user-facing
generation prompts (chat, RAG, data analysis). Not env-configurable.
Templates: .env.example (local), .env.prod.example (control-node secret).
Control-node secret path (server-infra on ai-server-4080):
~/Documents/secrets/chat_backend/chat_backend_prod.env
Validate with:
./scripts/validate-env.sh ~/Documents/secrets/chat_backend/chat_backend_prod.env
If DATABASE_URL password contains $, escape each as $$ for Compose.
Ollama
All clients (ollama.Client, OllamaLLM, OllamaEmbeddings, ChatOllama) use
OLLAMA_BASE_URL — never hardcoded localhost in deployed code.
| Env | Typical URL |
|---|---|
| Local (Ollama on same machine) | http://127.0.0.1:11434 |
| prod / beta (containers on adama/roslin/ai-server) | http://10.0.0.128:11434 |
Firewall / Ollama listen on ai-server-4080 must allow 10.0.0.0/24 → :11434.
File storage
Prompt attachments and workspace documents use DatabaseStorage
(chat_backend.StoredFile BinaryField in Postgres). Blobs are not written
to the container filesystem under media/.
RAG loaders that need a path materialize a short-lived temp file, then delete it.
Chroma’s vector index may still use a volume (chroma_db); that is embeddings
metadata, not the original upload.
Production (docker-compose.prod.yml)
- Single
webservice; no bundled DB —DATABASE_URL→ shared Postgres (10.0.0.230). - Host port from
WEB_PORT(catalog: 8003; beta reserved 8013). - Entrypoint: wait DB → migrate → collectstatic →
gunicorn+UvicornWorker(ASGI for HTTP and WebSockets). - Active/active on adama + roslin + ai-server-4080; NPM balances upstreams.
- Deployed by:
~/Documents/repos/server-infra/scripts/deploy.sh \
--app chat_backend --env prod --ref <sha>
CI / CD (Gitea Actions)
| Workflow | Trigger | Action |
|---|---|---|
unittests.yml |
push + PR → master |
uv sync + manage.py test |
ci.yml |
PR → master |
same unit tests |
deploy.yml |
after Unit Tests succeeds on master push |
docker build + tests on ephemeral compose Postgres → deploy.sh |
Deploy never runs on PRs.
Security note
Secrets previously hardcoded in settings.py (email password, captcha, Django
secret) must live only in the control-node env file. Rotate anything that was
ever committed; never commit .env or ~/Documents/secrets/.