Commit graph

49 commits

Author SHA1 Message Date
4593dd1f66 refactor: replace currency dropdowns with simple text inputs
- Currency ISO3 dropdowns replaced with <input type="text" maxlength="3">
- Users can type any currency code (KES, USD, EUR, etc.)
- No need to maintain a long list of currencies
- Exchange rate is user-defined so validation is unnecessary
- All currency inputs have text-transform: uppercase for consistency
2026-06-01 23:14:16 +00:00
e49b184be8 fix: install.sh installs git, curl, python3 if missing
- Checks for git before clone/pull — installs via apt/dnf/apk
- Checks for curl before downloading release binary
- Checks for python3 before parsing release JSON
- Multi-distro support (apt-get, dnf, apk)
2026-05-31 03:02:01 +00:00
92f070440f refactor: implement best-practice recommendations from code review
MUST FIX:
- M1: Fixed ignored errors in AI providers (json.Marshal, http.NewRequest, json.Unmarshal)
- M2: Template cache — pre-parse all templates once at startup, reuse via getTemplate()
- M3: Fixed silent ParseFloat error fallbacks — now returns HTTP 400 on invalid amounts
- M4: Wrapped readFile errors with context (fmt.Errorf with %w)
- M5: Deleted stale llm.go placeholder file
- M6: Renamed utils.New() to utils.NewUUID() for clarity
- M7: Validate current_event_id cookie UUID format, prevent tampering

SHOULD FIX:
- S4: Added utils.Timestamp() helper to replace repeated time.Now().Format() calls
- S6: Added request ID middleware for concurrent request log tracing
- S7: Increased DB pool from 1 to 4 connections (HTMX concurrency)
- S8: Graceful shutdown via http.Server.Shutdown() on SIGINT/SIGTERM
- S9: Storage served behind auth middleware with path traversal check

COULD FIX:
- C2: renderOTPForm uses cached template (not per-request Must)
- C3: CSP pinned to unpkg.com/htmx.org@1.9.10
- C4: Added ReadHeaderTimeout, ReadTimeout, WriteTimeout, IdleTimeout
- C7: PDF generation auto-adds page breaks when content overflows

ADDITIONAL:
- Pass config to AI provider constructors (newGeminiProvider, newOpenAIProvider)
- Value receivers on geminiProvider/openaiProvider (empty structs)
- Added envOrDefault() helper in ai/receipt.go
- Session cleanup goroutine started in main.go
- Removed duplicate imports and unused html/template from handlers
2026-05-31 02:13:07 +00:00
6a902f0b85 chore: remove last DeepSeek reference from main.go header 2026-05-31 02:02:02 +00:00
a7381bde0c fix: final vulnerability sweep — storage auth, security headers, body limits, cookie flags
- Storage route moved behind auth middleware (was publicly accessible)
- Security headers: X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy
- Request body size limit: 10 MB on all endpoints via MaxBytesReader
- Session cookie now sets Secure flag when BASE_URL uses HTTPS
- readFile() returns proper errors for dirs & oversized files (was nil,nil)
- Removed dead DEEPSEEK_API_KEY code from main.go
- Added fmt import to ai/receipt.go for error formatting
2026-05-31 02:01:12 +00:00
e831fcf617 fix: resolve 7 critical security findings from code review
CR-1: Path traversal in createReceiptZip — validate image_path is within storage/
CR-2: Missing authz on EditExpense/UpdateExpense — verify event ownership
CR-3: OTP timing side-channel — use crypto/subtle.ConstantTimeCompare
CR-4: Logout doesn't invalidate session — moved to AuthHandler with Sessions.Delete()
CR-5: OTP reuse race condition — mutex lock around validate+delete
CR-6: Live credentials on disk — removed .env from disk entirely
CR-7: No TLS — documented as expected behind-proxy deployment

Additional:
- Removed stale github.com/expenseflow import path from auth.go
- Made EnvironmentFile optional (prefix with -) so .env is not required
- App runs and starts clean without any .env file
2026-05-31 01:50:08 +00:00
2f26abfb2f docs: update README — remove Ollama references, update config table 2026-05-30 17:44:13 +00:00
9f52198647 fix: remove orphaned fallthrough after ollama removal 2026-05-30 17:40:40 +00:00
3327fd4fac feat: use glm-ocr as default Ollama model (specialized OCR, not a general LLM)
- glm-ocr is a 1.1B parameter model built specifically for OCR
- No reasoning overhead, no thinking field issues
- Faster inference than qwen3.5 on CPU
- Removed old qwen3.5 models (2B + 0.8B) to free ~4GB disk
- Updated install.sh, ollama.go, .env.example defaults
2026-05-30 17:17:06 +00:00
1eca4ae777 fix: qwen3.5 reasoning model outputs to thinking field not content
- ollamaResponse now reads both content and thinking fields
- Falls back to thinking if content is empty
- Install script adds model warm-up prompt to preload into memory
- Warm-up uses OLLAMA_HOST for correct user context
- Makes first real receipt analysis faster
2026-05-30 16:51:43 +00:00
59f73830fb fix: chown /usr/share/ollama after install so ollama user can write to it
- The Ollama installer run via sudo leaves /usr/share/ollama owned by root
- The ollama service runs as the ollama user and needs write access
- Added chown after installation to fix permissions
2026-05-30 16:45:00 +00:00
51c5d9a23e fix: set GOCACHE alongside GOMODCACHE for users without home write access
- Go build cache was trying to write to /app/.cache/go-build (unwritable)
- Now uses /opt/receiptnext/.go/build instead
2026-05-30 16:13:26 +00:00
de29841341 docs: remove sudo from local clone install command 2026-05-30 16:11:18 +00:00
0ab9f1e1be fix: go build module cache permission + README sudo
- Set GOMODCACHE + GOPATH to /opt/receiptnext/.go (writable location)
- Removes sudo from README one-liner — script self-elevates
- Build now works for users whose home directory isn't writable
2026-05-30 16:10:42 +00:00
b7fddf0069 fix: set OLLAMA_HOST explicitly for model pull
- ollama pull failed because the CLI didn't know where the server was
- Uses OLLAMA_HOST=http://127.0.0.1:11434 to connect regardless of user
2026-05-30 16:06:03 +00:00
bc5a5172dc fix: git safe.directory + ownership after clone/pull
- Adds safe.directory exception so git doesn't reject the repo
- chown directory to invoking user after clone/pull so build works
2026-05-30 16:02:44 +00:00
bb11a7f32c fix: wait for Ollama API before pulling model
- Wait loop now checks HTTP endpoint (127.0.0.1:11434/api/tags)
  instead of just the CLI version
- Increased retries from 10 to 15 (30s → 30s with 2s intervals)
- Uses sudo -u ollama for pull so models go to correct user directory
2026-05-30 16:00:08 +00:00
378c8e5b98 docs: update help text — no global sudo needed 2026-05-30 15:56:41 +00:00
e977907da3 fix: install.sh syntax errors from sed replacements
- Fixed stray dots and missing quotes in download URLs
- Fixed stale receiptnext binary references (now app)
- Fixed missing closing quote on info message
2026-05-30 15:55:28 +00:00
e20a484ebc chore: rename binary from receiptnext to app
- Binary output changed from 'receiptnext' to 'app'
- Release assets renamed to app-linux-amd64 / app-linux-arm64
- install.sh, Makefile, README.md updated to reference 'app'
- contrib/receiptnext.service uses /opt/receiptnext/app
- Systemd service updated to ExecStart=/opt/receiptnext/app
2026-05-30 15:53:24 +00:00
f9a7df1580 feat: greenfield deployment — no Go required
- install.sh detects if Go is installed
- If Go available: builds from source (current behavior)
- If Go missing: downloads pre-built binary from latest release
- Detects amd64/arm64 architecture automatically
- Same logic applies to -update mode
- Removed hard dependency on ollama.service in systemd unit
2026-05-30 15:49:05 +00:00
59788ba0c8 fix: app runs as dedicated user, not root
- install.sh detects the real user (SUDO_USER or whoami)
- If root, creates receiptnext system user
- If regular user, uses that user for the service
- User is added to ollama group for CLI access
- Systemd service uses User=receiptnext (or detected user)
- All file ownership set to the app user
2026-05-30 15:44:10 +00:00
b6c2c3f99d fix: Ollama timeout + image compression + selective sudo in install.sh 2026-05-30 15:41:47 +00:00
357483cfd2 fix: install zstd dependency before Ollama installer 2026-05-30 15:22:03 +00:00
57f501e0c8 docs: update one-liner install command in README 2026-05-30 15:21:25 +00:00
1aa5ec456e chore: install.sh asks for domain name instead of raw URL
- Prompts for domain and HTTPS preference
- Builds proper BASE_URL from domain + protocol
- Defaults to http://localhost:8080
2026-05-30 15:08:26 +00:00
c0d3002e22 fix: remove hardcoded credentials from .env.example and source
- .env.example: placeholder values only
- gemini.go: error on missing API key instead of fallback
- main.go: dynamic from address from SMTP_USER
- Security: old credentials removed from active codebase
2026-05-30 15:08:01 +00:00
7fdfba8095 chore: change install dir from /opt/rx to /opt/receiptnext 2026-05-30 15:06:20 +00:00
39fa7f6a12 chore: rewrite install.sh with AI provider choice + -update flag + pure Go
- install.sh now:
  • Clones repo to /opt/rx
  • Builds pure-Go binary (no CGO)
  • Prompts AI choice: Gemini / OpenAI / Ollama
  • If Ollama: auto-installs Ollama + pulls qwen3.5:2b
  • Creates .env interactively
  • Creates systemd service
  • Supports -update flag (pull, rebuild, restart)
- README.md fully rewritten for new install flow
- All binaries removed from git tracking (dist/ in .gitignore)
2026-05-30 15:03:39 +00:00
6de9c27f9c chore: switch from mattn/go-sqlite3 (CGO) to modernc.org/sqlite (pure Go)
- Zero CGO dependencies — builds with CGO_ENABLED=0
- Fully static binaries, no libc required
- Cross-compilation now works without any GCC cross-compilers
- ldd confirms: 'not a dynamic executable'
- Updates go.mod to Go 1.23 (required by modernc.org/sqlite)
2026-05-30 15:02:07 +00:00
b780ac1e1f chore: interactive install.sh — prompts user for credentials, installs to /opt/rx
- install.sh copies binary + assets to /opt/rx/
- Prompts for SMTP, Gemini, port, URL interactively
- Creates .env file with chmod 600
- Creates systemd service pointing to /opt/rx/
- Handles both pre-built and dist/ binaries
2026-05-30 14:23:42 +00:00
f50048e833 chore: rename to ReceiptNext + deployment scripts
- Full rename: module path, imports, directory structure
- install.sh: one-command bare-metal deployment
- contrib/receiptnext.service: systemd service file
- Makefile: build, install, manage service
- .gitenv updated to ReceiptNext
- New Releases page on git.lohmar.co.uk/cclohmar/ReceiptNext
2026-05-30 14:20:49 +00:00
5ca3ff7555 feat: configurable AI provider system (Gemini, OpenAI, Ollama)
- New provider architecture with common interface
- Provider selected via AI_PROVIDER env var (gemini/openai/ollama)
- Gemini (default): existing implementation, uses GEMINI_API_KEY
- OpenAI-compatible: uses OPENAI_API_KEY + AI_MODEL + AI_BASE_URL
  - Works with OpenAI, Perplexity, Together AI, Groq, etc.
- Ollama: local LLM, uses AI_BASE_URL + AI_MODEL
  - Supports llava, bakllava, and other vision models
- deepseek.go renamed to llm.go (cleanup)
- .env.example updated with all AI provider options
2026-05-30 14:05:19 +00:00
646378df64 feat: gallery upload + PDF receipt support
- Two upload buttons: Camera (capture) and Upload (gallery/PDF)
- PDF receipts from Uber/email now accepted and processed by Gemini Vision
- PDF detection via %PDF magic bytes in both handler and AI module
- Descriptions updated to reflect broader file support
2026-05-30 13:50:22 +00:00
dcb43b08a0 feat: send receipt images as ZIP attachment with report
- Email now includes both report (CSV/PDF) + ZIP of all receipt images
- ZIP images named {event-name}-{index}.{ext} matching list order
- Uses Go's archive/zip (stdlib, no external deps)
- Sender.SendReport now accepts []*Attachment for multiple files
- Gracefully skips missing image files with warnings
2026-05-30 13:38:23 +00:00
e22bd56169 fix: form fields too tight - increased spacing and padding
- Input padding increased from 12px to 16px
- Label-to-input gap increased from 4px to 8px
- Form group spacing increased from 16px to 20px
- Buttons padding increased from 8px/16px to 12px/20px
- Login card padding increased for more breathing room
- Added .form-row with responsive flex gap layout
- Added .main-content class with generous padding
2026-05-30 13:08:28 +00:00
67f5ed5624 feat: rebrand to ReceiptNext with 'The Terminal Mint' dark palette
- New favicon: Rx symbol in emerald green square (prescription/receipt)
- Dark theme: #0F172A base, #1E293B cards, #F8FAFC text
- Emerald #10B981 primary accent throughout
- Dashboard simplified to list view with event name + Open button
- Event page: receipt list with edit, Add Receipt button, Submit Event form
- Submit form shows total claim amount + email + format selection
- PWA manifest + service worker updated for ReceiptNext branding
- Variables-based theming for easy palette switching
2026-05-30 13:05:58 +00:00
ded4954c72 feat: add expense editing - view and edit all existing receipts
- Added ✏️ edit button on every expense in the list
- Clicking loads the receipt form pre-filled with the expense data
- Changes are saved via PUT /expenses/{id}
- Receipt form auto-switches between create (POST) and edit (PUT) mode
- Full HTMX multi-target response: form resets + list refreshes
2026-05-30 12:43:33 +00:00
e00c3373cb feat: replace abstract exchange rate with sample-based conversion
- Instead of entering a hard-to-calculate rate like 0.00773,
  users now enter a real sample (e.g. receipt=1000 KES, claimed=7.73 USD)
- The system computes the rate automatically: 7.73 / 1000 = 0.00773
- Users can get the sample values from their payment app notification
- Much more intuitive, especially for currencies with small exchange rates
2026-05-30 12:33:48 +00:00
4895b3d608 feat: add KES support + base currency + exchange rate for expense claims
- Added KES and 12+ additional currencies to receipt form
- Events now have base_currency (claim currency) and exchange_rate fields
- Receipts show original amount + auto-computed converted amount
- Converted amounts stored per expense in database
- CSV and PDF reports include both original and converted amounts
- Dashboard shows claim currency per event card
2026-05-30 12:23:31 +00:00
46c78ef507 chore: use gemini-2.0-flash-lite (most cost-effective model) 2026-05-30 12:14:46 +00:00
127046c9b7 feat: switch AI provider from DeepSeek to Google Gemini Vision API
- DeepSeek API does not support vision (only chat UI supports images)
- Google Gemini Vision supports native image analysis via inline_data
- Images are sent directly as base64 with proper MIME type detection
- No more OCR pipeline needed - Gemini sees the image directly
- Supports: JPEG, PNG, WebP, GIF, BMP, TIFF, HEIC, AVIF
- Updated .env.example to use GEMINI_API_KEY instead of DEEPSEEK_API_KEY
- Also: log OTP code in server log for easier debugging
2026-05-30 12:11:20 +00:00
fecf4af8a2 fix: AI receipt extraction now uses OCR (Tesseract) + LLM (DeepSeek) pipeline
- Problem: deepseek-v4-flash is text-only, cannot process base64 images - hallucinated fake data
- Solution: Two-step pipeline that actually extracts real data:
  1. Tesseract OCR extracts raw text from the receipt image
  2. DeepSeek v4 parses the OCR text into structured JSON
- Benefits: works with any image format, fast, accurate, no hallucinated data
- Properly handles HEIC/HEIF via heif-convert before OCR
2026-05-30 12:00:40 +00:00
ebe06082cf fix: support HEIC/HEIF photos from iPhone + broaden accepted image formats
- detectImageExtension now handles: JPEG, PNG, WebP, GIF, BMP, TIFF, HEIC, AVIF
- Added heif-convert + ImageMagick fallback for decoding unsupported formats
- AI extraction properly converts HEIC to JPEG before analysis
- Model: deepseek-v4-flash (confirmed working)
2026-05-30 11:54:31 +00:00
dc407fbf06 fix: DeepSeek Vision API changed - use text-embedded base64 + image compression instead of image_url content type
- DeepSeek no longer supports image_url in chat completions
- Images are now resized (max 300px) and JPEG-compressed (quality 50)
- Base64 data embedded directly in text prompt for processing
- Increased API timeout to 120s for larger prompts
- Also fixed mobile receipt capture (missing name attribute on file input)
- Also fixed OTP htmx:targetError (outerHTML → innerHTML swap)
2026-05-30 11:46:49 +00:00
64f5b9a65b fix: mobile receipt capture not working - missing name attribute on file input + htmx targetError on OTP verification 2026-05-30 11:34:36 +00:00
6b6196a59a fix: nil pointer panic when EmailSender is not configured 2026-05-29 20:05:01 +00:00
10c22bcaee chore: comprehensive README with binary distribution docs + build-all.sh 2026-05-29 19:54:20 +00:00
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