update README with local testing guide, fix feedback placeholder, sync .env.example

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-17 22:29:40 +01:00
parent f29c56cbaa
commit 7d3aa3141d
3 changed files with 289 additions and 29 deletions

View File

@@ -12,16 +12,20 @@ DATABASE_PATH=data/app.db
MAGIC_LINK_EXPIRY_MINUTES=15 MAGIC_LINK_EXPIRY_MINUTES=15
SESSION_LIFETIME_DAYS=30 SESSION_LIFETIME_DAYS=30
# Email (Resend) # Email (Resend) — leave blank for dev (emails print to console)
RESEND_API_KEY= RESEND_API_KEY=
EMAIL_FROM=hello@padelnomics.io EMAIL_FROM=hello@padelnomics.io
ADMIN_EMAIL=leads@padelnomics.io ADMIN_EMAIL=leads@padelnomics.io
# Paddle # Paddle — leave blank to skip checkout (overlay won't initialize)
PADDLE_API_KEY= PADDLE_API_KEY=
PADDLE_CLIENT_TOKEN=
PADDLE_WEBHOOK_SECRET= PADDLE_WEBHOOK_SECRET=
PADDLE_PRICE_STARTER= PADDLE_ENVIRONMENT=sandbox
PADDLE_PRICE_PRO=
# Umami — leave blank for dev (analytics tracking disabled)
UMAMI_API_URL=https://umami.padelnomics.io
UMAMI_API_TOKEN=
# Rate limiting # Rate limiting
RATE_LIMIT_REQUESTS=100 RATE_LIMIT_REQUESTS=100

View File

@@ -2,39 +2,295 @@
Plan, finance, and build your padel business. 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 ```bash
make css-watch # rebuild on file changes (dev) make css-watch # rebuild on file changes (dev)
make css-build # one-off minified build (CI/Docker) 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:
┌─────────────────┬────────────────────────────┬───────────────────────────────────────────┐ ## Testing Each Feature Locally
│ 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 │
└─────────────────┴────────────────────────────┴───────────────────────────────────────────┘
And on the server side (one-time setup): ### Authentication
1. Add the matching public key to ~/.ssh/authorized_keys for the deploy user
2. Clone the repo to /opt/padelnomics 1. Go to http://localhost:5000/auth/login
3. Create .env from padelnomics/.env.example with production values 2. **Dev shortcut**: http://localhost:5000/auth/dev-login?email=test@example.com
4. chmod +x deploy.sh && ./deploy.sh for the first deploy — instant login, no email needed (DEBUG mode only)
5. Point NPM to port 5000 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/<slug>`
3. Click tracking routes: `/directory/<slug>/website` and `/directory/<slug>/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

View File

@@ -73,7 +73,7 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="page_url" id="feedback-page-url"> <input type="hidden" name="page_url" id="feedback-page-url">
<p style="font-size:0.8125rem;font-weight:600;color:#1E293B;margin:0 0 8px">Send Feedback</p> <p style="font-size:0.8125rem;font-weight:600;color:#1E293B;margin:0 0 8px">Send Feedback</p>
<textarea name="message" rows="3" required placeholder="What's on your mind?" <textarea name="message" rows="3" required placeholder="Ideas to improve this page..."
style="width:100%;border:1px solid #E2E8F0;border-radius:6px;padding:8px;font-size:0.8125rem;font-family:inherit;resize:vertical"></textarea> style="width:100%;border:1px solid #E2E8F0;border-radius:6px;padding:8px;font-size:0.8125rem;font-family:inherit;resize:vertical"></textarea>
<button type="submit" class="btn" style="width:100%;margin-top:8px;font-size:0.8125rem;padding:8px">Send</button> <button type="submit" class="btn" style="width:100%;margin-top:8px;font-size:0.8125rem;padding:8px">Send</button>
</form> </form>