106 lines
3.6 KiB
Go
106 lines
3.6 KiB
Go
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
|
|
}
|