// Package routepublisher composes one PostgreSQL-backed route-state store // (notificationstore) with one Redis-backed lease store (redisstate.LeaseStore) // behind the publisher worker contracts. The composition lets push and email // publishers keep their existing one-store dependency while Stage 5 of // `PG_PLAN.md` splits durable state to PostgreSQL and the short-lived // per-replica exclusivity lease to Redis. package routepublisher import ( "context" "errors" "time" "galaxy/notification/internal/adapters/postgres/notificationstore" "galaxy/notification/internal/adapters/redisstate" "galaxy/notification/internal/service/acceptintent" "galaxy/notification/internal/service/routestate" "galaxy/notification/internal/telemetry" ) // Store delegates each route-publisher method to either the durable state // store (PostgreSQL) or the lease store (Redis), preserving the umbrella // contract consumed by `worker.PushPublisher` and `worker.EmailPublisher`. type Store struct { state *notificationstore.Store leases *redisstate.LeaseStore } // New constructs one composite route-publisher store. Both dependencies are // required: the SQL store owns route lifecycle and dead-letter persistence, // and the lease store owns the short-lived per-replica exclusivity hint // retained on Redis per PG_PLAN.md ยง5. func New(state *notificationstore.Store, leases *redisstate.LeaseStore) (*Store, error) { if state == nil { return nil, errors.New("new route publisher store: nil notification state store") } if leases == nil { return nil, errors.New("new route publisher store: nil lease store") } return &Store{state: state, leases: leases}, nil } // ListDueRoutes delegates to the SQL store. func (store *Store) ListDueRoutes(ctx context.Context, now time.Time, limit int64) ([]routestate.ScheduledRoute, error) { return store.state.ListDueRoutes(ctx, now, limit) } // TryAcquireRouteLease delegates to the Redis lease store. func (store *Store) TryAcquireRouteLease(ctx context.Context, notificationID string, routeID string, token string, ttl time.Duration) (bool, error) { return store.leases.TryAcquireRouteLease(ctx, notificationID, routeID, token, ttl) } // ReleaseRouteLease delegates to the Redis lease store. func (store *Store) ReleaseRouteLease(ctx context.Context, notificationID string, routeID string, token string) error { return store.leases.ReleaseRouteLease(ctx, notificationID, routeID, token) } // GetNotification delegates to the SQL store. func (store *Store) GetNotification(ctx context.Context, notificationID string) (acceptintent.NotificationRecord, bool, error) { return store.state.GetNotification(ctx, notificationID) } // GetRoute delegates to the SQL store. func (store *Store) GetRoute(ctx context.Context, notificationID string, routeID string) (acceptintent.NotificationRoute, bool, error) { return store.state.GetRoute(ctx, notificationID, routeID) } // CompleteRoutePublished delegates to the SQL store. func (store *Store) CompleteRoutePublished(ctx context.Context, input routestate.CompleteRoutePublishedInput) error { return store.state.CompleteRoutePublished(ctx, input) } // CompleteRouteFailed delegates to the SQL store. func (store *Store) CompleteRouteFailed(ctx context.Context, input routestate.CompleteRouteFailedInput) error { return store.state.CompleteRouteFailed(ctx, input) } // CompleteRouteDeadLetter delegates to the SQL store. func (store *Store) CompleteRouteDeadLetter(ctx context.Context, input routestate.CompleteRouteDeadLetterInput) error { return store.state.CompleteRouteDeadLetter(ctx, input) } // ReadRouteScheduleSnapshot delegates to the SQL store. func (store *Store) ReadRouteScheduleSnapshot(ctx context.Context) (telemetry.RouteScheduleSnapshot, error) { return store.state.ReadRouteScheduleSnapshot(ctx) }