feat: notification service

This commit is contained in:
Ilia Denisov
2026-04-22 08:49:45 +02:00
committed by GitHub
parent 5b7593e6f6
commit 32dc29359a
135 changed files with 21828 additions and 130 deletions
@@ -0,0 +1,140 @@
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
}