package redisstate import ( "context" "errors" "fmt" "galaxy/notification/internal/api/intentstream" "galaxy/notification/internal/service/acceptintent" "github.com/redis/go-redis/v9" ) // AcceptanceStore provides the Redis-backed durable storage used by the // intent-acceptance use case. type AcceptanceStore struct { client *redis.Client writer *AtomicWriter keys Keyspace cfg AcceptanceConfig } // NewAcceptanceStore constructs one Redis-backed acceptance store. func NewAcceptanceStore(client *redis.Client, cfg AcceptanceConfig) (*AcceptanceStore, error) { if client == nil { return nil, errors.New("new notification acceptance store: nil redis client") } writer, err := NewAtomicWriter(client, cfg) if err != nil { return nil, fmt.Errorf("new notification acceptance store: %w", err) } return &AcceptanceStore{ client: client, writer: writer, keys: Keyspace{}, cfg: cfg, }, nil } // CreateAcceptance stores one complete accepted notification write set in // Redis. func (store *AcceptanceStore) CreateAcceptance(ctx context.Context, input acceptintent.CreateAcceptanceInput) error { if store == nil || store.client == nil || store.writer == nil { return errors.New("create notification acceptance: nil store") } if ctx == nil { return errors.New("create notification acceptance: nil context") } if err := input.Validate(); err != nil { return fmt.Errorf("create notification acceptance: %w", err) } err := store.writer.CreateAcceptance(ctx, input) if errors.Is(err, ErrConflict) { return fmt.Errorf("create notification acceptance: %w", acceptintent.ErrConflict) } if err != nil { return fmt.Errorf("create notification acceptance: %w", err) } return nil } // GetIdempotency loads one accepted idempotency scope from Redis. func (store *AcceptanceStore) GetIdempotency(ctx context.Context, producer intentstream.Producer, idempotencyKey string) (acceptintent.IdempotencyRecord, bool, error) { if store == nil || store.client == nil { return acceptintent.IdempotencyRecord{}, false, errors.New("get notification idempotency: nil store") } if ctx == nil { return acceptintent.IdempotencyRecord{}, false, errors.New("get notification idempotency: nil context") } payload, err := store.client.Get(ctx, store.keys.Idempotency(producer, idempotencyKey)).Bytes() switch { case errors.Is(err, redis.Nil): return acceptintent.IdempotencyRecord{}, false, nil case err != nil: return acceptintent.IdempotencyRecord{}, false, fmt.Errorf("get notification idempotency: %w", err) } record, err := UnmarshalIdempotency(payload) if err != nil { return acceptintent.IdempotencyRecord{}, false, fmt.Errorf("get notification idempotency: %w", err) } return record, true, nil } // GetNotification loads one accepted notification record from Redis. func (store *AcceptanceStore) GetNotification(ctx context.Context, notificationID string) (acceptintent.NotificationRecord, bool, error) { if store == nil || store.client == nil { return acceptintent.NotificationRecord{}, false, errors.New("get notification record: nil store") } if ctx == nil { return acceptintent.NotificationRecord{}, false, errors.New("get notification record: nil context") } payload, err := store.client.Get(ctx, store.keys.Notification(notificationID)).Bytes() switch { case errors.Is(err, redis.Nil): return acceptintent.NotificationRecord{}, false, nil case err != nil: return acceptintent.NotificationRecord{}, false, fmt.Errorf("get notification record: %w", err) } record, err := UnmarshalNotification(payload) if err != nil { return acceptintent.NotificationRecord{}, false, fmt.Errorf("get notification record: %w", err) } return record, true, nil } // GetRoute loads one accepted notification route by NotificationID and // RouteID. func (store *AcceptanceStore) GetRoute(ctx context.Context, notificationID string, routeID string) (acceptintent.NotificationRoute, bool, error) { if store == nil || store.client == nil { return acceptintent.NotificationRoute{}, false, errors.New("get notification route: nil store") } if ctx == nil { return acceptintent.NotificationRoute{}, false, errors.New("get notification route: nil context") } payload, err := store.client.Get(ctx, store.keys.Route(notificationID, routeID)).Bytes() switch { case errors.Is(err, redis.Nil): return acceptintent.NotificationRoute{}, false, nil case err != nil: return acceptintent.NotificationRoute{}, false, fmt.Errorf("get notification route: %w", err) } record, err := UnmarshalRoute(payload) if err != nil { return acceptintent.NotificationRoute{}, false, fmt.Errorf("get notification route: %w", err) } return record, true, nil }