package sqlite import ( "fmt" "os" "path/filepath" "github.com/glebarez/sqlite" "github.com/rose_cat707/luminary/internal/model" "gorm.io/gorm" "gorm.io/gorm/logger" ) type Store struct { db *gorm.DB } func New(path string) (*Store, error) { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, fmt.Errorf("create data dir: %w", err) } db, err := gorm.Open(sqlite.Open(path+"?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), }) if err != nil { return nil, fmt.Errorf("open sqlite: %w", err) } sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(1) if err := ensureIPRulesSchema(db); err != nil { return nil, fmt.Errorf("migrate ip_rules: %w", err) } if err := db.AutoMigrate( &model.Provider{}, &model.ProviderKey{}, &model.ModelEntry{}, &model.IngressKey{}, &model.AdminUser{}, &model.AdminSession{}, &model.UsageRecord{}, &model.HealthCheckRecord{}, &model.RoutingRule{}, &model.SystemSettings{}, &model.Prediction{}, &model.StoredImage{}, ); err != nil { return nil, fmt.Errorf("migrate: %w", err) } return &Store{db: db}, nil } // ensureIPRulesSchema manages ip_rules without GORM AutoMigrate (avoids bad table rebuilds). func ensureIPRulesSchema(db *gorm.DB) error { if !tableExists(db, "ip_rules") { return db.Exec(` CREATE TABLE ip_rules ( id INTEGER PRIMARY KEY AUTOINCREMENT, cidr TEXT NOT NULL, type TEXT NOT NULL, scope TEXT NOT NULL DEFAULT 'proxy', enabled NUMERIC DEFAULT 1, created_at DATETIME ); `).Error } if !tableHasColumn(db, "ip_rules", "c_id_r") { return nil } cidrExpr := "c_id_r" if tableHasColumn(db, "ip_rules", "cidr") { cidrExpr = "COALESCE(NULLIF(cidr, ''), c_id_r)" } return db.Exec(` CREATE TABLE ip_rules_mig ( id INTEGER PRIMARY KEY AUTOINCREMENT, cidr TEXT NOT NULL, type TEXT NOT NULL, scope TEXT NOT NULL DEFAULT 'proxy', enabled NUMERIC DEFAULT 1, created_at DATETIME ); INSERT INTO ip_rules_mig (id, cidr, type, scope, enabled, created_at) SELECT id, ` + cidrExpr + `, type, scope, enabled, created_at FROM ip_rules; DROP TABLE ip_rules; ALTER TABLE ip_rules_mig RENAME TO ip_rules; `).Error } func tableHasColumn(db *gorm.DB, table, column string) bool { var n int64 db.Raw(`SELECT COUNT(*) FROM pragma_table_info(?) WHERE name = ?`, table, column).Scan(&n) return n > 0 } func tableExists(db *gorm.DB, table string) bool { var n int64 db.Raw(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?`, table).Scan(&n) return n > 0 } func (s *Store) DB() *gorm.DB { return s.db }