feat: notification service
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
package redisstate
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"galaxy/notification/internal/api/intentstream"
|
||||
)
|
||||
|
||||
const defaultPrefix = "notification:"
|
||||
|
||||
// Keyspace builds the frozen Notification Service Redis keys. All dynamic key
|
||||
// segments are encoded with base64url so raw key structure does not depend on
|
||||
// caller-provided characters.
|
||||
type Keyspace struct{}
|
||||
|
||||
// Notification returns the primary Redis key for one notification_record.
|
||||
func (Keyspace) Notification(notificationID string) string {
|
||||
return defaultPrefix + "records:" + encodeKeyComponent(notificationID)
|
||||
}
|
||||
|
||||
// Route returns the primary Redis key for one notification_route.
|
||||
func (Keyspace) Route(notificationID string, routeID string) string {
|
||||
return defaultPrefix + "routes:" + encodeKeyComponent(notificationID) + ":" + encodeKeyComponent(routeID)
|
||||
}
|
||||
|
||||
// ParseRoute returns the notification identifier and route identifier encoded
|
||||
// inside routeKey.
|
||||
func (Keyspace) ParseRoute(routeKey string) (string, string, error) {
|
||||
trimmedPrefix := defaultPrefix + "routes:"
|
||||
if !strings.HasPrefix(routeKey, trimmedPrefix) {
|
||||
return "", "", fmt.Errorf("parse route key: %q does not use %q prefix", routeKey, trimmedPrefix)
|
||||
}
|
||||
|
||||
encoded := strings.TrimPrefix(routeKey, trimmedPrefix)
|
||||
parts := strings.Split(encoded, ":")
|
||||
if len(parts) != 2 {
|
||||
return "", "", fmt.Errorf("parse route key: %q must contain exactly two encoded segments", routeKey)
|
||||
}
|
||||
|
||||
notificationID, err := decodeKeyComponent(parts[0])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parse route key: notification id: %w", err)
|
||||
}
|
||||
routeID, err := decodeKeyComponent(parts[1])
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parse route key: route id: %w", err)
|
||||
}
|
||||
|
||||
return notificationID, routeID, nil
|
||||
}
|
||||
|
||||
// Idempotency returns the primary Redis key for one
|
||||
// notification_idempotency_record.
|
||||
func (Keyspace) Idempotency(producer intentstream.Producer, idempotencyKey string) string {
|
||||
return defaultPrefix + "idempotency:" + encodeKeyComponent(string(producer)) + ":" + encodeKeyComponent(idempotencyKey)
|
||||
}
|
||||
|
||||
// DeadLetter returns the primary Redis key for one
|
||||
// notification_dead_letter_entry.
|
||||
func (Keyspace) DeadLetter(notificationID string, routeID string) string {
|
||||
return defaultPrefix + "dead_letters:" + encodeKeyComponent(notificationID) + ":" + encodeKeyComponent(routeID)
|
||||
}
|
||||
|
||||
// RouteLease returns the temporary Redis key used to coordinate exclusive
|
||||
// publication of one notification_route across replicas.
|
||||
func (Keyspace) RouteLease(notificationID string, routeID string) string {
|
||||
return defaultPrefix + "route_leases:" + encodeKeyComponent(notificationID) + ":" + encodeKeyComponent(routeID)
|
||||
}
|
||||
|
||||
// MalformedIntent returns the primary Redis key for one malformed-intent
|
||||
// record.
|
||||
func (Keyspace) MalformedIntent(streamEntryID string) string {
|
||||
return defaultPrefix + "malformed_intents:" + encodeKeyComponent(streamEntryID)
|
||||
}
|
||||
|
||||
// StreamOffset returns the primary Redis key for one persisted intent-consumer
|
||||
// offset.
|
||||
func (Keyspace) StreamOffset(stream string) string {
|
||||
return defaultPrefix + "stream_offsets:" + encodeKeyComponent(stream)
|
||||
}
|
||||
|
||||
// Intents returns the frozen ingress Redis Stream key.
|
||||
func (Keyspace) Intents() string {
|
||||
return defaultPrefix + "intents"
|
||||
}
|
||||
|
||||
// RouteSchedule returns the frozen route schedule sorted-set key.
|
||||
func (Keyspace) RouteSchedule() string {
|
||||
return defaultPrefix + "route_schedule"
|
||||
}
|
||||
|
||||
func encodeKeyComponent(value string) string {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(value))
|
||||
}
|
||||
|
||||
func decodeKeyComponent(value string) (string, error) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(decoded), nil
|
||||
}
|
||||
Reference in New Issue
Block a user