Full Go backend + Vue 3 frontend, OpenAI-compatible API, multi-provider account pools, billing/admin, Docker one-command deploy with auto HTTPS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
97 lines
2.5 KiB
Go
97 lines
2.5 KiB
Go
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()
|
|
}
|