# 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](https://git.lohmar.co.uk/cclohmar/ExpenseFlow/releases) page. ### Download & Run ```bash # 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](#-configuration) below) in the same directory you run the binary from. --- ## 🔧 Building from Source ### Prerequisites - **Go 1.22+** — [Download](https://go.dev/dl/) - **GCC** (CGO is required for the SQLite driver) ```bash # Debian/Ubuntu sudo apt install build-essential # macOS xcode-select --install # Alpine apk add build-base ``` ### Clone & Build ```bash 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. ```bash # 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](https://github.com/tpoechtrager/osxcross). For ARM Linux, install `gcc-aarch64-linux-gnu`. --- ## ⚙️ Configuration Copy `.env.example` to `.env` and fill in your credentials: ```bash 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](https://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 ```bash ./expenseflow ``` ### 2. Open in Browser Navigate to [http://localhost:8080](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 | ### Protected Endpoints (require session cookie) | 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: ```sql 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 ```dockerfile 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"] ``` ```bash 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 ```bash go vet ./... go test ./... ``` ### Manual Smoke Test ```bash # 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 ❤️*