- Passwordless email OTP authentication - Event-based expense tracking with HTMX UI - AI receipt extraction via DeepSeek Vision API - CSV/PDF report generation with email filing - PWA with service worker and manifest - Mobile-first responsive design - SQLite database with auto-migration
8.5 KiB
8.5 KiB
ExpenseFlow - AI-Powered Expense Tracker
A production-ready, mobile-first Progressive Web App (PWA) for expense management with passwordless email OTP authentication, event-based expense tracking, AI receipt extraction via DeepSeek Vision, and CSV/PDF reporting delivered via email.
Features
- Passwordless OTP Auth — Email-based 6-digit code, no passwords, HTTP-only session cookie
- Event Dashboard — Create events (e.g. "WebSummit 2026"), track status (open/closed), reopen closed events
- AI Receipt Capture — Snap a photo → DeepSeek Vision extracts amount, merchant, category, date → save
- Event Filing — Generate CSV or PDF report → email as attachment → auto-close event
- PWA — Installable on mobile homescreen, offline shell, camera capture for receipts
Tech Stack
| Layer | Technology |
|---|---|
| Backend | Go (net/http + chi router) |
| Frontend | HTMX (server-driven UI, partial page updates) |
| Database | SQLite (auto-migrated on startup) |
| AI OCR | DeepSeek Vision API |
| SMTP (OTP delivery + report attachments) | |
| PWA | manifest.json + Service Worker |
Quick Start
Prerequisites
- Go 1.21+
- GCC (required for CGO/SQLite via
mattn/go-sqlite3)
Setup
# Clone the repository
git clone https://github.com/your-org/expenseflow.git
cd expenseflow
# Copy environment configuration
cp .env.example .env
# Run the application
go run main.go
Open http://localhost:8080 in your browser.
Environment Variables
Copy .env.example to .env and configure:
| Variable | Description | Default |
|---|---|---|
SMTP_HOST |
SMTP server hostname | smtp.openxchange.eu |
SMTP_PORT |
SMTP server port | 587 |
SMTP_USER |
SMTP authentication username | post@2-4-h.app |
SMTP_PASS |
SMTP authentication password | — |
DEEPSEEK_API_KEY |
DeepSeek Vision API key | — |
BASE_URL |
Public base URL for absolute links in emails | http://localhost:8080 |
Project Structure
ExpenseFlow/
├── main.go # Entry point, router, static file server
├── go.mod # Module dependencies
├── .env # Credentials (not committed)
├── .env.example # Env template
├── internal/
│ ├── ai/
│ │ └── deepseek.go # DeepSeek Vision API client
│ ├── auth/
│ │ ├── otp.go # OTP generation & validation
│ │ └── session.go # Session management (in-memory store)
│ ├── database/
│ │ └── db.go # SQLite init, queries, auto-migration
│ ├── email/
│ │ └── smtp.go # SMTP email sender (OTP + reports)
│ ├── handlers/
│ │ ├── auth.go # Auth endpoints (login, OTP, verify)
│ │ ├── events.go # Event CRUD + reopen
│ │ ├── expenses.go # Upload, AI extract, save
│ │ └── file.go # CSV/PDF generation, email delivery
│ └── utils/
│ └── uuid.go # UUID generation helper
├── static/
│ ├── css/
│ │ └── style.css # Application styles
│ ├── icons/ # PWA icons (192x192, 512x512)
│ ├── manifest.json # PWA manifest
│ └── sw.js # Service Worker (offline cache)
├── templates/ # HTMX templates (server-rendered HTML)
│ ├── layout.html
│ ├── index.html
│ ├── dashboard.html
│ ├── event_expenses.html
│ ├── receipt_form.html
│ └── expense_list.html
└── storage/ # Uploaded receipt images (created at runtime)
API Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/ |
Landing page — email input for OTP login |
POST |
/request-otp |
Request a 6-digit OTP code (sent via email) |
POST |
/verify-otp |
Verify OTP and create session cookie |
GET |
/dashboard |
User dashboard — list of events |
POST |
/events |
Create a new expense event |
GET |
/events/{id}/expenses |
View expenses for an event |
POST |
/expenses/upload |
Upload receipt image → AI extraction |
POST |
/expenses |
Save an expense record |
POST |
/events/{id}/file |
File report (CSV/PDF) and close event |
PUT |
/events/{id}/reopen |
Reopen a closed event |
Feature Walkthrough
1. Passwordless OTP Auth
Log in with just your email — no passwords needed.
sequenceDiagram
User->>Browser: Enter email
Browser->>Server: POST /request-otp (email)
Server->>Email: Send 6-digit code
User->>Browser: Enter code
Browser->>Server: POST /verify-otp (email, code)
Server->>Browser: Set session cookie, redirect to /dashboard
2. Event Dashboard
- Open events — click to view expenses and capture receipts
- Closed events — show a Reopen button (
PUT /events/{id}/reopen) - Each card shows: event name, status badge, created date
3. AI Receipt Capture
# Upload a receipt image
curl -X POST http://localhost:8080/expenses/upload \
-H "Cookie: session_token=..." \
-F "image=@receipt.jpg"
What happens:
- Image saved to
./storage/{uuid}.jpg - Sent to DeepSeek Vision API for OCR
- AI returns:
{amount, currency, merchant, category, date} - Pre-filled form rendered for user review/edit
- On save → expense stored, list updated
4. Event Filing
Generate a CSV or PDF report and email it:
# File event as PDF
curl -X POST http://localhost:8080/events/123/file \
-H "Cookie: session_token=..." \
-d "email=user@example.com&format=pdf"
- Creates report from all event expenses
- Sends email with attachment
- Event status changes to
closed
Acceptance Test Scenario
Run through the full flow manually:
- Visit
http://localhost:8080— see email input - Enter email — receive 6-digit OTP in inbox
- Enter OTP — redirected to dashboard (empty)
- Create event — name it "Test Event"
- Click event — see expense list (empty) + Capture Receipt button
- Upload receipt — snap/take a photo → AI extracts amount, merchant, category, date
- Review & save — pre-filled form, click Save → expense appears in list
- File event — click File Event, enter email, pick PDF → report sent, event closes
- Verify — event shows as closed on dashboard
- Reopen — click Reopen → badge changes back to open
Database
SQLite database (expenses.db) created and auto-migrated on first startup.
Tables
-- Users
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- OTP codes
CREATE TABLE auth_otps (
email TEXT PRIMARY KEY,
otp_code TEXT NOT NULL,
expires_at DATETIME NOT NULL
);
-- Events (expense containers)
CREATE TABLE events (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT CHECK(status IN ('open','closed')) DEFAULT 'open',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
);
-- Expenses (receipt records)
CREATE TABLE expenses (
id TEXT PRIMARY KEY,
event_id TEXT NOT NULL,
amount REAL NOT NULL,
currency TEXT NOT NULL,
merchant TEXT NOT NULL,
category TEXT NOT NULL,
description TEXT,
date TEXT NOT NULL,
image_path TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(event_id) REFERENCES events(id) ON DELETE CASCADE
);
PWA Features
- Installable —
manifest.jsonwith standalone display, theme color#10b981 - Offline shell — Service Worker caches core assets (CSS, HTMX, shell) on install
- Camera capture —
<input type="file" accept="image/*" capture="environment">for mobile receipt snapping
Service Worker
- Cache name:
expenseflow-v1 - Install: caches
/, CSS, HTMX library - Fetch: cache-first for static assets, network-only for API calls
Security
| Area | Implementation |
|---|---|
| Session | In-memory store, 24h TTL, HTTP-only cookie |
| OTP | 5-minute expiry, 3-attempt cooldown |
| Uploads | Max 10 MB, JPEG/PNG only, sanitized filenames |
| AI fallback | Editable empty form if DeepSeek API fails |
License
MIT