Files
contact.allucanget.biz/README.md
zwitschi 912f80966b
All checks were successful
CI / test (3.11) (push) Successful in 9m27s
CI / build-image (push) Successful in 2m14s
Update README to reflect Gitea Actions workflow and Docker image tagging process
2025-10-22 17:46:21 +02:00

206 lines
12 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.
# Server README
Backend service for the contact website. The app accepts contact and newsletter submissions, persists them, applies rate limiting and origin checks, and sends notification emails when SMTP is configured. Includes admin authentication for accessing application settings and managing dynamic configuration.
## Overview
- Flask application exposed through `app.py` (development) and Gunicorn (container runtime)
- Service and blueprint architecture under `server/` for HTTP routes, business logic, and observability
- SQLite by default with optional PostgreSQL support and Redis-backed distributed rate limiting
- Docker- and docker-compose-friendly with health checks and environment-driven configuration
## Architecture
- `app.py`: simple shim that imports the Flask app for local development.
- `server/`: application package.
- `factory.py`: application factory that wires logging, middleware, routes, and database initialisation.
- `routes/`: Flask blueprints for `contact`, `newsletter`, `monitoring`, `auth`, and `admin` endpoints.
- `services/`: reusable business logic for persisting submissions and sending notifications.
- `database.py`: SQLite/PostgreSQL helpers and connection management.
- `middleware.py`, `rate_limit.py`, `metrics.py`: request guards, throttling, and Prometheus-style instrumentation.
- `settings.py`: environment-driven configuration loader.
- `auth.py`: authentication utilities and login required decorator.
- `templates/`: Jinja2 templates for HTML pages (login, admin dashboard, newsletter management, settings).
- `entrypoint.sh`: container entrypoint that tunes Gunicorn worker count and timeout before booting the WSGI app.
- `Dockerfile`: multi-stage build that installs requirements in a builder image and runs the app behind Gunicorn.
- `docker-compose.yml`: local stack for the API with bind-mounted SQLite data.
- `docker-compose.redis.yml`: optional Redis sidecar for distributed rate limiting tests.
- `tests/`: pytest suite covering API behaviour, services, metrics, and SMTP integration (opt-in).
## Quick Start
1. Create a virtual environment and install dependencies:
```pwsh
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```
2. Copy the sample environment file and adjust values:
```pwsh
Copy-Item .env.example .env
```
3. Run the development server:
```pwsh
python app.py
```
The development server listens on `http://127.0.0.1:5002` by default.
### Admin Access
Access the admin interface at `http://127.0.0.1:5002/auth/login` using the configured `ADMIN_USERNAME` and `ADMIN_PASSWORD` (defaults: admin/admin). The admin interface provides a dashboard overview, newsletter subscriber management with search and pagination, newsletter creation and sending capabilities, and dynamic application settings management. The settings page displays current application configuration and allows dynamic management of application settings, while the submissions page allows viewing and managing contact form submissions.
## API Surface
- `POST /api/contact`: accepts `name`, `email`, `message`, and optional `company`, `timeline`.
- `GET /api/contact`: retrieves contact form submissions (admin only, requires authentication). Supports pagination (`page`, `per_page`), filtering (`email`, `date_from`, `date_to`), and sorting (`sort_by`, `sort_order`).
- `GET /api/contact/<id>`: retrieves a specific contact submission by ID (admin only).
- `DELETE /api/contact/<id>`: deletes a contact submission by ID (admin only).
- `POST /api/newsletter`: subscribes an address and optional metadata to the newsletter list.
- `DELETE /api/newsletter`: unsubscribes an email address from the newsletter list.
- `PUT /api/newsletter`: updates a subscriber's email address (requires `old_email` and `new_email`).
- `GET /api/newsletter/manage`: displays HTML form for newsletter subscription management.
- `POST /api/newsletter/manage`: processes subscription management actions (subscribe, unsubscribe, update).
- `GET /health`: lightweight database connectivity check used for container health monitoring.
- `GET /metrics`: Prometheus-compatible metrics endpoint (requires `ENABLE_REQUEST_LOGS` for detailed tracing).
- `GET /admin/api/settings`: retrieves all application settings (admin only).
- `PUT /admin/api/settings/<key>`: updates a specific application setting (admin only).
- `DELETE /admin/api/settings/<key>`: deletes a specific application setting (admin only).
- `GET /admin/api/newsletter`: retrieves newsletter subscribers with pagination, filtering, and sorting (admin only).
- `POST /admin/api/newsletters`: creates a new newsletter (admin only).
- `GET /admin/api/newsletters`: retrieves newsletters with pagination and filtering (admin only).
- `POST /admin/api/newsletters/<id>/send`: sends a newsletter to all subscribers (admin only).
- `GET /admin/api/contact`: retrieves contact form submissions with pagination, filtering, and sorting (admin only).
- `DELETE /admin/api/contact/<id>`: deletes a contact submission by ID (admin only).
## Running With Docker
### Build manually
```pwsh
docker build -t contact.allucanget.biz -f Dockerfile .
```
### Run with explicit environment variables
```pwsh
docker run --rm -p 5002:5002 `
-e FLASK_SECRET_KEY=change-me `
-e SMTP_HOST=smtp.example.com `
-e SMTP_PORT=587 `
-e SMTP_USERNAME=api@example.com `
-e SMTP_PASSWORD=secret `
-e SMTP_RECIPIENTS=hello@example.com `
contact.allucanget.biz
```
### Run using docker-compose
```pwsh
docker-compose up --build
```
- Mounts `./data` into the container for SQLite persistence.
- Exposes port `5002` and wires environment variables from your `.env` file.
To experiment with Redis-backed throttling:
```pwsh
docker-compose -f docker-compose.redis.yml up --build
```
## Environment Variables
See `.env.example` for a complete reference. The most relevant groups are captured below.
### Core runtime
| Variable | Description | Default |
| --------------------- | ------------------------------------------------------------------------------------- | ------- |
| `FLASK_SECRET_KEY` | Secret used by Flask for session signing; set to a strong random value in production. | `dev` |
| `ENABLE_JSON_LOGS` | Emit logs in JSON format when `true`. | `false` |
| `ENABLE_REQUEST_LOGS` | Enable request start/end logging. | `true` |
### Admin authentication
| Variable | Description | Default |
| ---------------- | ------------------------- | ------- |
| `ADMIN_USERNAME` | Username for admin login. | `admin` |
| `ADMIN_PASSWORD` | Password for admin login. | `admin` |
### Database configuration
| Variable | Description | Default |
| -------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `DATABASE_URL` | SQLite URL or filesystem path. Examples: `sqlite:///./forms.db`, `./data/forms.db`. | `sqlite:///./data/forms.db` |
| `POSTGRES_URL` | PostgreSQL connection URI, e.g. `postgresql://user:pass@host:5432/dbname`. Requires `psycopg2-binary` installed. | _(none)_ |
When `POSTGRES_URL` is set and `psycopg2-binary` is available, the app prefers PostgreSQL. Otherwise it falls back to SQLite. A custom `DATABASE_URL` always wins over `POSTGRES_URL` to simplify local development.
### Email delivery
| Variable | Description | Default |
| ----------------- | ----------------------------------------------------------------------- | -------- |
| `SMTP_HOST` | SMTP server hostname or IP. Leave empty to disable email notifications. | _(none)_ |
| `SMTP_PORT` | SMTP server port. | `587` |
| `SMTP_USERNAME` | Username for SMTP authentication. | _(none)_ |
| `SMTP_PASSWORD` | Password or token for SMTP authentication. | _(none)_ |
| `SMTP_SENDER` | Sender email address; defaults to `SMTP_USERNAME` when unset. | _(none)_ |
| `SMTP_RECIPIENTS` | Comma-separated recipient list for notifications. | _(none)_ |
| `SMTP_USE_TLS` | Enables STARTTLS when `true`. | `true` |
### Rate limiting and caching
| Variable | Description | Default |
| ------------------- | ------------------------------------------------------------------------------------------ | -------- |
| `RATE_LIMIT_MAX` | Maximum submissions allowed per window from a single IP. Set to `0` to disable throttling. | `10` |
| `RATE_LIMIT_WINDOW` | Sliding window size (seconds) for rate limiting. | `60` |
| `REDIS_URL` | Redis connection string for distributed rate limiting; when empty, use in-memory storage. | _(none)_ |
### Request hardening
| Variable | Description | Default |
| --------------------- | ------------------------------------------------------------------------------- | -------- |
| `STRICT_ORIGIN_CHECK` | Enforce `Origin`/`Referer` validation when `true`. | `false` |
| `ALLOWED_ORIGIN` | Expected site origin (e.g. `https://example.com`) used by strict origin checks. | _(none)_ |
### Observability
| Variable | Description | Default |
| --------------------------- | --------------------------------------------------- | -------- |
| `SENTRY_DSN` | Sentry project DSN for error reporting. | _(none)_ |
| `SENTRY_TRACES_SAMPLE_RATE` | Sampling rate for Sentry performance tracing (0-1). | `0.0` |
### Docker / Gunicorn runtime
| Variable | Description | Default |
| ------------------ | ----------------------------------------------------------------------------------------------- | -------- |
| `GUNICORN_WORKERS` | Overrides auto-calculated worker count; leave blank to use `(2 × CPU) + 1` capped at 8 workers. | _(auto)_ |
| `GUNICORN_TIMEOUT` | Worker timeout in seconds used by Gunicorn. | `30` |
## Health Checks and Monitoring
- `/health` and Docker health checks verify that the API can reach the configured database.
- `/metrics` surfaces request counters, latency histograms, rate limit metrics, and SMTP success/error counts.
- Enable Sentry by setting `SENTRY_DSN` to forward exceptions and performance traces.
## Testing
```pwsh
pytest -q tests
```
SMTP integration tests are skipped unless `RUN_SMTP_INTEGRATION_TEST=1` and valid SMTP credentials are set. Run `pytest -q tests/test_integration_smtp.py` to target them explicitly.
## Deployment Notes
- A single Gitea Actions workflow (`.github/workflows/ci.yml`) exercises pytest on each push, pull request, or manual dispatch, then conditionally builds the Docker image.
- When the default branch (`main`) runs and registry secrets (`REGISTRY_URL`, `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`) are configured in Gitea, the workflow logs in and pushes both `latest` and commit-specific image tags.
- Import or mirror the required reusable actions (`actions/checkout`, `actions/setup-python`, and the Docker actions) into your Gitea instance so that the workflow can resolve them.
- For production use, deploy the container behind a load balancer or reverse proxy and supply the appropriate environment variables.