112 lines
3.0 KiB
Go
112 lines
3.0 KiB
Go
package redisstate
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"galaxy/mail/internal/domain/malformedcommand"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
// MalformedCommandStore provides the Redis-backed storage used for
|
|
// operator-visible malformed async command records.
|
|
type MalformedCommandStore struct {
|
|
client *redis.Client
|
|
keys Keyspace
|
|
}
|
|
|
|
// NewMalformedCommandStore constructs one Redis-backed malformed-command
|
|
// store.
|
|
func NewMalformedCommandStore(client *redis.Client) (*MalformedCommandStore, error) {
|
|
if client == nil {
|
|
return nil, errors.New("new malformed command store: nil redis client")
|
|
}
|
|
|
|
return &MalformedCommandStore{
|
|
client: client,
|
|
keys: Keyspace{},
|
|
}, nil
|
|
}
|
|
|
|
// Record stores entry idempotently by stream entry id.
|
|
func (store *MalformedCommandStore) Record(ctx context.Context, entry malformedcommand.Entry) error {
|
|
if store == nil || store.client == nil {
|
|
return errors.New("record malformed command: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("record malformed command: nil context")
|
|
}
|
|
if err := entry.Validate(); err != nil {
|
|
return fmt.Errorf("record malformed command: %w", err)
|
|
}
|
|
|
|
payload, err := MarshalMalformedCommand(entry)
|
|
if err != nil {
|
|
return fmt.Errorf("record malformed command: %w", err)
|
|
}
|
|
|
|
key := store.keys.MalformedCommand(entry.StreamEntryID)
|
|
indexKey := store.keys.MalformedCommandCreatedAtIndex()
|
|
score := float64(entry.RecordedAt.UTC().UnixMilli())
|
|
|
|
watchErr := store.client.Watch(ctx, func(tx *redis.Tx) error {
|
|
exists, err := tx.Exists(ctx, key).Result()
|
|
if err != nil {
|
|
return fmt.Errorf("record malformed command: %w", err)
|
|
}
|
|
if exists > 0 {
|
|
return nil
|
|
}
|
|
|
|
_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
|
|
pipe.Set(ctx, key, payload, DeadLetterTTL)
|
|
pipe.ZAdd(ctx, indexKey, redis.Z{
|
|
Score: score,
|
|
Member: entry.StreamEntryID,
|
|
})
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("record malformed command: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}, key)
|
|
switch {
|
|
case errors.Is(watchErr, redis.TxFailedErr):
|
|
return nil
|
|
case watchErr != nil:
|
|
return watchErr
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Get loads one malformed-command entry by stream entry id.
|
|
func (store *MalformedCommandStore) Get(ctx context.Context, streamEntryID string) (malformedcommand.Entry, bool, error) {
|
|
if store == nil || store.client == nil {
|
|
return malformedcommand.Entry{}, false, errors.New("get malformed command: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return malformedcommand.Entry{}, false, errors.New("get malformed command: nil context")
|
|
}
|
|
|
|
payload, err := store.client.Get(ctx, store.keys.MalformedCommand(streamEntryID)).Bytes()
|
|
switch {
|
|
case errors.Is(err, redis.Nil):
|
|
return malformedcommand.Entry{}, false, nil
|
|
case err != nil:
|
|
return malformedcommand.Entry{}, false, fmt.Errorf("get malformed command: %w", err)
|
|
}
|
|
|
|
entry, err := UnmarshalMalformedCommand(payload)
|
|
if err != nil {
|
|
return malformedcommand.Entry{}, false, fmt.Errorf("get malformed command: %w", err)
|
|
}
|
|
|
|
return entry, true, nil
|
|
}
|