# 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** — `` 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