- 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
278 lines
8.5 KiB
Markdown
278 lines
8.5 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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](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.
|
|
|
|
```mermaid
|
|
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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```sql
|
|
-- 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.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
|