- Fix pipeline granularity: add market_year to cleaned/serving SQL models - Add DuckDB data access layer with async query functions (analytics.py) - Build Chart.js dashboard: supply/demand, STU ratio, top producers, YoY table - Add country comparison page with multi-select picker - Replace items CRUD with read-only commodity API (list, metrics, countries, CSV) - Configure BeanFlows plan tiers (Free/Starter/Pro) with feature gating - Rewrite public pages for coffee market intelligence positioning - Remove boilerplate items schema, update health check for DuckDB - Add test suite: 139 tests passing (dashboard, API, billing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
4.5 KiB
Markdown
79 lines
4.5 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## What This Is
|
|
|
|
A **Copier template** for generating SaaS applications. The template lives in `{{project_slug}}/` with Jinja2-templated files (`.jinja` extension). Files without `.jinja` are copied as-is. The `copier.yml` defines template variables: `project_slug`, `project_name`, `description`, `author_name`, `author_email`, `base_url`, `payment_provider` (stripe/paddle/lemonsqueezy).
|
|
|
|
This is NOT a runnable application itself — it generates one via `copier copy`.
|
|
|
|
## Generated Project Commands
|
|
|
|
After generation, the project uses **uv** as package manager:
|
|
|
|
```bash
|
|
uv sync # Install dependencies
|
|
uv run python -m <slug>.migrations.migrate # Initialize/migrate DB
|
|
uv run python -m <slug>.app # Run dev server (port 5000)
|
|
uv run python -m <slug>.worker # Run background worker
|
|
uv run python -m <slug>.worker scheduler # Run periodic scheduler
|
|
uv run pytest # Run tests
|
|
uv run ruff check . # Lint
|
|
docker compose up -d # Production deploy
|
|
```
|
|
|
|
## Stack
|
|
|
|
- **Quart** (async Flask) with Jinja2 templates and Pico CSS (no build step)
|
|
- **SQLite** with WAL mode + aiosqlite (no ORM — plain SQL everywhere)
|
|
- **Stripe**, **Paddle**, or **LemonSqueezy** for billing (chosen via `payment_provider` template variable)
|
|
- **Resend** for transactional email
|
|
- **Litestream** for SQLite replication/backups
|
|
- **Docker + Caddy** for deployment
|
|
- **Hypercorn** as ASGI server in production
|
|
|
|
## Architecture
|
|
|
|
### Domain-based flat structure
|
|
|
|
Each domain is a directory with `routes.py` + `templates/`. Routes, SQL queries, and decorators all live together in `routes.py` — no separate models/services/repositories layers.
|
|
|
|
```
|
|
src/<slug>/
|
|
app.py → Application factory, blueprint registration, middleware
|
|
core.py → Config class, DB helpers (fetch_one/fetch_all/execute), email, CSRF, rate limiting
|
|
worker.py → SQLite-based background task queue (no Redis)
|
|
auth/ → Magic link auth, login_required/subscription_required decorators
|
|
billing/ → Checkout/webhooks/portal (Stripe, Paddle, or LemonSqueezy), plan feature/limit checks
|
|
dashboard/ → User settings, API key management
|
|
public/ → Marketing pages (landing, terms, privacy)
|
|
api/ → REST API with Bearer token auth (api_key_required decorator)
|
|
admin/ → Password-protected admin panel (ADMIN_PASSWORD env var)
|
|
migrations/ → schema.sql + migrate.py (runs full schema idempotently with CREATE IF NOT EXISTS)
|
|
```
|
|
|
|
### Key patterns
|
|
|
|
- **Database access**: Use `fetch_one()`, `fetch_all()`, `execute()` from `core.py`. No ORM. Queries live directly in route files next to the routes that use them.
|
|
- **Auth decorators**: `@login_required` for user pages, `@subscription_required(plans=["pro"])` for plan-gated features, `@api_key_required(scopes=["read"])` for API endpoints.
|
|
- **CSRF**: `@csrf_protect` decorator on POST routes. Templates include `<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">`.
|
|
- **Background tasks**: `from ..worker import enqueue` then `await enqueue("task_name", {"key": "value"})`. Register handlers with `@task("task_name")` decorator in `worker.py`.
|
|
- **Blueprints**: Each domain registers as a Quart Blueprint with its own `template_folder`. Templates in domain dirs override shared ones.
|
|
- **Soft deletes**: `deleted_at` column pattern. Use `soft_delete()`, `restore()`, `hard_delete()` from `core.py`.
|
|
- **Dates**: All stored as ISO 8601 TEXT in SQLite, using `datetime.utcnow().isoformat()`.
|
|
- **Rate limiting**: SQLite-based, no Redis. `@rate_limit()` decorator or `check_rate_limit()` function.
|
|
- **Migrations**: Single `schema.sql` file with all `CREATE TABLE IF NOT EXISTS`. Run via `python -m <slug>.migrations.migrate`.
|
|
|
|
### Conditional template blocks
|
|
|
|
`billing/routes.py.jinja` conditionally generates the full billing implementation based on the `payment_provider` variable (stripe/paddle/lemonsqueezy). The `.env.example.jinja`, `core.py.jinja`, and `schema.sql.jinja` similarly adapt per provider.
|
|
|
|
## Design Philosophy
|
|
|
|
Data-oriented, minimal abstraction. Plain SQL over ORM. Write code first, extract patterns only when repeated 3+ times. SQLite as the default database. Server-rendered HTML with Pico CSS.
|
|
|
|
## Ruff Config
|
|
|
|
Line length 100, target Python 3.11+, rules: E, F, I, UP (ignoring E501).
|