119 lines
4.2 KiB
Go
119 lines
4.2 KiB
Go
package notificationstore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"galaxy/notification/internal/api/intentstream"
|
|
"galaxy/notification/internal/service/acceptintent"
|
|
)
|
|
|
|
// Compile-time confirmation that *Store satisfies acceptintent.Store. The
|
|
// runtime wiring depends on this so the accept-intent service can consume
|
|
// the PostgreSQL adapter directly.
|
|
var _ acceptintent.Store = (*Store)(nil)
|
|
|
|
// CreateAcceptance writes one notification record together with its derived
|
|
// route slots inside one BEGIN … COMMIT transaction. Idempotency races
|
|
// surface as `acceptintent.ErrConflict`.
|
|
func (store *Store) CreateAcceptance(ctx context.Context, input acceptintent.CreateAcceptanceInput) error {
|
|
if store == nil {
|
|
return errors.New("create notification acceptance: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return errors.New("create notification acceptance: nil context")
|
|
}
|
|
if err := input.Validate(); err != nil {
|
|
return fmt.Errorf("create notification acceptance: %w", err)
|
|
}
|
|
|
|
return store.withTx(ctx, "create notification acceptance", func(ctx context.Context, tx *sql.Tx) error {
|
|
if err := insertRecord(ctx, tx, input.Notification, input.Idempotency.ExpiresAt); err != nil {
|
|
if isUniqueViolation(err) {
|
|
return acceptintent.ErrConflict
|
|
}
|
|
return fmt.Errorf("create notification acceptance: insert record: %w", err)
|
|
}
|
|
for index, route := range input.Routes {
|
|
if err := insertRoute(ctx, tx, route); err != nil {
|
|
return fmt.Errorf("create notification acceptance: insert route[%d]: %w", index, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetIdempotency loads one accepted idempotency reservation. Because the
|
|
// records row IS the idempotency reservation, the lookup keys on
|
|
// `(producer, idempotency_key)` and projects the relevant subset of the row
|
|
// into an IdempotencyRecord.
|
|
func (store *Store) GetIdempotency(ctx context.Context, producer intentstream.Producer, idempotencyKey string) (acceptintent.IdempotencyRecord, bool, error) {
|
|
if store == nil {
|
|
return acceptintent.IdempotencyRecord{}, false, errors.New("get notification idempotency: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return acceptintent.IdempotencyRecord{}, false, errors.New("get notification idempotency: nil context")
|
|
}
|
|
|
|
operationCtx, cancel, err := store.operationContext(ctx, "get notification idempotency")
|
|
if err != nil {
|
|
return acceptintent.IdempotencyRecord{}, false, err
|
|
}
|
|
defer cancel()
|
|
|
|
scanned, found, err := loadIdempotencyByKey(operationCtx, store.db, string(producer), idempotencyKey)
|
|
if err != nil {
|
|
return acceptintent.IdempotencyRecord{}, false, err
|
|
}
|
|
if !found {
|
|
return acceptintent.IdempotencyRecord{}, false, nil
|
|
}
|
|
return idempotencyRecordFromScanned(scanned), true, nil
|
|
}
|
|
|
|
// GetNotification loads one accepted notification by NotificationID.
|
|
func (store *Store) GetNotification(ctx context.Context, notificationID string) (acceptintent.NotificationRecord, bool, error) {
|
|
if store == nil {
|
|
return acceptintent.NotificationRecord{}, false, errors.New("get notification record: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return acceptintent.NotificationRecord{}, false, errors.New("get notification record: nil context")
|
|
}
|
|
|
|
operationCtx, cancel, err := store.operationContext(ctx, "get notification record")
|
|
if err != nil {
|
|
return acceptintent.NotificationRecord{}, false, err
|
|
}
|
|
defer cancel()
|
|
|
|
scanned, found, err := loadRecord(operationCtx, store.db, notificationID)
|
|
if err != nil {
|
|
return acceptintent.NotificationRecord{}, false, err
|
|
}
|
|
if !found {
|
|
return acceptintent.NotificationRecord{}, false, nil
|
|
}
|
|
return scanned.Record, true, nil
|
|
}
|
|
|
|
// GetRoute loads one accepted notification route by `(notificationID,
|
|
// routeID)`. Required by the publisher worker contracts.
|
|
func (store *Store) GetRoute(ctx context.Context, notificationID string, routeID string) (acceptintent.NotificationRoute, bool, error) {
|
|
if store == nil {
|
|
return acceptintent.NotificationRoute{}, false, errors.New("get notification route: nil store")
|
|
}
|
|
if ctx == nil {
|
|
return acceptintent.NotificationRoute{}, false, errors.New("get notification route: nil context")
|
|
}
|
|
|
|
operationCtx, cancel, err := store.operationContext(ctx, "get notification route")
|
|
if err != nil {
|
|
return acceptintent.NotificationRoute{}, false, err
|
|
}
|
|
defer cancel()
|
|
|
|
return loadRoute(operationCtx, store.db, notificationID, routeID)
|
|
}
|