Replace the entity slugs on organizations, workspaces, resource pools, and plan ladders with nullable `key` columns and add keys to products, prices, and entitlement sets. Rename `providers.slug` to `provider` and add partial unique indexes for system and org role names. Assign invoice numbers per billing account from a gapless transactional counter; Stripe's number moves to the invoice mapping as an external reference. Seeds, fixtures, and the operator lookup address rows by key, and the returning-login resync no longer blanks a display name when the IdP sends no `name` claim.
52 lines
2.4 KiB
Go
52 lines
2.4 KiB
Go
package integration
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// Enqueue inserts a pending core.outbox row — the transactional dispatch
|
|
// handoff ratified in design.md Decision 4 for the integration-extraction
|
|
// change: a domain write and its outbox row commit atomically inside the
|
|
// caller's Postgres transaction, and a separate poller (e.g. the Stripe
|
|
// outbox drainer, internal/integrations/stripe/workflows's
|
|
// PollIntegrationOutbox) executes the action against the external provider
|
|
// later.
|
|
//
|
|
// This is the only supported write path into core.outbox; core code must
|
|
// not INSERT into it directly (see Decision 4's "concession to
|
|
// coherence"). It replaces the raw `INSERT INTO core.outbox` statements
|
|
// formerly hand-written in internal/server/operator_billing.go and
|
|
// internal/integrations/stripe/store/ensure_customer.go.
|
|
//
|
|
// db accepts a *sql.DB or a *sql.Tx via this package's DBTX interface
|
|
// (generated by sqlc for the provider-registry querier but equally
|
|
// satisfied by both). Pass a *sql.Tx to keep the enqueue atomic with the
|
|
// caller's other writes in the same transaction — the pattern both
|
|
// converted call sites rely on. A single logical action (e.g. "create the
|
|
// product, then its price") that needs more than one outbox row is two
|
|
// separate Enqueue calls against the same *sql.Tx, not a batch API: this
|
|
// keeps the helper's shape simple and the atomicity guarantee is already
|
|
// provided by the shared transaction.
|
|
//
|
|
// payload is marshaled to JSON here — pass a struct or map, not
|
|
// pre-marshaled bytes. providerKey and actionType are the outbox's
|
|
// existing free-form vocabulary (e.g. "stripe" / "create_stripe_customer");
|
|
// this helper does not validate or constrain that vocabulary (the Stripe
|
|
// action-type vocabulary baked into core is the payments-seam follow-up
|
|
// noted in design.md Decision 4, out of scope here).
|
|
func Enqueue(ctx context.Context, db DBTX, providerKey, actionType string, payload any) error {
|
|
payloadJSON, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal outbox payload for %s/%s: %w", providerKey, actionType, err)
|
|
}
|
|
if _, err := db.ExecContext(ctx,
|
|
`INSERT INTO core.outbox (provider, action_type, payload) VALUES ($1, $2, $3)`,
|
|
providerKey, actionType, payloadJSON,
|
|
); err != nil {
|
|
return fmt.Errorf("enqueue %s/%s outbox entry: %w", providerKey, actionType, err)
|
|
}
|
|
return nil
|
|
}
|