Files
chat_backend/README.md
T
westfarn ad44359804
Unit Tests / test (push) Successful in 10s
Add finance app with Stripe Checkout subscriptions (#21) (#23)
## 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
2026-07-26 17:35:06 -07:00

172 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](https://git.aimloperations.com/ai_ml_operations/chat_web_app)
(node-static, not Docker).
Ticket: [chat_backend#6](https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/6)
## Layout
```text
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](https://docs.astral.sh/uv/)
- Docker + Docker Compose (optional, recommended)
- Ollama reachable at `OLLAMA_BASE_URL` for LLM features
### uv (host)
```bash
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:
```bash
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:
```bash
cd llm_be
RUN_LIVE_OLLAMA_TESTS=1 uv run python manage.py test chat_backend.tests.test_live_ollama
```
### Docker (dev, bundled Postgres)
```bash
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):
```text
~/Documents/secrets/chat_backend/chat_backend_prod.env
```
Validate with:
```bash
./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.
Chromas vector index may still use a volume (`chroma_db`); that is embeddings
metadata, not the original upload.
## Production (docker-compose.prod.yml)
- Single `web` service; **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:
```bash
~/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/`.