summaryrefslogtreecommitdiff
path: root/internal/store/sqlite.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/store/sqlite.go')
-rw-r--r--internal/store/sqlite.go114
1 files changed, 42 insertions, 72 deletions
diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go
index 366b24e..33edbf2 100644
--- a/internal/store/sqlite.go
+++ b/internal/store/sqlite.go
@@ -73,20 +73,54 @@ func (s *Store) DB() *sql.DB {
return s.db
}
-// runMigrations executes all migration files in order
+// runMigrations executes all migration files in order, skipping already-applied ones.
func (s *Store) runMigrations() error {
- // Get migration files from configured directory
+ // Check if schema_migrations exists before creating it — used to detect legacy DBs.
+ var trackingExists int
+ s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='schema_migrations'`).Scan(&trackingExists)
+
+ if _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations (
+ filename TEXT PRIMARY KEY,
+ applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ )`); err != nil {
+ return fmt.Errorf("failed to create schema_migrations table: %w", err)
+ }
+
pattern := filepath.Join(s.migrationDir, "*.sql")
migrationFiles, err := filepath.Glob(pattern)
if err != nil {
return fmt.Errorf("failed to read migration files: %w", err)
}
- // Sort migrations by filename
sort.Strings(migrationFiles)
- // Execute each migration
+ // If schema_migrations was just created on a pre-existing DB (legacy), seed all
+ // current migration filenames as applied so we don't re-run them.
+ if trackingExists == 0 {
+ var existingTables int
+ s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name != 'schema_migrations'`).Scan(&existingTables)
+ if existingTables > 0 {
+ for _, file := range migrationFiles {
+ name := filepath.Base(file)
+ if _, err := s.db.Exec(`INSERT OR IGNORE INTO schema_migrations (filename) VALUES (?)`, name); err != nil {
+ return fmt.Errorf("failed to seed migration record %s: %w", name, err)
+ }
+ }
+ return nil
+ }
+ }
+
for _, file := range migrationFiles {
+ name := filepath.Base(file)
+
+ var count int
+ if err := s.db.QueryRow(`SELECT COUNT(*) FROM schema_migrations WHERE filename = ?`, name).Scan(&count); err != nil {
+ return fmt.Errorf("failed to check migration %s: %w", name, err)
+ }
+ if count > 0 {
+ continue // already applied
+ }
+
content, err := os.ReadFile(file)
if err != nil {
return fmt.Errorf("failed to read migration %s: %w", file, err)
@@ -95,6 +129,10 @@ func (s *Store) runMigrations() error {
if _, err := s.db.Exec(string(content)); err != nil {
return fmt.Errorf("failed to execute migration %s: %w", file, err)
}
+
+ if _, err := s.db.Exec(`INSERT INTO schema_migrations (filename) VALUES (?)`, name); err != nil {
+ return fmt.Errorf("failed to record migration %s: %w", name, err)
+ }
}
return nil
@@ -588,74 +626,6 @@ func (s *Store) ClearSyncToken(service string) error {
return err
}
-// Bug represents a user-reported bug
-type Bug struct {
- ID int64
- Description string
- CreatedAt time.Time
- ResolvedAt *time.Time
-}
-
-// SaveBug saves a new bug report
-func (s *Store) SaveBug(description string) error {
- _, err := s.db.Exec(`INSERT INTO bugs (description) VALUES (?)`, description)
- return err
-}
-
-// GetBugs retrieves all bugs, newest first
-func (s *Store) GetBugs() ([]Bug, error) {
- rows, err := s.db.Query(`SELECT id, description, created_at, resolved_at FROM bugs ORDER BY created_at DESC`)
- if err != nil {
- return nil, err
- }
- defer func() { _ = rows.Close() }()
-
- var bugs []Bug
- for rows.Next() {
- var b Bug
- var resolvedAt sql.NullTime
- if err := rows.Scan(&b.ID, &b.Description, &b.CreatedAt, &resolvedAt); err != nil {
- return nil, err
- }
- if resolvedAt.Valid {
- b.ResolvedAt = &resolvedAt.Time
- }
- bugs = append(bugs, b)
- }
- return bugs, rows.Err()
-}
-
-// GetUnresolvedBugs retrieves bugs that haven't been resolved yet
-func (s *Store) GetUnresolvedBugs() ([]Bug, error) {
- rows, err := s.db.Query(`SELECT id, description, created_at FROM bugs WHERE resolved_at IS NULL ORDER BY created_at DESC`)
- if err != nil {
- return nil, err
- }
- defer func() { _ = rows.Close() }()
-
- var bugs []Bug
- for rows.Next() {
- var b Bug
- if err := rows.Scan(&b.ID, &b.Description, &b.CreatedAt); err != nil {
- return nil, err
- }
- bugs = append(bugs, b)
- }
- return bugs, rows.Err()
-}
-
-// ResolveBug marks a bug as resolved
-func (s *Store) ResolveBug(id int64) error {
- _, err := s.db.Exec(`UPDATE bugs SET resolved_at = CURRENT_TIMESTAMP WHERE id = ?`, id)
- return err
-}
-
-// UnresolveBug marks a bug as unresolved (reopens it)
-func (s *Store) UnresolveBug(id int64) error {
- _, err := s.db.Exec(`UPDATE bugs SET resolved_at = NULL WHERE id = ?`, id)
- return err
-}
-
// UserShoppingItem represents a user-added shopping item
type UserShoppingItem struct {
ID int64