package data import ( "context" "fmt" "log" "sync" "time" "github.com/google/wire" "github.com/redis/go-redis/v9" "gorm.io/gorm" "kra/internal/conf" ) var ProviderSet = wire.NewSet(NewData, NewSystemRepo, NewAccessRepo, NewMenuRepo, NewOrganizationRepo, NewSettingsRepo, NewVersionRepo, NewExportRepo, NewAuditRepo, NewTaskRepo, NewMediaRepo, NewAnnouncementRepo, NewEmailRepo, NewCache, NewFileStorage) type Data struct { mu sync.RWMutex initMu sync.Mutex gormDB *gorm.DB redis *redis.Client database *conf.Data_Database config *conf.Data admin *conf.AdminBackend storage *reloadableStorage } func NewData(c *conf.Data, admin *conf.AdminBackend) (*Data, func(), error) { if c == nil || c.Database == nil { return nil, nil, fmt.Errorf("database configuration is required") } d := &Data{database: c.Database, config: c, admin: admin} db, err := openDatabase(c.Database, false, "") usingFallback := false if err != nil { // The initialization endpoint must remain available when the configured // target database has not been created yet. log.Printf("configured database unavailable before initialization: %v", err) db, err = openFallbackDatabase() if err != nil { return nil, nil, fmt.Errorf("open bootstrap database: %w", err) } usingFallback = true } d.gormDB = db if !usingFallback { if err = migrateAll(db); err != nil { return nil, nil, fmt.Errorf("migrate tables: %w", err) } } if candidate := openRedis(c.Redis); candidate != nil { d.redis = candidate } cleanup := func() { d.mu.RLock() db := d.gormDB d.mu.RUnlock() if sqlDB, closeErr := db.DB(); closeErr == nil { _ = sqlDB.Close() } if d.redis != nil { _ = d.redis.Close() } } return d, cleanup, nil } func openRedis(config *conf.Data_Redis) *redis.Client { if config == nil || config.Addr == "" { return nil } options := &redis.Options{Addr: config.Addr, Network: config.Network} if config.ReadTimeout != nil { options.ReadTimeout = config.ReadTimeout.AsDuration() } if config.WriteTimeout != nil { options.WriteTimeout = config.WriteTimeout.AsDuration() } candidate := redis.NewClient(options) pingCtx, cancel := context.WithTimeout(context.Background(), 800*time.Millisecond) defer cancel() if err := candidate.Ping(pingCtx).Err(); err != nil { log.Printf("redis unavailable, using in-memory cache: %v", err) _ = candidate.Close() return nil } return candidate } func (d *Data) activateDatabase(db *gorm.DB, config *conf.Data_Database) { d.mu.Lock() old := d.gormDB d.gormDB = db d.database = config d.config.Database = config d.mu.Unlock() if old != nil { if sqlDB, e := old.DB(); e == nil { _ = sqlDB.Close() } } }