hsa-app/internal/storage/snapshot.go
Jean-Michel Tremblay 8b7252dad4 Initial commit: HSA receipt tracker
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>
2026-06-17 21:40:12 -04:00

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, "'", "''")
}