Add an explicit registry with capability hooks for migrations, routes, workflows, config, and UI assets. Move FedWiki fully and Stripe's separable store, workflow, and webhook pieces under internal/integrations. Drive startup wiring from declarations, including config validation, secret file pairs, CSRF exemptions, UI composition, and workflow startup. Move integration DB roles and grants into their owning migration streams, and route outbox writes through a shared enqueue helper.
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. providerSlug 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, providerSlug, actionType string, payload any) error {
|
|
payloadJSON, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal outbox payload for %s/%s: %w", providerSlug, actionType, err)
|
|
}
|
|
if _, err := db.ExecContext(ctx,
|
|
`INSERT INTO core.outbox (provider, action_type, payload) VALUES ($1, $2, $3)`,
|
|
providerSlug, actionType, payloadJSON,
|
|
); err != nil {
|
|
return fmt.Errorf("enqueue %s/%s outbox entry: %w", providerSlug, actionType, err)
|
|
}
|
|
return nil
|
|
}
|