# {{ project_name }}

{{ description }}

## Quick Start

```bash
# Start Postgres and Redis
docker compose up -d

# Install dependencies
pdm install

# Configure environment
cp .env .env.local   # edit .env.local with your settings

# Set up pre-commit hooks
pdm run setup-hooks

# Run migrations and create superuser
pdm run migrate
pdm run createsu

# Start development server
pdm run startdev 8000
```

API docs available at [http://localhost:8000/schema/swagger/](http://localhost:8000/schema/swagger/) (DEBUG mode only).

## Scripts

| Command | Description |
|---------|-------------|
| `pdm run startdev 8000` | Run development server |
| `pdm run pytest` | Run tests with coverage (80% threshold) |
| `pdm run lint-check` | Run black + isort + pylint |
| `pdm run format` | Auto-format with black + isort |
| `pdm run setup-hooks` | Install pre-commit hooks |
| `pdm run makemigrations` | Create database migrations |
| `pdm run migrate` | Apply database migrations |
| `pdm run createsu` | Create superuser from env vars |
| `pdm run collectstatic` | Collect static files |

## Architecture

### Project Layout

- **`main/`** -- Django project config: settings, root URLs, Celery app, WSGI/ASGI.
- **`base/`** -- Shared foundation: models, services, DI container, pagination, constants.
- **`tests/`** -- Integration and cross-app tests.
- **`<app>/tests/`** -- Per-app unit tests (e.g., `base/tests/`).

### Patterns

**Service layer** -- Business logic lives in `<app>/services/`, not in views or serializers. Views handle HTTP concerns, serializers handle validation, services handle everything else.

```
View (HTTP) --> Serializer (validation) --> Service (logic) --> Model (data)
```

**Dependency injection** -- Services are wired through `base/containers.py` using `dependency-injector`. Views receive services via injection rather than direct instantiation. This makes testing straightforward -- inject mocks instead of patching.

**Error handling** -- `drf-standardized-errors` is configured globally as the DRF exception handler. Raise standard DRF exceptions (`ValidationError`, `NotFound`, `PermissionDenied`, etc.) and they'll be returned in a consistent format. Never return ad-hoc error dicts.

**Health check** -- `/health/` returns 200 if the database is reachable, 503 otherwise. Nginx also exposes `/healthz` (returns 200 without hitting Django, used by load balancers).

### Adding a New App

```bash
# 1. Create the app
pdm run python manage.py startapp users

# 2. Create test directory
mkdir users/tests && touch users/tests/__init__.py

# 3. Add to SELF_APPS in main/settings.py
SELF_APPS = ["base", "users"]

# 4. Add to testpaths in pyproject.toml
testpaths = ["tests", "base", "users"]

# 5. Wire URLs in main/urls.py
path("users/", include("users.urls")),
```

## Deployment Flow

```
feat-branch --> PR (auto-labeled) --> development --> main
                                          |              |
                                      Dev deploy    Prod deploy
                                      Draft release  Publish release
                                                     Update CHANGELOG.md
```

1. Create a branch with a conventional prefix (`feat-`, `fix-`, `breaking-`, `chore-`, `docs-`).
2. Open a PR to `development` -- a label is applied automatically from the branch prefix.
3. Merge to `development` -- deploys to dev, creates a draft GitHub release with auto-generated notes grouped by label.
4. Merge to `main` -- deploys to production, publishes the release, and updates `CHANGELOG.md`.

No manual release steps. Branch naming drives everything.

### CI/CD Setup

**CI (pull requests)** -- runs automatically, no configuration needed. Tests run against a Postgres service container with hardcoded credentials.

{% if platform == "aws-eb" -%}
**CD (deployment)** -- requires two GitHub repository secrets:

| Secret | Description |
|--------|-------------|
| `AWS_ACCOUNT_ID` | Your 12-digit AWS account ID |
| `DEV_AWS_REGION` | Dev deployment region (e.g. `eu-central-1`) |
| `PROD_AWS_REGION` | Prod deployment region (e.g. `eu-central-1`) |

The CD workflows authenticate via **OIDC federation** (no static AWS keys). Create an IAM role named `GHA-EB-Deploy` with:
- **Trusted entity**: GitHub Actions OIDC provider for your repository
- **Permissions**: Elastic Beanstalk, ECR, and S3 access

{% endif -%}
### Environment Variables

All app configuration is via environment variables. See `.env` for local defaults. Key variables:

| Variable | Description |
|----------|-------------|
| `SECRET_KEY` | Django secret key (change in production) |
| `DEBUG` | `True` for development, `False` in production |
| `ALLOWED_HOSTS` | Comma-separated list of allowed hostnames |
| `CORS_ALLOWED_ORIGINS` | Comma-separated list of allowed CORS origins |
| `CSRF_TRUSTED_ORIGINS` | Comma-separated list of trusted CSRF origins |
| `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_HOST`, `DB_PORT` | PostgreSQL connection |
{% if platform == "aws-eb" -%}
| `CELERY_BROKER_URL` | SQS URL in production, local SQS/Redis in dev |
| `USE_S3` | `True` to use S3 storage, `False` for local filesystem |
| `AWS_STORAGE_BUCKET_NAME` | S3 bucket for static/media files |
{% else -%}
| `CELERY_BROKER_URL` | Redis URL for Celery broker |
{% endif -%}
| `SUPERUSER_EMAIL`, `SUPERUSER_PASSWORD` | Auto-created superuser credentials |
| `ACCESS_TOKEN_LIFETIME_MINUTES` | JWT access token lifetime (default: 60) |
| `REFRESH_TOKEN_LIFETIME_MINUTES` | JWT refresh token lifetime (default: 1440) |
| `SECURE_SSL_REDIRECT` | Auto-enabled in production (`DEBUG=False`) |
{% if platform == "aws-eb" %}

### Log Whisperer (optional)

[log-whisperer](https://pypi.org/project/log-whisperer/) monitors container logs for anomalies after each deployment. It is **disabled by default** — set `LOG_WHISPERER_ENABLED=true` in your EB environment to activate.

| Variable | Default | Description |
|----------|---------|-------------|
| `LOG_WHISPERER_ENABLED` | `false` | Master switch |
| `LOG_WHISPERER_CRON_INTERVAL` | `15` | Check interval in minutes |
| `LOG_WHISPERER_MIN_SEVERITY` | `WARN` | Minimum severity to report |
| `LOG_WHISPERER_BASELINE_MINUTES` | `30` | Learning window after deploy (suppresses alerts) |

Configure at least one notification channel:

| Variable | Description |
|----------|-------------|
| `LOG_WHISPERER_NTFY_TOPIC` | [ntfy](https://ntfy.sh) topic name |
| `LOG_WHISPERER_NTFY_URL` | Custom ntfy server URL (optional) |
| `LOG_WHISPERER_TELEGRAM_TOKEN` | Telegram bot token |
| `LOG_WHISPERER_TELEGRAM_CHAT` | Telegram chat ID |
| `LOG_WHISPERER_EMAIL_TO` | Email recipient |
| `LOG_WHISPERER_EMAIL_FROM` | Sender address |
| `LOG_WHISPERER_SMTP_HOST` | SMTP server hostname |
| `LOG_WHISPERER_SMTP_PORT` | SMTP server port |
| `LOG_WHISPERER_SMTP_USER` | SMTP username |
| `LOG_WHISPERER_SMTP_PASSWORD` | SMTP password |

These are set as EB environment variables (not GitHub Secrets).
{% endif %}

## Stack

- Python {{ python_version }}, Django {{ django_version }}, DRF {{ drf_version }}
- PostgreSQL, Redis
{% if platform == "aws-eb" -%}
- Celery (SQS broker), S3 storage, AWS Elastic Beanstalk
{% else -%}
- Celery (Redis broker)
{% endif -%}
- JWT auth (`djangorestframework-simplejwt`)
- API docs (`drf-spectacular`)
- CI/CD via GitHub Actions
