inboxer/README.md
cclohmar ca970104ee chore: initial commit — ExpenseFlow AI-Powered Expense Tracker
- 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
2026-05-29 19:43:30 +00:00

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
Email 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:

  1. Image saved to ./storage/{uuid}.jpg
  2. Sent to DeepSeek Vision API for OCR
  3. AI returns: {amount, currency, merchant, category, date}
  4. Pre-filled form rendered for user review/edit
  5. 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:

  1. Visit http://localhost:8080 — see email input
  2. Enter email — receive 6-digit OTP in inbox
  3. Enter OTP — redirected to dashboard (empty)
  4. Create event — name it "Test Event"
  5. Click event — see expense list (empty) + Capture Receipt button
  6. Upload receipt — snap/take a photo → AI extracts amount, merchant, category, date
  7. Review & save — pre-filled form, click Save → expense appears in list
  8. File event — click File Event, enter email, pick PDF → report sent, event closes
  9. Verify — event shows as closed on dashboard
  10. 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

  • Installablemanifest.json with 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