From 7d3aa3141dbabc788f155e19514dd3501d2ce5b2 Mon Sep 17 00:00:00 2001 From: Deeman Date: Tue, 17 Feb 2026 22:29:40 +0100 Subject: [PATCH] update README with local testing guide, fix feedback placeholder, sync .env.example Co-Authored-By: Claude Opus 4.6 --- padelnomics/.env.example | 12 +- padelnomics/README.md | 304 ++++++++++++++++-- .../src/padelnomics/templates/base.html | 2 +- 3 files changed, 289 insertions(+), 29 deletions(-) diff --git a/padelnomics/.env.example b/padelnomics/.env.example index 045cbc7..de4e09a 100644 --- a/padelnomics/.env.example +++ b/padelnomics/.env.example @@ -12,16 +12,20 @@ DATABASE_PATH=data/app.db MAGIC_LINK_EXPIRY_MINUTES=15 SESSION_LIFETIME_DAYS=30 -# Email (Resend) +# Email (Resend) — leave blank for dev (emails print to console) RESEND_API_KEY= EMAIL_FROM=hello@padelnomics.io ADMIN_EMAIL=leads@padelnomics.io -# Paddle +# Paddle — leave blank to skip checkout (overlay won't initialize) PADDLE_API_KEY= +PADDLE_CLIENT_TOKEN= PADDLE_WEBHOOK_SECRET= -PADDLE_PRICE_STARTER= -PADDLE_PRICE_PRO= +PADDLE_ENVIRONMENT=sandbox + +# Umami — leave blank for dev (analytics tracking disabled) +UMAMI_API_URL=https://umami.padelnomics.io +UMAMI_API_TOKEN= # Rate limiting RATE_LIMIT_REQUESTS=100 diff --git a/padelnomics/README.md b/padelnomics/README.md index 62ee193..7bf5f74 100644 --- a/padelnomics/README.md +++ b/padelnomics/README.md @@ -2,39 +2,295 @@ Plan, finance, and build your padel business. -## Development +## Local Development Setup -### CSS (Tailwind) +### Prerequisites -Uses the [Tailwind CSS standalone CLI](https://tailwindcss.com/blog/standalone-cli) — no Node.js required. +- Python 3.12+ +- [uv](https://docs.astral.sh/uv/) (Python package manager) +- WeasyPrint system dependencies (for PDF export): + - Fedora/RHEL: `sudo dnf install pango gdk-pixbuf2 cairo` + - Debian/Ubuntu: `sudo apt install libpango-1.0-0 libgdk-pixbuf2.0-0 libcairo2` + - macOS: `brew install pango gdk-pixbuf cairo` + +### 1. Install dependencies + +```bash +cd padelnomics +uv sync +``` + +### 2. Configure environment + +```bash +cp .env.example .env +# Edit .env if you want to change defaults — it works out of the box for dev +``` + +Key defaults for local dev: +- `DEBUG=true` — enables dev login, console email output, verbose errors +- `ADMIN_PASSWORD=admin` — admin panel login at `/admin` +- `DATABASE_PATH=data/app.db` — SQLite file, auto-created +- Paddle/Resend keys left blank — checkout overlay and email sending disabled, + magic links print to console + +### 3. Run database migrations + +```bash +uv run python -m padelnomics.migrations.migrate +``` + +This creates `data/app.db` with all tables. Fresh databases get the full +schema in one shot; existing databases get incremental migrations applied. + +### 4. (Optional) Set up Paddle sandbox products + +Only needed if you want to test actual checkout flows: + +```bash +# Add your Paddle sandbox keys to .env first: +# PADDLE_API_KEY=test_xxx +# PADDLE_CLIENT_TOKEN=test_xxx +# PADDLE_WEBHOOK_SECRET=pdl_ntfset_xxx + +uv run python -m padelnomics.scripts.setup_paddle +``` + +This creates all products/prices in Paddle sandbox and writes the IDs to the +`paddle_products` table. Without this, checkout buttons will show an error +("Invalid plan selected") but everything else works. + +### 5. Start the app + +```bash +uv run python -m padelnomics.app +``` + +App runs at **http://localhost:5000**. + +### 6. Start the background worker (separate terminal) + +```bash +uv run python -m padelnomics.worker +``` + +The worker processes background tasks: emails, lead forwarding, PDF generation, +credit refills. Without it, queued tasks stay pending but the app still works. + +### 7. CSS (Tailwind) ```bash make css-watch # rebuild on file changes (dev) make css-build # one-off minified build (CI/Docker) ``` -The first run downloads the Tailwind binary to `bin/tailwindcss` automatically. +First run downloads the Tailwind standalone CLI to `bin/tailwindcss`. -Edit `src/padelnomics/static/css/input.css` for theme tokens, base styles, and component classes. Output is gitignored — Docker builds it in a dedicated stage. +Edit `src/padelnomics/static/css/input.css` for theme tokens, base styles, +and component classes. -## CI/CD: - Go to GitLab → padelnomics → Settings → CI/CD → Variables and add: +--- - ┌─────────────────┬────────────────────────────┬───────────────────────────────────────────┐ - │ Variable │ Value │ Notes │ - ├─────────────────┼────────────────────────────┼───────────────────────────────────────────┤ - │ SSH_PRIVATE_KEY │ Your ed25519 private key │ Mask it, type "Variable" │ - ├─────────────────┼────────────────────────────┼───────────────────────────────────────────┤ - │ DEPLOY_HOST │ Your Hetzner server IP │ e.g. 1.2.3.4 │ - ├─────────────────┼────────────────────────────┼───────────────────────────────────────────┤ - │ DEPLOY_USER │ SSH username on the server │ e.g. deploy or root │ - ├─────────────────┼────────────────────────────┼───────────────────────────────────────────┤ - │ SSH_KNOWN_HOSTS │ Server host key │ Run ssh-keyscan $YOUR_SERVER_IP to get it │ - └─────────────────┴────────────────────────────┴───────────────────────────────────────────┘ +## Testing Each Feature Locally - And on the server side (one-time setup): - 1. Add the matching public key to ~/.ssh/authorized_keys for the deploy user - 2. Clone the repo to /opt/padelnomics - 3. Create .env from padelnomics/.env.example with production values - 4. chmod +x deploy.sh && ./deploy.sh for the first deploy - 5. Point NPM to port 5000 +### Authentication + +1. Go to http://localhost:5000/auth/login +2. **Dev shortcut**: http://localhost:5000/auth/dev-login?email=test@example.com + — instant login, no email needed (DEBUG mode only) +3. Magic links: submit any email → link prints to console (no Resend key needed) + +### Financial Planner + +1. Log in → http://localhost:5000/planner/ +2. Create a scenario, fill in parameters, click Calculate +3. View results across tabs (Investment, Revenue, Cash Flow, Metrics) + +### Business Plan PDF Export + +1. Create at least one scenario with calculated results +2. Click "Export Business Plan (PDF)" in the planner sidebar, or go to + http://localhost:5000/planner/export +3. **Without Paddle**: checkout will fail (no products in DB). To test the + PDF generation directly, you can: + - Insert a test export record in the DB: + ```bash + uv run python -c " + import sqlite3 + conn = sqlite3.connect('data/app.db') + conn.execute('''INSERT INTO business_plan_exports + (user_id, scenario_id, language, status) + VALUES (1, 1, 'en', 'pending')''') + conn.commit() + print('Export record created with id:', conn.execute('SELECT last_insert_rowid()').fetchone()[0]) + " + ``` + - Then visit http://localhost:5000/planner/export (the generating page + will show). The worker generates the PDF if running. +4. **With Paddle sandbox**: complete checkout → webhook triggers worker → + PDF generated → download link appears + +### Quote Request (Lead Submission) + +1. Go to http://localhost:5000/leads/quote +2. Walk through the 9-step wizard +3. If logged in: lead submitted directly +4. If guest: verification email prints to console → click the link → lead + goes live + +### Supplier Directory + +1. http://localhost:5000/directory/ — full-text search, tier-based ordering +2. Click a supplier → profile page at `/directory/` +3. Click tracking routes: `/directory//website` and `/directory//quote` + redirect to supplier website / quote form + +### Supplier Signup + Dashboard + +1. Visit http://localhost:5000/suppliers — the "For Suppliers" landing page +2. Click "Get Started" on a plan → redirects to `/suppliers/signup` +3. Walk through the 4-step HTMX wizard (plan → boosts → credits → checkout) +4. **Without Paddle**: the final checkout step will fail. To simulate a + signed-up supplier, insert test data: + ```bash + uv run python -c " + import sqlite3 + conn = sqlite3.connect('data/app.db') + # Update an existing supplier to be claimed by user 1 with growth tier + conn.execute('''UPDATE suppliers SET + claimed_by = 1, tier = 'growth', credit_balance = 30 + WHERE id = 1''') + conn.commit() + print('Supplier 1 claimed by user 1 with growth tier') + " + ``` +5. Log in as that user → http://localhost:5000/suppliers/dashboard +6. Browse tabs: Overview, Lead Feed, My Listing, Boost & Upsells +7. Lead Feed: unlock leads (costs credits), filter by heat/country/timeline +8. My Listing: edit profile fields, upload logo +9. Boosts: purchase boosts and credit packs (needs Paddle for actual checkout) + +### Admin Panel + +1. Go to http://localhost:5000/admin +2. Log in with password: `admin` (default in dev) +3. **Dashboard**: user stats, lead funnel, supplier stats, task queue health +4. **Leads** (`/admin/leads`): filter by status/heat/country, view detail, + update status, forward to supplier +5. **Suppliers** (`/admin/suppliers`): filter by tier/country/name, view detail + with credit ledger + boost history, adjust credits, change tier +6. **Feedback** (`/admin/feedback`): view all feedback submissions +7. **Users** (`/admin/users`): search, impersonate users +8. **Tasks** (`/admin/tasks`): view/retry/delete background tasks + +### Feedback Widget + +1. Click the "Feedback" button in the top navbar (visible on every page) +2. Type a message → Send +3. View submissions at http://localhost:5000/admin/feedback + +### Seeding Test Data + +The database starts empty. To populate it with test data for development: + +```bash +uv run python -c " +import sqlite3, json +conn = sqlite3.connect('data/app.db') + +# Create a test supplier +conn.execute('''INSERT OR IGNORE INTO suppliers + (name, slug, category, country_code, city, tier, credit_balance, + short_description, website, contact_name, contact_email) + VALUES + ('Padel Pro Courts', 'padel-pro-courts', 'court_manufacturer', 'DE', 'Munich', + 'free', 0, 'Premium padel court manufacturer', 'https://example.com', + 'Max Mueller', 'max@example.com')''') + +# Create a test lead +conn.execute('''INSERT OR IGNORE INTO lead_requests + (lead_type, facility_type, court_count, glass_type, country, location, + timeline, budget_estimate, heat_score, credit_cost, status, + contact_name, contact_email, contact_phone, stakeholder_type, + verified_at, build_context, financing_status) + VALUES + ('quote', 'indoor_rent', 6, 'panoramic', 'Germany', 'Berlin', + '3-6 months', 450000, 'hot', 35, 'new', + 'John Doe', 'john@example.com', '+49123456', 'investor', + datetime('now'), 'lease_signed', 'loan_approved')''') + +conn.commit() +print('Test data seeded') +" +``` + +--- + +## Architecture Overview + +``` +padelnomics/src/padelnomics/ +├── app.py # Application factory, blueprint registration +├── core.py # Config, database, email, CSRF, rate limiting +├── credits.py # Credit system (balance, ledger, unlock) +├── businessplan.py # WeasyPrint PDF generation engine +├── worker.py # SQLite-based background task queue +├── migrations/ +│ ├── schema.sql # Full schema (source of truth for fresh DBs) +│ ├── migrate.py # Migration runner +│ └── versions/ # Incremental migrations (0001-0008) +├── scripts/ +│ └── setup_paddle.py # Create Paddle products + write IDs to DB +├── templates/ +│ ├── base.html # Layout: nav, footer, HTMX, Paddle.js, Umami +│ └── businessplan/ # PDF templates (plan.html, plan.css) +├── static/ # CSS, JS, images +├── admin/ # Admin panel (dashboard, users, leads, suppliers, feedback) +├── auth/ # Magic link auth, dev-login +├── billing/ # Paddle checkout, webhooks, subscription management +├── dashboard/ # User dashboard +├── directory/ # Supplier directory (FTS5 search, profiles) +├── leads/ # Quote request wizard +├── planner/ # Financial planner + PDF export routes +├── public/ # Landing, marketing, legal, feedback endpoint +└── suppliers/ # Supplier signup wizard, dashboard (4 tabs), lead feed +``` + +### Key Patterns + +- **HTMX everywhere**: server renders HTML partials, HTMX swaps them. No + client-side state management. Forms use `hx-post`, filters use `hx-get`. +- **Paddle.js overlay checkout**: server returns JSON `{items, customData, + settings}`, frontend calls `Paddle.Checkout.open()`. Webhook handles the rest. +- **Credit system**: heat-based pricing (hot=35, warm=20, cool=8). Suppliers + unlock leads by spending credits. Ledger-based balance tracking. +- **SQLite + aiosqlite**: single-file database with WAL mode. Background + worker uses same DB file. +- **Blueprints**: each domain has its own blueprint with template folder. + +--- + +## Docker (Production) + +```bash +docker compose up -d # app + worker + scheduler + litestream +docker compose logs -f app # tail logs +``` + +## CI/CD + +Go to GitLab → padelnomics → Settings → CI/CD → Variables and add: + +| Variable | Value | Notes | +|----------|-------|-------| +| SSH_PRIVATE_KEY | Your ed25519 private key | Mask it, type "Variable" | +| DEPLOY_HOST | Your Hetzner server IP | e.g. 1.2.3.4 | +| DEPLOY_USER | SSH username on the server | e.g. deploy or root | +| SSH_KNOWN_HOSTS | Server host key | Run `ssh-keyscan $YOUR_SERVER_IP` | + +Server-side one-time setup: +1. Add the matching public key to `~/.ssh/authorized_keys` for the deploy user +2. Clone the repo to `/opt/padelnomics` +3. Create `.env` from `padelnomics/.env.example` with production values +4. `chmod +x deploy.sh && ./deploy.sh` for the first deploy +5. Point reverse proxy to port 5000 diff --git a/padelnomics/src/padelnomics/templates/base.html b/padelnomics/src/padelnomics/templates/base.html index cc4b3de..bb67988 100644 --- a/padelnomics/src/padelnomics/templates/base.html +++ b/padelnomics/src/padelnomics/templates/base.html @@ -73,7 +73,7 @@

Send Feedback

-