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

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