feat: use postgres
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user