package repo import ( "context" "time" "backend/internal/model" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) const siteSettingCachePrefix = "setting:" // siteSettingCacheTTL is a safety-net expiry; writes invalidate eagerly, so this // only bounds staleness if an invalidation is ever missed (e.g. Redis blip). const siteSettingCacheTTL = 5 * time.Minute type SiteSettingRepository struct { db *gorm.DB cache *redis.Client } // NewSiteSettingRepository wires the config KV store. cache may be nil, in which // case the repository transparently falls back to DB-only access. func NewSiteSettingRepository(db *gorm.DB, cache *redis.Client) *SiteSettingRepository { return &SiteSettingRepository{db: db, cache: cache} } func (r *SiteSettingRepository) cacheKey(key string) string { return siteSettingCachePrefix + key } func (r *SiteSettingRepository) GetValue(ctx context.Context, key string) (string, error) { if r.cache != nil { if v, err := r.cache.Get(ctx, r.cacheKey(key)).Result(); err == nil { return v, nil } // redis.Nil (miss) or any transient cache error -> fall through to DB. } value := "" var setting model.SiteSetting if err := r.db.WithContext(ctx).First(&setting, "key = ?", key).Error; err != nil { if err != gorm.ErrRecordNotFound { return "", err } // Not found stays as "" — still cached below to absorb repeated misses. } else { value = setting.Value } if r.cache != nil { _ = r.cache.Set(ctx, r.cacheKey(key), value, siteSettingCacheTTL).Err() } return value, nil } func (r *SiteSettingRepository) UpsertValue(ctx context.Context, key, value string) error { if err := r.db.WithContext(ctx).Save(&model.SiteSetting{ Key: key, Value: value, }).Error; err != nil { return err } r.invalidate(ctx, key) return nil } func (r *SiteSettingRepository) UpsertValues(ctx context.Context, values map[string]string) error { if err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { for key, value := range values { if err := tx.Save(&model.SiteSetting{ Key: key, Value: value, }).Error; err != nil { return err } } return nil }); err != nil { return err } for key := range values { r.invalidate(ctx, key) } return nil } // invalidate drops the cached entry so the next read repopulates from the DB. // Deleting (rather than overwriting) keeps writes simple and race-tolerant. func (r *SiteSettingRepository) invalidate(ctx context.Context, key string) { if r.cache == nil { return } _ = r.cache.Del(ctx, r.cacheKey(key)).Err() }