Go app for capturing and archiving HSA-eligible receipts: OIDC/PKCE auth against Authelia, SQLite storage with dual-write (filesystem + DB blob), mobile-first upload, and DB export. Adds AI receipt classification: a config.json catalog of people and categories (seeded into the DB on startup), a prompt builder that derives name-order/initial variants from the data (with same-surname ambiguity handling), and an Anthropic tool-use client behind POST /classify. Tests run against a mock endpoint; a live integration test is env-gated to the cheapest model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
41 lines
1.3 KiB
Go
41 lines
1.3 KiB
Go
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// SnapshotTo writes a consistent point-in-time copy of the database to destPath
|
|
// using SQLite's VACUUM INTO (never touching/locking the live file). When
|
|
// includeBlobs is false, the image_data blobs are stripped from the copy,
|
|
// leaving only metadata — which is ~99% smaller.
|
|
//
|
|
// destPath must not already exist (VACUUM INTO requires this).
|
|
func (s *Store) SnapshotTo(destPath string, includeBlobs bool) error {
|
|
if _, err := s.db.Exec(`VACUUM INTO '` + escapeSQLiteString(destPath) + `'`); err != nil {
|
|
return fmt.Errorf("vacuum into snapshot: %w", err)
|
|
}
|
|
if includeBlobs {
|
|
return nil
|
|
}
|
|
|
|
// Strip blobs from the copy only. image_data is NOT NULL, so use an empty blob.
|
|
snap, err := sql.Open("sqlite", destPath)
|
|
if err != nil {
|
|
return fmt.Errorf("open snapshot: %w", err)
|
|
}
|
|
defer snap.Close()
|
|
if _, err := snap.Exec(`UPDATE receipts SET image_data = X''`); err != nil {
|
|
return fmt.Errorf("strip blobs: %w", err)
|
|
}
|
|
if _, err := snap.Exec(`VACUUM`); err != nil {
|
|
return fmt.Errorf("vacuum stripped snapshot: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// escapeSQLiteString escapes a string for use inside single quotes in SQL.
|
|
func escapeSQLiteString(s string) string {
|
|
return strings.ReplaceAll(s, "'", "''")
|
|
}
|