package database import ( "context" "database/sql" "log/slog" "go.uber.org/fx" "gorm.io/driver/sqlite" "gorm.io/gorm" _ "modernc.org/sqlite" // Pure Go SQLite driver "sneak.berlin/go/webhooker/internal/config" "sneak.berlin/go/webhooker/internal/logger" ) // nolint:revive // DatabaseParams is a standard fx naming convention type DatabaseParams struct { fx.In Config *config.Config Logger *logger.Logger } type Database struct { db *gorm.DB log *slog.Logger params *DatabaseParams } func New(lc fx.Lifecycle, params DatabaseParams) (*Database, error) { d := &Database{ params: ¶ms, log: params.Logger.Get(), } lc.Append(fx.Hook{ OnStart: func(_ context.Context) error { // nolint:revive // ctx unused but required by fx return d.connect() }, OnStop: func(_ context.Context) error { // nolint:revive // ctx unused but required by fx return d.close() }, }) return d, nil } func (d *Database) connect() error { dbURL := d.params.Config.DBURL if dbURL == "" { // Default to SQLite for development dbURL = "file:webhooker.db?cache=shared&mode=rwc" } // First, open the database with the pure Go driver sqlDB, err := sql.Open("sqlite", dbURL) if err != nil { d.log.Error("failed to open database", "error", err) return err } // Then use it with GORM db, err := gorm.Open(sqlite.Dialector{ Conn: sqlDB, }, &gorm.Config{}) if err != nil { d.log.Error("failed to connect to database", "error", err) return err } d.db = db d.log.Info("connected to database", "database", dbURL) // Run migrations return d.migrate() } func (d *Database) migrate() error { // Run GORM auto-migrations if err := d.Migrate(); err != nil { d.log.Error("failed to run database migrations", "error", err) return err } d.log.Info("database migrations completed") // Check if admin user exists var userCount int64 if err := d.db.Model(&User{}).Count(&userCount).Error; err != nil { d.log.Error("failed to count users", "error", err) return err } if userCount == 0 { // Create admin user d.log.Info("no users found, creating admin user") // Generate random password password, err := GenerateRandomPassword(16) if err != nil { d.log.Error("failed to generate random password", "error", err) return err } // Hash the password hashedPassword, err := HashPassword(password) if err != nil { d.log.Error("failed to hash password", "error", err) return err } // Create admin user adminUser := &User{ Username: "admin", Password: hashedPassword, } if err := d.db.Create(adminUser).Error; err != nil { d.log.Error("failed to create admin user", "error", err) return err } // Log the password - this will only happen once on first startup d.log.Info("admin user created", "username", "admin", "password", password, "message", "SAVE THIS PASSWORD - it will not be shown again!") } return nil } func (d *Database) close() error { if d.db != nil { sqlDB, err := d.db.DB() if err != nil { return err } return sqlDB.Close() } return nil } func (d *Database) DB() *gorm.DB { return d.db }