package utils import ( "path/filepath" "time" logger "git.site.quack-lab.dev/dave/cylogger" "gorm.io/driver/sqlite" "gorm.io/gorm" gormlogger "gorm.io/gorm/logger" ) // dbLogger is a scoped logger for the utils/db package. var dbLogger = logger.Default.WithPrefix("utils/db") type DB interface { DB() *gorm.DB Raw(sql string, args ...any) *gorm.DB SaveFile(filePath string, fileData []byte) error GetFile(filePath string) ([]byte, error) GetAllFiles() ([]FileSnapshot, error) RemoveAllFiles() error } type FileSnapshot struct { Date time.Time `gorm:"primaryKey"` FilePath string `gorm:"primaryKey"` FileData []byte `gorm:"type:blob"` } type DBWrapper struct { db *gorm.DB } var globalDB *DBWrapper func GetDB() (DB, error) { getDBLogger := dbLogger.WithPrefix("GetDB") getDBLogger.Debug("Attempting to get database connection") var err error dbFile := filepath.Join("data.sqlite") getDBLogger.Debug("Opening database file: %q", dbFile) db, err := gorm.Open(sqlite.Open(dbFile), &gorm.Config{ // SkipDefaultTransaction: true, PrepareStmt: true, Logger: gormlogger.Default.LogMode(gormlogger.Silent), }) if err != nil { getDBLogger.Error("Failed to open database: %v", err) return nil, err } getDBLogger.Debug("Database opened successfully, running auto migration") if err := db.AutoMigrate(&FileSnapshot{}); err != nil { getDBLogger.Error("Auto migration failed: %v", err) return nil, err } getDBLogger.Debug("Auto migration completed") globalDB = &DBWrapper{db: db} getDBLogger.Debug("Database wrapper initialized") return globalDB, nil } // Just a wrapper func (db *DBWrapper) Raw(sql string, args ...any) *gorm.DB { rawLogger := dbLogger.WithPrefix("Raw").WithField("sql", sql) rawLogger.Debug("Executing raw SQL query with args: %v", args) return db.db.Raw(sql, args...) } func (db *DBWrapper) DB() *gorm.DB { dbLogger.WithPrefix("DB").Debug("Returning GORM DB instance") return db.db } func (db *DBWrapper) FileExists(filePath string) (bool, error) { fileExistsLogger := dbLogger.WithPrefix("FileExists").WithField("filePath", filePath) fileExistsLogger.Debug("Checking if file exists in database") var count int64 err := db.db.Model(&FileSnapshot{}).Where("file_path = ?", filePath).Count(&count).Error if err != nil { fileExistsLogger.Error("Error checking if file exists: %v", err) return false, err } fileExistsLogger.Debug("File exists: %t", count > 0) return count > 0, err } func (db *DBWrapper) SaveFile(filePath string, fileData []byte) error { saveFileLogger := dbLogger.WithPrefix("SaveFile").WithField("filePath", filePath) saveFileLogger.Debug("Attempting to save file to database") saveFileLogger.Trace("File data length: %d", len(fileData)) exists, err := db.FileExists(filePath) if err != nil { saveFileLogger.Error("Error checking if file exists: %v", err) return err } if exists { saveFileLogger.Debug("File already exists, skipping save") return nil } saveFileLogger.Debug("Creating new file snapshot in database") err = db.db.Create(&FileSnapshot{ Date: time.Now(), FilePath: filePath, FileData: fileData, }).Error if err != nil { saveFileLogger.Error("Failed to create file snapshot: %v", err) } else { saveFileLogger.Debug("File saved successfully to database") } return err } func (db *DBWrapper) GetFile(filePath string) ([]byte, error) { getFileLogger := dbLogger.WithPrefix("GetFile").WithField("filePath", filePath) getFileLogger.Debug("Getting file from database") var fileSnapshot FileSnapshot err := db.db.Model(&FileSnapshot{}).Where("file_path = ?", filePath).First(&fileSnapshot).Error if err != nil { // Downgrade not-found to warning to avoid noisy errors during first run getFileLogger.Warning("Failed to get file from database: %v", err) return nil, err } getFileLogger.Debug("File found in database") getFileLogger.Trace("Retrieved file data length: %d", len(fileSnapshot.FileData)) return fileSnapshot.FileData, nil } func (db *DBWrapper) GetAllFiles() ([]FileSnapshot, error) { getAllFilesLogger := dbLogger.WithPrefix("GetAllFiles") getAllFilesLogger.Debug("Getting all files from database") var fileSnapshots []FileSnapshot err := db.db.Model(&FileSnapshot{}).Find(&fileSnapshots).Error if err != nil { getAllFilesLogger.Error("Failed to get all files from database: %v", err) return nil, err } getAllFilesLogger.Debug("Found %d files in database", len(fileSnapshots)) getAllFilesLogger.Trace("File snapshots retrieved: %v", fileSnapshots) return fileSnapshots, nil } func (db *DBWrapper) RemoveAllFiles() error { removeAllFilesLogger := dbLogger.WithPrefix("RemoveAllFiles") removeAllFilesLogger.Debug("Removing all files from database") err := db.db.Exec("DELETE FROM file_snapshots").Error if err != nil { removeAllFilesLogger.Error("Failed to remove all files from database: %v", err) } else { removeAllFilesLogger.Debug("All files removed from database") } return err }