feat: mail service
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user