Files

6.9 KiB
Raw Permalink Blame History

Vela Platform v2

Single FastAPI application that powers the Vela business — a home server installation service in Casablanca, Morocco.

Built: June 26, 2026
Deployed: Proxmox CT 124 @ 192.168.1.244


Architecture

┌─────────────────────────────────────────┐
│  nginx :80                              │
│  ├── /          → Marketing site        │
│  ├── /admin     → Admin P&L dashboard   │
│  ├── /backup    → Backup explainer      │
│  ├── /static/*  → Logos, assets         │
│  └── /api/*     → JSON API              │
│       │                                 │
│  uvicorn :8000 (systemd: vela.service)  │
│  ├── main.py    — FastAPI app           │
│  ├── models.py  — SQLAlchemy models     │
│  ├── database.py — SQLite WAL mode      │
│  └── auth.py    — JWT auth              │
│                                         │
│  vela.db (SQLite)                       │
└─────────────────────────────────────────┘

One box. One app. One database. No microservices.


What It Does

Public Marketing Site (/)

  • Bilingual FR/EN with URL-based language toggle (?lang=fr / ?lang=en)
  • Dark/light mode toggle (persisted in localStorage)
  • Hero section with value proposition
  • Problem/solution section
  • Live pricing cards pulled from API
  • Immich & Nextcloud sections with real SVG logos
  • "How it works" — 3-step visual
  • WhatsApp CTA buttons (all pointing to +212 725-569519)
  • Footer with barely-visible "Admin" link (30% opacity)

Backup Explainer (/backup)

  • Explains backups to non-technical users
  • Simple vs redundant backup comparison
  • "The golden rule: if it only exists in one place, it doesn't really exist"
  • Bilingual

Admin Panel (/admin)

  • JWT login (username: oimwiodev, password: kxdr781020)
  • Token stored in localStorage, verified via /api/verify
  • 4 tabs: Dashboard, Tiers, Transactions, Expenses

Dashboard

  • Real-time P&L cards: Revenue, COGS, Gross Profit, Margin %, Expenses, Net Profit
  • Monthly breakdown table with month filter
  • Formula: Revenue COGS Expenses = Net Profit (not the fake v1 math)

Tiers

  • 4 tiers: Rif, Rif+, Atlas, Atlas+
  • Component breakdown with unit costs
  • Inline edit: tier name, sell price, component costs
  • Toggle active/inactive
  • Cost price auto-calculated from components
  • Margin % shown per tier

Transactions

  • Add manual sale (select tier → snapshots sell price + cost at time of sale)
  • Filter by month
  • Delete with confirm + undo timer:
    • 1st click: "Sure?" (4s timeout)
    • 2nd click: deletes, row fades, 8s undo window

Expenses

  • Add/delete expenses with month and category
  • Monthly expense templates (auto-generate from templates)
  • Soft delete (deleted_at timestamp)

API Endpoints

Public (no auth)

Method Path Description
GET /api/public/pricing Active tiers: name, description, sell_price
GET /api/public/services/{id} Single tier details + components

Auth

Method Path Description
POST /api/auth/login Login (username + password) → JWT
POST /api/auth/logout Clear cookie
GET /api/verify Verify JWT, returns email

Admin (JWT required)

Method Path Description
GET /api/pnl P&L summary + monthly breakdown
GET /api/services All tiers with components
PUT /api/services/{id} Update tier (name, price, active, etc.)
PUT /api/services/{id}/toggle Toggle active/inactive
PUT /api/services/{id}/components/{cid} Update component cost/qty
GET /api/transactions List transactions (optional ?month=)
POST /api/transactions Create sale (snapshots prices)
DELETE /api/transactions/{id} Delete transaction
GET /api/expenses List expenses
POST /api/expenses Add expense
DELETE /api/expenses/{id} Soft-delete expense
GET /api/expense-templates List recurring templates
POST /api/expenses/generate-month Generate from templates

Database Schema

6 tables in SQLite (vela.db):

Table Purpose
users Single admin (email, bcrypt password)
services 4 tiers (name, sell_price, cost_price, active, sort_order)
service_components Per-tier parts (name, unit_cost, quantity)
transactions Frozen sales with price/cost snapshots, unique (source_type, source_id)
expenses Monthly costs with soft delete
expense_templates Recurring expense patterns

Key design: transactions freeze sell_price and cost_price at time of sale. If you change an SSD cost later, old sales don't change. Real P&L math.

SQLite pragmas: journal_mode=WAL, foreign_keys=ON, busy_timeout=5000


Deployment

Server: Proxmox CT 124 (Debian 12)
IP: 192.168.1.244
Service: systemctl restart vela
Files: /opt/vela/
DB: /opt/vela/vela.db

Update workflow

# 1. Edit files locally
# 2. Package and deploy
cd ~/src/vela-platform
tar czf /tmp/vela-update.tar.gz main.py templates/
sshpass -p 'PW' scp /tmp/vela-update.tar.gz root@192.168.1.109:/tmp/
sshpass -p 'PW' ssh root@192.168.1.109 "
  pct push 124 /tmp/vela-update.tar.gz /tmp/vela-update.tar.gz &&
  pct exec 124 -- bash -c 'cd /opt/vela && tar xzf /tmp/vela-update.tar.gz && systemctl restart vela'
"

Public access

Point Nginx Proxy Manager at 192.168.1.244:80 for your domain.


Design Decisions

  • No Jinja2 — hit a cache bug (unhashable type: 'dict'). Switched to FileResponse serving raw HTML. All dynamic content via fetch() to JSON APIs.
  • No framework — vanilla HTML/CSS/JS. No React, no Vue, no build step.
  • CSS variables — dark/light mode is a single data-theme attribute. Zero hardcoded hex colors in JS.
  • Single admin — no user management. One account. Simple.
  • Public API is read-only — pricing only. No costs, no margins, no internals leak.

WhatsApp Sales Bot

A separate Hermes agent with a custom SOUL.md handles WhatsApp conversations. The bot:

  • Answers questions in French/Darija/English
  • Fetches live pricing via curl http://192.168.1.244/api/public/pricing
  • Follows a 4-step sales process: understand → recommend → handle objections → close
  • Never hardcodes prices — always checks the API
  • Personality: calm, direct, short messages, no corporate tone

SOUL.md at /tmp/vela-sales-bot-soul.md


What Was Removed (v1)

The old Vela had two separate LXCs (CT125 + CT126) with three Python services (two FastAPI apps + nginx, two auth systems, two databases). All nuked. Replaced with this single monolith.