Clean up web changes and add favicon

- Update uv.lock dependencies
- Remove web/CLAUDE.md (moved to root)
- Update base.html template
- Add favicon.svg

Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-19 22:46:33 +01:00
parent 1e8a173ae8
commit 32132974b2
4 changed files with 1455 additions and 919 deletions

View File

@@ -1,78 +0,0 @@
# 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).

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<ellipse cx="16" cy="16" rx="11" ry="14" fill="#B45309"/>
<path d="M16 4 C14 10, 14 22, 16 28" stroke="#FFFBF5" stroke-width="1.5" fill="none" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.svg') }}" type="image/svg+xml">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">