NextExpense/README.md

389 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 ❤️*