webapp/WebApp.md
Rama main · réplica de lectura del repositorio original.
# Web App
A small Flask front-end for the local SQL agent (NLQ engine). Users log in,
ask natural-language questions (Persian supported), and see the generated SQL,
a plain-language interpretation, and the result rows — the full result set is
also exported to a CSV under `outputs/`.
## Structure
```
webapp/
├── app.py # Flask entry point: routes (/login, /register, /logout, /) + create-user CLI
├── agent.py # Bridge to the repo's NLQ engine (api.runner.run_query); exports rows to CSV
├── db.py # SQLite persistence: users + logs tables in app.db
├── requirements.txt # flask, python-dotenv
├── .gitignore
├── .secret_key # auto-generated session secret (created on first run)
├── app.db # SQLite database (auto-created; users + query logs)
├── outputs/ # CSV exports of query results: output_YYYYMMDD_HHMMSS.csv
├── static/
│ └── style.css # styles shared by all pages
├── templates/
│ ├── base.html # layout used by every page
│ ├── index.html # main page: ask a question, view SQL / interpretation / result table
│ ├── login.html # login form
│ └── register.html # admin-only account creation form
└── __pycache__/ # generated bytecode (git-ignored)
```
## How it works
- `agent.py` adds the repo root to `sys.path` and loads `.env` from there, so
the web app runs the exact same generation → validation → execution pipeline
as the CLI / API (`api.runner.run_query`).
- Every question and its outcome is logged to the `logs` table in `app.db`.
- Up to 100 result rows are rendered on the page; the full set goes to the CSV.
- Passwords are stored as salted hashes (werkzeug), never plain text.
## Prerequisites
- Python 3.13 (the app is invoked as `py -3.13`)
- The repo-root `.env` file (copy `.env.example`) with:
- `OPENAI_BASE_URL` / `OPENAI_MODEL` (and `OPENAI_API_KEY` if the endpoint
wants one) — any OpenAI-compatible endpoint serving the model (vLLM, LM
Studio, Ollama's `/v1`)
- `DB_CONNECTION_URL` and `DB_PASSWORD` — the SQL Server connection, or a
`project_config/datasources.yaml` with one `DB_PASSWORD_*` per source when
the deployment queries several databases (`docs/deployment-runbook.md`
§16); the web app goes through the same pipeline, so it follows
whichever the API uses
- The repo-root dependencies (`api` package) installed
## Running
From this folder (`webapp/`):
```powershell
# 1. Install the web app dependencies
pip install -r requirements.txt
# 2. Install the repo-root (NLQ engine) dependencies — once, from the repo root
pip install -r ..\requirements.lock
# 3. Create a user account (password is prompted if omitted)
py -3.13 app.py create-user alice
# or: py -3.13 app.py create-user alice s3cret
# 4. Start the dev server
py -3.13 app.py
```
Then open http://127.0.0.1:5000 and log in.
## Notes
- `ADMIN_USER` **must** be set (no default — a security-audit fix removed
the literal username that used to ship here). Only that account can
create new accounts via `/register`; the app refuses to start at all
without it.
- The session secret comes from the `SECRET_KEY` env var if set; otherwise a
random key is generated once and persisted in `.secret_key` (owner-only
permissions on POSIX — see `_secret_key()`'s docstring).
- Sessions last 8 hours.
- Repeated failed `/login` attempts from the same IP are throttled: after
roughly 30 consecutive failures within a 15-minute window, further
attempts get `429` until a login from that IP succeeds, or the window
rolls past with no further failures — this throttle expires on its own
and does not lock an IP out indefinitely.
- Every POST form, `/login` included, carries a CSRF token and is
rejected with `400` if it is missing or wrong — see `app.py`'s
`_CSRF_EXEMPT_ENDPOINTS` comment for how a token is bound to a session
before that session has ever authenticated.
- **The session cookie requires HTTPS** (`SESSION_COOKIE_SECURE=True`).
Running the dev server above over plain `http://` still lets you load
every page, but the browser will not send the cookie back, so
`/login` will look like it silently does nothing — you get bounced
back to the login page after what looked like a successful sign-in.
Serve the app over HTTPS (a reverse proxy terminating TLS is the usual
shape for this) to actually keep a session. This is intentionally not
an environment-variable toggle: a flag that is easy to flip off is a
flag that stays off past whatever local experiment prompted it — if
you need it off for local HTTP-only development, comment out that one
line in `create_app()` and remember why before you deploy.
- `app.db` is created automatically on first run (`db.init_db()`).
- Query result CSVs are written to `outputs/` (UTF-8 with BOM, so Excel opens
Persian text correctly) with formula-injection defusing applied to every
cell (`exporters/sanitize.py`).