// Package configprovider implements ports.ConfigProvider with Redis-backed // dynamic auth/session configuration. package configprovider import ( "context" "errors" "fmt" "strconv" "strings" "time" "galaxy/authsession/internal/ports" "github.com/redis/go-redis/v9" ) // Config configures one Redis-backed config provider instance. The store does // not own its Redis client; the runtime supplies a shared client constructed // via `pkg/redisconn`. type Config struct { // SessionLimitKey identifies the single Redis string key that stores the // active-session-limit configuration value. SessionLimitKey string // OperationTimeout bounds each Redis round trip performed by the adapter. OperationTimeout time.Duration } // Store reads dynamic auth/session configuration from Redis. type Store struct { client *redis.Client sessionLimitKey string operationTimeout time.Duration } // New constructs a Redis-backed config provider that uses client and applies // the namespace and timeout settings from cfg. func New(client *redis.Client, cfg Config) (*Store, error) { switch { case client == nil: return nil, errors.New("new redis config provider: nil redis client") case strings.TrimSpace(cfg.SessionLimitKey) == "": return nil, errors.New("new redis config provider: session limit key must not be empty") case cfg.OperationTimeout <= 0: return nil, errors.New("new redis config provider: operation timeout must be positive") } return &Store{ client: client, sessionLimitKey: cfg.SessionLimitKey, operationTimeout: cfg.OperationTimeout, }, nil } // LoadSessionLimit returns the current active-session-limit configuration. // Missing or invalid Redis values are treated as “limit absent” by policy. func (s *Store) LoadSessionLimit(ctx context.Context) (ports.SessionLimitConfig, error) { operationCtx, cancel, err := s.operationContext(ctx, "load session limit from redis") if err != nil { return ports.SessionLimitConfig{}, err } defer cancel() value, err := s.client.Get(operationCtx, s.sessionLimitKey).Result() switch { case errors.Is(err, redis.Nil): return ports.SessionLimitConfig{}, nil case err != nil: return ports.SessionLimitConfig{}, fmt.Errorf("load session limit from redis: %w", err) } config, valid := parseSessionLimitConfig(value) if !valid { return ports.SessionLimitConfig{}, nil } if err := config.Validate(); err != nil { return ports.SessionLimitConfig{}, nil } return config, nil } func (s *Store) operationContext(ctx context.Context, operation string) (context.Context, context.CancelFunc, error) { if s == nil || s.client == nil { return nil, nil, fmt.Errorf("%s: nil store", operation) } if ctx == nil { return nil, nil, fmt.Errorf("%s: nil context", operation) } operationCtx, cancel := context.WithTimeout(ctx, s.operationTimeout) return operationCtx, cancel, nil } func parseSessionLimitConfig(raw string) (ports.SessionLimitConfig, bool) { if strings.TrimSpace(raw) == "" || strings.TrimSpace(raw) != raw { return ports.SessionLimitConfig{}, false } for _, symbol := range raw { if symbol < '0' || symbol > '9' { return ports.SessionLimitConfig{}, false } } parsed, err := strconv.ParseInt(raw, 10, strconv.IntSize) if err != nil || parsed <= 0 { return ports.SessionLimitConfig{}, false } limit := int(parsed) return ports.SessionLimitConfig{ ActiveSessionLimit: &limit, }, true } var _ ports.ConfigProvider = (*Store)(nil)