AI-Powered Expense Tracker — Go + HTMX + SQLite
Find a file
cclohmar 4895b3d608 feat: add KES support + base currency + exchange rate for expense claims
- Added KES and 12+ additional currencies to receipt form
- Events now have base_currency (claim currency) and exchange_rate fields
- Receipts show original amount + auto-computed converted amount
- Converted amounts stored per expense in database
- CSV and PDF reports include both original and converted amounts
- Dashboard shows claim currency per event card
2026-05-30 12:23:31 +00:00
internal feat: add KES support + base currency + exchange rate for expense claims 2026-05-30 12:23:31 +00:00
static chore: initial commit — ExpenseFlow AI-Powered Expense Tracker 2026-05-29 19:43:30 +00:00
templates feat: add KES support + base currency + exchange rate for expense claims 2026-05-30 12:23:31 +00:00
.env.example chore: initial commit — ExpenseFlow AI-Powered Expense Tracker 2026-05-29 19:43:30 +00:00
.gitignore chore: comprehensive README with binary distribution docs + build-all.sh 2026-05-29 19:54:20 +00:00
build-all.sh chore: comprehensive README with binary distribution docs + build-all.sh 2026-05-29 19:54:20 +00:00
go.mod chore: initial commit — ExpenseFlow AI-Powered Expense Tracker 2026-05-29 19:43:30 +00:00
go.sum chore: initial commit — ExpenseFlow AI-Powered Expense Tracker 2026-05-29 19:43:30 +00:00
main.go chore: initial commit — ExpenseFlow AI-Powered Expense Tracker 2026-05-29 19:43:30 +00:00
README.md chore: comprehensive README with binary distribution docs + build-all.sh 2026-05-29 19:54:20 +00:00

ExpenseFlow — AI-Powered Expense Tracker

A production-ready, mobile-first Progressive Web App (PWA) that uses passwordless email OTP login, event-based expense tracking, AI receipt extraction (DeepSeek Vision), and event filing (CSV/PDF via email).

Tech Stack: Go 1.22+ · HTMX · SQLite · DeepSeek Vision API · PWA


📦 Quick Start — Binary Distribution

Pre-compiled binaries are available for download from the Releases page.

Download & Run

# Linux (amd64)
curl -L -o expenseflow https://git.lohmar.co.uk/cclohmar/ExpenseFlow/releases/download/v1.0.0/expenseflow-linux-amd64
chmod +x expenseflow
./expenseflow

# Linux (arm64) — Raspberry Pi, etc.
curl -L -o expenseflow https://git.lohmar.co.uk/cclohmar/ExpenseFlow/releases/download/v1.0.0/expenseflow-linux-arm64
chmod +x expenseflow
./expenseflow

# macOS (Intel)
curl -L -o expenseflow https://git.lohmar.co.uk/cclohmar/ExpenseFlow/releases/download/v1.0.0/expenseflow-darwin-amd64
chmod +x expenseflow
./expenseflow

# macOS (Apple Silicon M1/M2/M3)
curl -L -o expenseflow https://git.lohmar.co.uk/cclohmar/ExpenseFlow/releases/download/v1.0.0/expenseflow-darwin-arm64
chmod +x expenseflow
./expenseflow

The server starts on http://localhost:8080 by default. Set PORT=3000 to change the port.

Note: The binary embeds no configuration. You must create a .env file (see Configuration below) in the same directory you run the binary from.


🔧 Building from Source

Prerequisites

  • Go 1.22+Download

  • GCC (CGO is required for the SQLite driver)

    # Debian/Ubuntu
    sudo apt install build-essential
    
    # macOS
    xcode-select --install
    
    # Alpine
    apk add build-base
    

Clone & Build

git clone https://git.lohmar.co.uk/cclohmar/ExpenseFlow.git
cd ExpenseFlow

# Build for your current platform
go build -o expenseflow .

# The binary is now ready: ./expenseflow

Cross-Compilation

The binary uses CGO (for mattn/go-sqlite3), so cross-compilation requires a cross-compiler.

# Linux amd64
GOOS=linux GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-linux-gnu-gcc go build -o expenseflow-linux-amd64 .

# Linux arm64 (Raspberry Pi, etc.)
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc go build -o expenseflow-linux-arm64 .

# macOS Intel
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 CC=o64-clang go build -o expenseflow-darwin-amd64 .

# macOS Apple Silicon
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 CC=aarch64-apple-darwin-clang go build -o expenseflow-darwin-arm64 .

Tip: For macOS cross-compilation from Linux, use osxcross. For ARM Linux, install gcc-aarch64-linux-gnu.


⚙️ Configuration

Copy .env.example to .env and fill in your credentials:

cp .env.example .env
Variable Required Default Description
SMTP_HOST Yes* SMTP server hostname
SMTP_PORT Yes* SMTP server port (usually 587)
SMTP_USER Yes* SMTP username
SMTP_PASS Yes* SMTP password
DEEPSEEK_API_KEY Yes* DeepSeek Vision API key
BASE_URL No http://localhost:8080 Public URL for email links
PORT No 8080 HTTP server port

* The app will start without SMTP/DeepSeek configured, but OTP emails and AI extraction will not work.

Getting Credentials

  • DeepSeek Vision API: Sign up at platform.deepseek.com and create an API key.
  • SMTP: Use any SMTP provider. The default config points to an OX hosting SMTP server.

🚀 Usage

1. Start the Server

./expenseflow

2. Open in Browser

Navigate to http://localhost:8080

3. Full Acceptance Flow

1. Enter your email → click "Send Verification Code"
2. Check your inbox for the 6-digit OTP code
3. Enter the OTP → click "Verify Code"
4. Create an event (e.g., "WebSummit 2026")
5. Click "Add Expenses" on the event card
6. Click "Capture Receipt" → take a photo or select an image
7. AI extracts: amount, merchant, category, date → pre-fills the form
8. Review and click "Save Expense"
9. Click "File Event" → enter recipient email → choose CSV or PDF
10. Report is emailed and event status changes to "closed"
11. Click "Reopen" to re-open a closed event

📡 API Reference

Public Endpoints (no authentication)

Method Path Description
GET / Landing page with email login form
POST /request-otp Request a 6-digit OTP code (sends email)
POST /verify-otp Verify OTP code and create session
POST /logout Clear session and redirect to login
Method Path Description
GET /dashboard Event dashboard
POST /events Create a new event
GET /events/{id}/expenses View event with expense list
PUT /events/{id}/reopen Reopen a closed event
POST /expenses/upload Upload receipt image (multipart)
POST /expenses Save expense from form data
POST /events/{id}/file Generate report (CSV/PDF) and email it

Static Files

Path Description
/static/css/style.css Application stylesheet
/static/icons/icon-192.png PWA icon (192×192)
/static/icons/icon-512.png PWA icon (512×512)
/manifest.json PWA manifest
/sw.js Service worker
/storage/{filename} Uploaded receipt images

🏗️ Project Structure

ExpenseFlow/
├── main.go                        # Entry point, router, middleware, server
├── go.mod / go.sum                # Go module definition
├── .env.example                   # Environment variable template
├── README.md                      # This file
├── internal/
│   ├── database/db.go             # SQLite init, auto-migration, 11 query functions
│   ├── auth/
│   │   ├── otp.go                 # 6-digit OTP generation + 3-fail lockout
│   │   └── session.go             # In-memory session store (crypto tokens, 24h TTL)
│   ├── handlers/
│   │   ├── auth.go                # Auth endpoints + middleware
│   │   ├── events.go              # Event CRUD + dashboard
│   │   ├── expenses.go            # Receipt upload, AI extraction, save
│   │   └── file.go                # CSV/PDF generation + email filing
│   ├── ai/deepseek.go             # DeepSeek Vision API client
│   ├── email/smtp.go              # SMTP sender (OTP + attachments)
│   └── utils/uuid.go              # UUID generation
├── templates/
│   ├── index.html                 # Landing page
│   ├── dashboard.html             # Event cards + create form
│   ├── event_expenses.html        # Event detail + capture + filing
│   ├── receipt_form.html          # AI-prefilled edit form
│   └── expense_list.html          # HTMX expense list fragment
├── static/
│   ├── css/style.css              # Mobile-first responsive CSS (1845 lines)
│   ├── manifest.json              # PWA manifest
│   ├── sw.js                      # Service worker
│   └── icons/                     # PWA placeholder icons
└── storage/                       # Uploaded receipts (created at runtime)

🧩 Features in Detail

🔐 Passwordless OTP Authentication

  • Email-based 6-digit code, 5-minute expiry
  • Auto-creates user account on first login
  • 3 failed attempts trigger a 1-minute cooldown
  • HTTP-only session cookie (SameSite=Lax, 24h TTL)
  • No passwords to store or forget

📋 Event-Based Expense Tracking

  • Group expenses into events (trips, conferences, months)
  • Open/closed lifecycle with reopen support
  • Dashboard with event cards showing name, status, and creation date

🤖 AI Receipt Extraction

  • Upload receipt images (JPEG/PNG, max 10 MB)
  • DeepSeek Vision API extracts: amount, currency, merchant, category, date
  • Editable pre-filled form on failure or success
  • Images stored locally in ./storage/

📧 Email Reporting

  • Generate CSV (via encoding/csv) or PDF (via gofpdf)
  • Automatic email delivery via SMTP with file attachment
  • Event auto-closes after successful filing
  • Multipart MIME support with proper content headers

📱 Progressive Web App

  • Installable on mobile and desktop (manifest.json)
  • Offline shell caching (service worker)
  • Camera capture for receipts (capture="environment")
  • Theme color: #10b981 (emerald green)
  • Responsive design: 320px → 768px → 1024px+

🗄️ Database Schema (SQLite)

Auto-created on first run — 4 tables with foreign keys:

CREATE TABLE users (
    id TEXT PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE auth_otps (
    email TEXT PRIMARY KEY,
    otp_code TEXT NOT NULL,
    expires_at DATETIME NOT NULL
);

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)
);

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
);

🐳 Docker

FROM golang:1.22-alpine AS builder
RUN apk add --no-cache build-base
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o expenseflow .

FROM alpine:3.19
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=builder /app/expenseflow .
COPY --from=builder /app/templates ./templates
COPY --from=builder /app/static ./static
COPY --from=builder /app/.env.example ./.env.example
EXPOSE 8080
CMD ["./expenseflow"]
docker build -t expenseflow .
docker run -p 8080:8080 -v $(pwd)/.env:/app/.env -v $(pwd)/storage:/app/storage expenseflow

🔒 Security

Area Implementation
Sessions Cryptographically random tokens (32 bytes, hex-encoded), in-memory store, 24h TTL
OTP 6-digit codes from crypto/rand, 5-minute expiry, 3-fail lockout (1 minute)
Cookies HTTP-only, SameSite=Lax, path restricted
SQL Injection Parameterized queries on all database operations
File Upload Magic byte validation (JPEG/PNG), 10 MB limit, sanitized filenames (UUID)
Credentials All secrets via environment variables only — never hardcoded
HTMX Server-rendered HTML, no client-side data exposure

🧪 Development

Run Tests

go vet ./...
go test ./...

Manual Smoke Test

# Start server
go run main.go &

# Test landing page
curl -s http://localhost:8080/ | head -5

# Test OTP request
curl -s -X POST -d "email=test@example.com" http://localhost:8080/request-otp

# Check database
sqlite3 expenses.db "SELECT * FROM auth_otps;"

📄 License

MIT — Free to use, modify, and distribute.


🙌 Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit changes (git commit -am 'feat: add my feature')
  4. Push (git push origin feature/my-feature)
  5. Open a Pull Request

Built with Go, HTMX, SQLite, and ❤️