Help us shape the future of Go! Take the 2026 Go Developer Survey

webhook

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: MIT Imports: 38 Imported by: 0

README

Arandu

Arandu Webhook

Webhooks both ways for an Arandu application, in one package: what spatie/laravel-webhook-server and spatie/laravel-webhook-client are in Laravel.

  • Sending. A tenant keeps endpoints: an address, the events it subscribes to, a signing secret and extra headers, both sealed at rest. The events outbox delivers each event to the endpoints subscribed to it, signed, through hesape/webhook's Manager: one durable record and one queued job per event and endpoint, a transport that refuses private and loopback addresses, retries with backoff and Retry-After. A call can also be built in code, as webhook-server's WebhookCall::create().
  • Receiving. A receiver per system that calls the application. A call is checked against its signature, offered to a profile, stored with the headers the receiver keeps, answered at once, and processed on the queue by the receiver's processor. Old calls are cleaned on a schedule.

Parity with Spatie

laravel-webhook-server
Spatie Here
WebhookCall::create()->url()->payload()->useSecret()->dispatch() module.Outgoing().URL().Payload().UseSecret().Dispatch(ctx, g)
dispatchIf, dispatchUnless DispatchIf, DispatchUnless
uuid() UUID(): one identity to one address is delivered once
withHeaders() WithHeaders(), refusing the headers the delivery writes
sendRawBody() RawBody()
DefaultSigner: hex HMAC-SHA256 of the body in Signature SignWithHMAC(header) on a call, signer: "hmac" on an endpoint
useTimestamp() always: the Arandu signature covers a timestamp and the delivery id
tries, ExponentialBackoffStrategy hesape/webhook's retries: 5 tries, 5 s to 10 min, and the receiver's Retry-After
timeout_in_seconds, verify_ssl Server.Timeout; TLS is always verified
WebhookCallSucceededEvent, WebhookCallFailedEvent, FinalWebhookCallFailedEvent the delivery's record: EndpointService.Deliveries and GET …/deliveries
(none: the application keeps its own endpoints) endpoints per tenant, sealed secret and headers, subscriptions, a test call, the events outbox publisher
laravel-webhook-client
Spatie Here
configs[] in config/webhook-client.php Config.Receivers
Route::webhooks('url', 'name') the receiver's route, ReceivePrefix + "/" + Name
signing_secret, signature_header_name Receiver.Secret (and PreviousSecrets during a rotation), HMAC{Header}
DefaultSignatureValidator HMAC{Header: "Signature"}, the default; Prefix reads sha256=… signatures
SignatureValidator SignatureValidator, and Arandu{} for hesape's scheme with replay protection
InvalidWebhookSignatureEvent Receiver.OnInvalidSignature
WebhookProfile, ProcessEverythingWebhookProfile Profile, ProcessEverything
RespondsToWebhook, DefaultRespondsTo Responder, RespondOK: 200 {"message":"ok"}
store_headers Receiver.StoreHeaders, "*" for every header
process_webhook_job Receiver.Process, a Processor run by the queue
WebhookCall model, saveException, clearException Call, with Status, Attempts and Exception
delete_after_days with model:prune Config.DeleteAfterDays and the scheduled webhook.calls.clean
What differs, and why
  • The tenant. Every endpoint and every call belongs to a tenant. A management route takes it from the Grant of the session; a received call takes it from its Receiver, which the application configured, once the signature checked. Nothing in a request chooses it.
  • The body of a received call is kept exactly as it arrived, because those are the bytes the signature was checked over. Call.JSON and Call.Form decode it. Attachments are not stored.
  • An invalid signature is answered 403, not 500: sending the same bytes again cannot make them valid, and a 5xx invites the sender to retry.
  • Outgoing calls are POST with a JSON body, which is what hesape/webhook sends. There is no dispatchSync: a delivery is always a durable record and a job, so a crash between the two cannot lose one.
  • No events per attempt. hesape/webhook keeps a record of every delivery instead; reading it is how a panel shows the last answer of an endpoint.

Install

go get github.com/hyz-is/arandu-webhook@v0.1.0

It needs framework v0.56 and hesape v0.58.

Wire it

There is no service provider, no container and no discovery: these are the lines in bootstrap/app.go, and there are no others.

import (
	webhook "github.com/hyz-is/arandu-webhook"
)
	webhookModule, err := webhook.New(webhook.Config{
		// The application's key: the endpoints' secrets and headers are sealed
		// with a key derived from it.
		Key: fw.App.Key,
		Server: webhook.Server{
			Events:       []string{"order.paid", "order.refunded"},
			HeaderPrefix: "X-Acme",
			Secrets:      map[string]string{"partners": cfg.Webhook.PartnersSecret},
		},
		Receivers: []webhook.Receiver{{
			Name:    "billing",
			Tenant:  tenant, // whose calls these are, from the application's configuration
			Secret:  cfg.Billing.WebhookSecret,
			Process: billingWebhooks, // a webhook.Processor of the application
		}},
	}, db, sessions)
	if err != nil {
		return App{}, err
	}

Register it with the others:

	k.Register(
		// ...
		webhookModule,
	)

A receiver is called by a system with no session, so its path is exempted from the CSRF check:

	middleware.CSRFProtect(csrf, sessions.IDFromRequest, middleware.CSRFExcept(webhookModule.ReceivePrefix()+"/"))

The events outbox delivers to the endpoints through the publisher, and the worker runs the deliveries and the processing of received calls:

	// The relay takes one publisher; an application with several listeners
	// fans out to them, and the module's publisher is one of them.
	relay := events.NewRelay(outbox, webhookModule.Publisher(), events.RelayOptions{Locker: locker})

	// In the worker command, with the application's other handlers.
	if err := webhookModule.RegisterJobHandlers(worker); err != nil {
		return err
	}

Then, before the application serves:

aru migrate

The package owns webhook_server_endpoints and webhook_client_calls, and returns hesape/webhook's webhook_deliveries migration with them. An application that already runs hesape/webhook's Manager has that one applied, and the migrator skips a name it has run. The jobs table is the queue's, which the application already migrates.

Sending

An endpoint is created through EndpointService or the management routes. Its secret is answered once, in clear, for the person to give the receiver:

endpoint, secret, err := webhookModule.EndpointService().Create(ctx, who, webhook.EndpointRequest{
	Name:   "Billing",
	URL:    "https://billing.example.com/hooks",
	Events: []string{"order.paid"},
	Active: true,
})

Every event the outbox relays goes to the active endpoints of its tenant subscribed to its name, or to *. Each request carries:

header what it is
X-Arandu-Signature sha256= and the hex HMAC-SHA256 of timestamp.delivery-id.body
X-Arandu-Timestamp the Unix time of the attempt
X-Arandu-Delivery-ID the delivery, the same on every retry
the endpoint's SignatureHeader for signer: "hmac": the hex HMAC-SHA256 of the body, webhook-server's DefaultSigner

Server.HeaderPrefix renames the first three, X-Arandu- becoming X-Acme-. The signature covers no header's name, so a receiver verifies the same bytes.

A call built in code names one of Server.Secrets, and the Grant it is dispatched with carries hesape/webhook's dispatch action for its tenant:

err := webhookModule.Outgoing().
	URL("https://partner.example.com/hooks").
	Event("order.paid").
	Payload(map[string]any{"order": order.ID}).
	UseSecret("partners").
	SignWithHMAC("Signature").
	Dispatch(ctx, security.SystemGrant(hwebhook.ActionDispatch, tenant))

Receiving

var billingWebhooks = webhook.ProcessorFunc(func(ctx context.Context, g security.Grant, call *webhook.Call) error {
	var event struct {
		Type    string `json:"type"`
		Invoice string `json:"invoice"`
	}
	if err := call.JSON(&event); err != nil {
		return err
	}
	return invoices.MarkPaid(ctx, g, event.Invoice)
})

A call to POST /webhooks/billing:

  1. is read up to MaxBodyBytes (1 MiB), or answered 413;
  2. is checked by the Validator, or answered 403 after OnInvalidSignature;
  3. is offered to the Profile; one it turns down is answered and kept nowhere;
  4. is stored with the headers in StoreHeaders, and its processing is queued in the same transaction;
  5. is answered by the Responder, 200 {"message":"ok"} by default.

The worker then runs the Processor with the Grant of the call's tenant. An error leaves the call failed with its message, and the queue tries again until the receiver's Tries (3) are spent, waiting 10 s, then 100 s. A failed call is processed again from CallService.Process or POST …/calls/{id}/process.

The validators:

validator checks
HMAC{Header, Prefix} the hex HMAC-SHA256 of the body; Prefix: "sha256=" for GitHub-style headers
Arandu{HeaderPrefix, Tolerance} hesape's scheme, as another Arandu application sends it, refusing a timestamp more than 5 minutes away
SignatureValidatorFunc whatever the sender does

Every validator receives the current secret and the PreviousSecrets, so a sender can rotate without a gap.

Routes

The management routes sit behind RequireAuth and answer JSON. With a nil session store, New mounts none of them.

route name action
GET /webhook/endpoints webhook.endpoints.index EndpointList
POST /webhook/endpoints webhook.endpoints.store EndpointCreate
GET /webhook/endpoints/{id} webhook.endpoints.show EndpointView
PUT /webhook/endpoints/{id} webhook.endpoints.update EndpointUpdate
DELETE /webhook/endpoints/{id} webhook.endpoints.destroy EndpointDelete
GET /webhook/endpoints/{id}/secret webhook.endpoints.secret EndpointSecret
POST /webhook/endpoints/{id}/test webhook.endpoints.test EndpointTest
GET /webhook/endpoints/{id}/deliveries webhook.endpoints.deliveries EndpointView
GET /webhook/calls webhook.calls.index CallList
GET /webhook/calls/{id} webhook.calls.show CallView
POST /webhook/calls/{id}/process webhook.calls.process CallProcess
POST /webhooks/{receiver} webhook.receive.{receiver} the signature

Who may do what

EndpointPolicy and CallPolicy allow an action to a subject that carries it, in its own tenant, and never to a guest. webhook.Actions() lists the ten an application gives through arandu-permission. CallReceive and CallClean are given to nobody: they are the receiver's and the clean's own Grants.

Every method of EndpointService and CallService calls security.Authorize before it reaches the Model, and the Model takes its tenant from the Grant. The queries are generated by aru model:build from model.go: Endpoints(db) starts an *EndpointQuery, Calls(db) a *CallQuery, Deliveries(db) a *DeliveryQuery, and none of them runs without a Grant.

Tests

GOWORK=off go test -race ./...

The feature suite runs on SQLite with the real queue: a call received, stored, processed and reprocessed; a refused signature, a stale timestamp, a rotation; an event delivered and verified by a test server; and the module sending to its own receivers, both schemes, end to end.

License

MIT. See LICENSE.md.

Documentation

Overview

Package webhook is an Arandu module for webhooks both ways, as spatie/laravel-webhook-server and spatie/laravel-webhook-client are in Laravel, in one package.

Sending: endpoints a tenant subscribes to events, each with a sealed signing secret and sealed extra headers; the events outbox delivers to them through hesape/webhook -- one durable record and one queued job per event and endpoint, a signed request through a transport that refuses private addresses, retries with backoff and Retry-After. Outgoing builds a call in code, as webhook-server's WebhookCall. An endpoint signs with hesape's scheme or adds webhook-server's DefaultSigner: the hex HMAC-SHA256 of the body.

Receiving: a Receiver per system that calls the application, mounted at ReceivePrefix/<name>, as webhook-client's configs. A call is checked against its signature, offered to the Profile, stored with the headers the receiver keeps, and processed on the queue by the receiver's Processor; the sender gets the Responder's answer at once. Old calls are cleaned on a schedule.

The files are laid out by role:

module.go     -> registration, routes, handlers, jobs, schedule, migrations
config.go     -> what the application passes in
model.go      -> the endpoint, the received call, the delivery record
policy.go     -> who may do what
service.go    -> the use cases, and Model access after authorization
server.go     -> sending: the Manager underneath, Outgoing, the resolver
client.go     -> receiving: the pipeline and its processing job
signature.go  -> the signers, the validators and the seal

Index

Constants

View Source
const (
	// DefaultPrefix is where the management routes are mounted when Config
	// leaves Prefix empty.
	DefaultPrefix = "/webhook"
	// DefaultReceivePrefix is where the receivers are mounted when Config
	// leaves ReceivePrefix empty: a receiver named "billing" answers at
	// /webhooks/billing.
	DefaultReceivePrefix = "/webhooks"
	// DefaultPageSize is how many records one page answers with when Config
	// leaves PageSize at zero.
	DefaultPageSize = 25
	// MaxPageSize is the ceiling PageSize is refused above.
	MaxPageSize = 200
	// DefaultQueue is the queue the deliveries and the processing of received
	// calls travel on.
	DefaultQueue = "webhooks"
	// DefaultUserAgent is what an outgoing call says it is.
	DefaultUserAgent = "Arandu-Webhook/1.0"
	// DefaultTimeout is how long an outgoing call may take.
	DefaultTimeout = 10 * time.Second
	// DefaultDeleteAfterDays is how old a received call is when the clean
	// removes it, as webhook-client's delete_after_days.
	DefaultDeleteAfterDays = 30
	// DefaultCleanSpec is when the scheduled clean runs: every day at 03:20.
	DefaultCleanSpec = "20 3 * * *"
	// DefaultSignatureHeader is the header a body-HMAC signature travels in,
	// as webhook-server's and webhook-client's signature_header_name.
	DefaultSignatureHeader = "Signature"
	// DefaultMaxBodyBytes is the largest body a receiver reads.
	DefaultMaxBodyBytes = 1 << 20
	// DefaultTries is how many times a received call is processed before it
	// is left failed.
	DefaultTries = 3
	// MaxHeaders is how many extra headers an endpoint may carry.
	MaxHeaders = 20
	// MinSecretBytes is the shortest signing secret accepted.
	MinSecretBytes = 32
)

The defaults for the optional settings. They are constants rather than literals inside Config.withDefaults, so the value a reader finds here is the value the package uses.

View Source
const (
	// SignerArandu signs with hesape/webhook's scheme: X-Arandu-Signature,
	// sha256= over the timestamp, the delivery id and the body.
	SignerArandu = "arandu"
	// SignerHMAC adds webhook-server's DefaultSigner: the hex HMAC-SHA256 of
	// the body, in the endpoint's SignatureHeader.
	SignerHMAC = "hmac"
)

The signers of an endpoint.

View Source
const (
	// EndpointView is reading one endpoint, and its deliveries.
	EndpointView security.Action = "webhook.endpoint.view"
	// EndpointList is paging through the endpoints.
	EndpointList security.Action = "webhook.endpoint.list"
	// EndpointCreate is adding one.
	EndpointCreate security.Action = "webhook.endpoint.create"
	// EndpointUpdate is changing one, including turning it off.
	EndpointUpdate security.Action = "webhook.endpoint.update"
	// EndpointDelete is removing one.
	EndpointDelete security.Action = "webhook.endpoint.delete"
	// EndpointSecret is reading an endpoint's signing secret.
	EndpointSecret security.Action = "webhook.endpoint.secret"
	// EndpointTest is sending one webhook.test call to an endpoint.
	EndpointTest security.Action = "webhook.endpoint.test"

	// CallView is reading one received call.
	CallView security.Action = "webhook.call.view"
	// CallList is paging through the received calls.
	CallList security.Action = "webhook.call.list"
	// CallProcess is sending a received call to its processor again.
	CallProcess security.Action = "webhook.call.process"

	// CallReceive is storing a call whose signature checked, and CallClean
	// removing old calls. Nobody holds them: the receiver and the scheduled
	// clean issue their own Grant for the tenant the application configured,
	// and the policy refuses them to any subject.
	CallReceive security.Action = "webhook.call.receive"
	CallClean   security.Action = "webhook.call.clean"
)

The actions of the package. Constants rather than strings at the call site: a typo in an action name would silently authorize nothing, or worse, everything. arandu-permission fills a Subject's Actions with them.

View Source
const AllEvents = "*"

AllEvents is the subscription to every event.

View Source
const DefaultEvent = "webhook.call"

DefaultEvent is the event name of an Outgoing call that names none.

View Source
const DefaultTolerance = 5 * time.Minute

DefaultTolerance is how far a signed timestamp may be from now before an Arandu signature is refused as a replay.

View Source
const ProcessJob = "webhook.process"

ProcessJob is the queued job that hands a stored call to its receiver's Processor: webhook-client's ProcessWebhookJob.

View Source
const TestEvent = "webhook.test"

TestEvent is the event the test button sends.

Variables

View Source
var (
	// ErrInvalidSignature is a call whose signature does not check, as
	// webhook-client's InvalidWebhookSignature. It is answered 403: sending
	// the same bytes again cannot make it valid, so a 5xx would only invite
	// the sender to retry.
	ErrInvalidSignature = statusError{/* contains filtered or unexported fields */}
	// ErrBodyTooLarge is a body above the receiver's MaxBodyBytes.
	ErrBodyTooLarge = statusError{/* contains filtered or unexported fields */}
)

The refusals of a receiver. Each answers the sender with the status it carries and no detail.

View Source
var (
	// ErrURLNotSet is a call with no address.
	ErrURLNotSet = errors.New("webhook: the call has no URL")
	// ErrSecretNotSet is a call with no secret to sign it with.
	ErrSecretNotSet = errors.New("webhook: the call has no secret: name one of Config.Server.Secrets with UseSecret")
	// ErrUnknownSecret is a call naming a secret the configuration does not
	// have.
	ErrUnknownSecret = errors.New("webhook: no secret of that name in Config.Server.Secrets")
	// ErrReservedHeader is an extra header the delivery writes itself.
	ErrReservedHeader = errors.New("webhook: the header is written by the delivery itself")
)

The refusals of an Outgoing call, as webhook-server's CouldNotCallWebhook.

View Source
var ErrNoReceiver = errors.New("webhook: the call's receiver is not configured any more")

ErrNoReceiver is a call whose receiver the configuration no longer has.

View Source
var ErrNotFound = fmt.Errorf("webhook: record not found: %w", model.ErrModelNotFound)

ErrNotFound is returned when no row matches, including when the row exists in another tenant. The two cases are deliberately indistinguishable.

It wraps model.ErrModelNotFound, so the router answers it with 404.

Functions

func Actions

func Actions() []security.Action

Actions are the actions a person can be given, for arandu-permission to offer.

func SignBody

func SignBody(secret []byte, body []byte) string

SignBody returns webhook-server's DefaultSigner signature of a body: the hex HMAC-SHA256 under the secret.

Types

type Arandu

type Arandu struct {
	// HeaderPrefix is the senders' header prefix. Empty means X-Arandu.
	HeaderPrefix string
	// Tolerance is the accepted clock difference. Zero means
	// DefaultTolerance.
	Tolerance time.Duration
	// Now is the clock, replaceable by a test. Nil is time.Now.
	Now func() time.Time
}

Arandu checks hesape/webhook's scheme: the Signature, Timestamp and Delivery-ID headers under HeaderPrefix, sha256= over the timestamp, the delivery id and the body. A timestamp further than Tolerance from now is refused, so a captured call cannot be sent again later.

func (Arandu) Valid

func (v Arandu) Valid(r *http.Request, body []byte, secrets hwebhook.SecretSet) bool

Valid checks the timestamp's age, then the signature.

type Call

type Call struct {
	model.Model

	ID       string `db:"id"`
	TenantID string `db:"tenant_id"`

	// Name is the receiver it arrived at.
	Name string `db:"name"`
	// Method and URL are how it arrived.
	Method string `db:"method"`
	URL    string `db:"url"`
	// HeaderData is the JSON object of the headers the receiver keeps, by
	// lowercase name.
	HeaderData string `db:"headers"`
	// Payload is the body, exactly as it arrived.
	Payload string `db:"payload"`
	// Status is where its processing stands.
	Status CallStatus `db:"status"`
	// Attempts is how many times it was processed.
	Attempts int `db:"attempts"`
	// Exception is the message of the last processing that failed, empty
	// when the last one did not.
	Exception string `db:"exception"`
	// ProcessedAt is when it was processed; it means something only when
	// Status is CallProcessed.
	ProcessedAt time.Time `db:"processed_at"`

	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

Call is one webhook another system sent to a receiver of this application: webhook-client's WebhookCall. The body is kept exactly as it arrived, because those are the bytes the signature was checked over.

Calls, CallQuery and CallCollection are generated beside this file, in CallQuery.go, by aru model:build.

func (Call) Body

func (c Call) Body() []byte

Body returns the body as it arrived.

func (*Call) Failed

func (c *Call) Failed(err error, now time.Time)

Failed records what the processing answered, as webhook-client's saveException. Only the message is kept: a trace names the application's files, and the call row is read by whoever reads the panel.

func (Call) Form

func (c Call) Form() (url.Values, error)

Form parses a form-encoded body.

func (*Call) Fresh

func (row *Call) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Call, error)

Fresh reads the row again, as a new Call; it shadows the untyped one the embedded model promotes.

func (Call) Header

func (c Call) Header(name string) string

Header returns the first value of a kept header, by any case of its name.

func (Call) Headers

func (c Call) Headers() map[string][]string

Headers returns the kept headers, by lowercase name.

func (Call) JSON

func (c Call) JSON(v any) error

JSON decodes a JSON body into v.

func (*Call) Processed

func (c *Call) Processed(now time.Time)

Processed marks the call handled at now, and clears the last failure, as webhook-client's clearException.

func (*Call) Replicate

func (row *Call) Replicate(except ...string) (*Call, error)

Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.

type CallCollection

type CallCollection []*Call

CallCollection is rows of Call, as a query returns them.

func CallCollectionOf

func CallCollectionOf(rows model.Rows) CallCollection

CallCollectionOf converts rows of the table of Call. A nil input is a nil collection.

func (CallCollection) Contains

func (c CallCollection) Contains(key any) bool

func (CallCollection) Except

func (c CallCollection) Except(keys ...any) CallCollection

func (CallCollection) Find

func (c CallCollection) Find(key any) *Call

func (CallCollection) FindOrFail

func (c CallCollection) FindOrFail(key any) (*Call, error)

func (CallCollection) Fresh

func (c CallCollection) Fresh(ctx context.Context, g auth.Grant, with ...string) (CallCollection, error)

func (CallCollection) Load

func (c CallCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error

func (CallCollection) LoadCount

func (c CallCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error

func (CallCollection) LoadMissing

func (c CallCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error

func (CallCollection) ModelKeys

func (c CallCollection) ModelKeys() []any

func (CallCollection) Only

func (c CallCollection) Only(keys ...any) CallCollection

func (CallCollection) Pluck

func (c CallCollection) Pluck(column string) []any

func (CallCollection) Rows

func (c CallCollection) Rows() model.Rows

Rows converts the collection back to the core's rows.

func (CallCollection) ToArray

func (c CallCollection) ToArray() []map[string]any

func (CallCollection) Unique

func (c CallCollection) Unique() CallCollection

type CallFilter

type CallFilter struct {
	// Name is a receiver's name.
	Name string
	// Status is one of the CallStatus values.
	Status CallStatus
}

CallFilter narrows a listing of calls.

type CallPolicy

type CallPolicy struct{}

CallPolicy is the only authority over who reads and reprocesses a received Call.

func (CallPolicy) Can

Can decides whether the subject may perform the action on the call.

type CallQuery

type CallQuery struct {
	// contains filtered or unexported fields
}

CallQuery is a query on the table of Call.

The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Call rows or a value; there is no way to run one without a Grant.

func Calls

func Calls(db model.DB) *CallQuery

Calls starts a query on the table of Call, through db.

Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.

func (*CallQuery) AddSelect

func (q *CallQuery) AddSelect(columns ...any) *CallQuery

func (*CallQuery) Avg

func (q *CallQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)

func (*CallQuery) Base

func (q *CallQuery) Base() *model.Builder

Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.

func (*CallQuery) Chunk

func (q *CallQuery) Chunk(ctx context.Context, g auth.Grant, count int, callback func(CallCollection, int) (bool, error)) error

func (*CallQuery) Clone

func (q *CallQuery) Clone() *CallQuery

Clone returns an independent copy of the query.

func (*CallQuery) Count

func (q *CallQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)

func (*CallQuery) Create

func (q *CallQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Call, error)

func (*CallQuery) Cursor

func (q *CallQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Call, error]

func (*CallQuery) CursorPaginate

func (q *CallQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (CallCollection, *pagination.CursorPage, error)

func (*CallQuery) Decrement

func (q *CallQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*CallQuery) Delete

func (q *CallQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)

func (*CallQuery) DoesntExist

func (q *CallQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)

func (*CallQuery) DoesntHave

func (q *CallQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) Each

func (q *CallQuery) Each(ctx context.Context, g auth.Grant, count int, callback func(*Call, int) (bool, error)) error

func (*CallQuery) Exists

func (q *CallQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)

func (*CallQuery) Find

func (q *CallQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Call, error)

func (*CallQuery) FindMany

func (q *CallQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (CallCollection, error)

func (*CallQuery) FindOrFail

func (q *CallQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Call, error)

func (*CallQuery) FindOrNew

func (q *CallQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Call, error)

func (*CallQuery) First

func (q *CallQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Call, error)

First returns the first row, or nil and no error when there is none.

func (*CallQuery) FirstOrCreate

func (q *CallQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Call, error)

func (*CallQuery) FirstOrFail

func (q *CallQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Call, error)

func (*CallQuery) FirstOrNew

func (q *CallQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Call, error)

func (*CallQuery) FirstWhere

func (q *CallQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Call, error)

func (*CallQuery) ForPage

func (q *CallQuery) ForPage(page, perPage int) *CallQuery

func (*CallQuery) ForceCreate

func (q *CallQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Call, error)

func (*CallQuery) Get

func (q *CallQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (CallCollection, error)

Get runs the query and returns the rows.

func (*CallQuery) GroupBy

func (q *CallQuery) GroupBy(groups ...any) *CallQuery

func (*CallQuery) Has

func (q *CallQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) Increment

func (q *CallQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*CallQuery) Insert

func (q *CallQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*CallQuery) InsertOrIgnore

func (q *CallQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*CallQuery) Join

func (q *CallQuery) Join(table any, first any, args ...any) *CallQuery

func (*CallQuery) Latest

func (q *CallQuery) Latest(column ...string) *CallQuery

func (*CallQuery) Limit

func (q *CallQuery) Limit(value int) *CallQuery

func (*CallQuery) Max

func (q *CallQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)

func (*CallQuery) Min

func (q *CallQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)

func (*CallQuery) New

func (q *CallQuery) New() (*Call, error)

New returns an empty Call on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.

func (*CallQuery) Offset

func (q *CallQuery) Offset(value int) *CallQuery

func (*CallQuery) Oldest

func (q *CallQuery) Oldest(column ...string) *CallQuery

func (*CallQuery) OrDoesntHave

func (q *CallQuery) OrDoesntHave(relation string) *CallQuery

func (*CallQuery) OrHas

func (q *CallQuery) OrHas(relation, operator string, count int) *CallQuery

func (*CallQuery) OrWhere

func (q *CallQuery) OrWhere(column any, args ...any) *CallQuery

OrWhere adds an or-where clause; a func(*CallQuery) in place of the column adds a group.

func (*CallQuery) OrWhereDoesntHave

func (q *CallQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) OrWhereHas

func (q *CallQuery) OrWhereHas(relation string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) OrWhereNot

func (q *CallQuery) OrWhereNot(column any, args ...any) *CallQuery

OrWhereNot adds an or-joined negated where clause, or a negated group.

func (*CallQuery) OrWhereRelation

func (q *CallQuery) OrWhereRelation(relation string, column any, args ...any) *CallQuery

func (*CallQuery) OrderBy

func (q *CallQuery) OrderBy(column any, direction ...string) *CallQuery

func (*CallQuery) OrderByDesc

func (q *CallQuery) OrderByDesc(column any) *CallQuery

func (*CallQuery) Paginate

func (q *CallQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (CallCollection, *pagination.LengthAwarePage, error)

func (*CallQuery) Pluck

func (q *CallQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)

func (*CallQuery) Select

func (q *CallQuery) Select(columns ...any) *CallQuery

func (*CallQuery) SelectRaw

func (q *CallQuery) SelectRaw(expression string, bindings ...any) *CallQuery

func (*CallQuery) SimplePaginate

func (q *CallQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (CallCollection, *pagination.Page, error)

func (*CallQuery) Sole

func (q *CallQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Call, error)

func (*CallQuery) SoleValue

func (q *CallQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)

func (*CallQuery) Sum

func (q *CallQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)

func (*CallQuery) Touch

func (q *CallQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)

func (*CallQuery) Update

func (q *CallQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)

func (*CallQuery) UpdateOrCreate

func (q *CallQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Call, error)

func (*CallQuery) Upsert

func (q *CallQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, uniqueBy, update []string) (int64, error)

func (*CallQuery) Value

func (q *CallQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)

func (*CallQuery) ValueOrFail

func (q *CallQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)

func (*CallQuery) Where

func (q *CallQuery) Where(column any, args ...any) *CallQuery

Where adds a where clause; a func(*CallQuery) in place of the column adds a group.

func (*CallQuery) WhereBelongsTo

func (q *CallQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *CallQuery

func (*CallQuery) WhereBetween

func (q *CallQuery) WhereBetween(column any, from, to any) *CallQuery

func (*CallQuery) WhereColumn

func (q *CallQuery) WhereColumn(first any, args ...any) *CallQuery

func (*CallQuery) WhereDoesntHave

func (q *CallQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) WhereExists

func (q *CallQuery) WhereExists(callback func(*query.Builder)) *CallQuery

func (*CallQuery) WhereHas

func (q *CallQuery) WhereHas(relation string, callback func(*query.Builder)) *CallQuery

func (*CallQuery) WhereHasCount

func (q *CallQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *CallQuery

func (*CallQuery) WhereIn

func (q *CallQuery) WhereIn(column any, values []any) *CallQuery

func (*CallQuery) WhereKey

func (q *CallQuery) WhereKey(id any) *CallQuery

func (*CallQuery) WhereNot

func (q *CallQuery) WhereNot(column any, args ...any) *CallQuery

WhereNot adds a negated where clause, or a negated group.

func (*CallQuery) WhereNotExists

func (q *CallQuery) WhereNotExists(callback func(*query.Builder)) *CallQuery

func (*CallQuery) WhereNotIn

func (q *CallQuery) WhereNotIn(column any, values []any) *CallQuery

func (*CallQuery) WhereNotNull

func (q *CallQuery) WhereNotNull(columns ...any) *CallQuery

func (*CallQuery) WhereNull

func (q *CallQuery) WhereNull(columns ...any) *CallQuery

func (*CallQuery) WhereRelation

func (q *CallQuery) WhereRelation(relation string, column any, args ...any) *CallQuery

func (*CallQuery) With

func (q *CallQuery) With(relations ...string) *CallQuery

func (*CallQuery) WithAvg

func (q *CallQuery) WithAvg(relation, column string) *CallQuery

func (*CallQuery) WithCount

func (q *CallQuery) WithCount(relations ...string) *CallQuery

func (*CallQuery) WithExists

func (q *CallQuery) WithExists(relation string) *CallQuery

func (*CallQuery) WithMax

func (q *CallQuery) WithMax(relation, column string) *CallQuery

func (*CallQuery) WithMin

func (q *CallQuery) WithMin(relation, column string) *CallQuery

func (*CallQuery) WithSum

func (q *CallQuery) WithSum(relation, column string) *CallQuery

func (*CallQuery) Without

func (q *CallQuery) Without(relations ...string) *CallQuery

func (*CallQuery) WithoutGlobalScope

func (q *CallQuery) WithoutGlobalScope(identifier string) *CallQuery

func (*CallQuery) WithoutGlobalScopes

func (q *CallQuery) WithoutGlobalScopes(identifiers ...string) *CallQuery

type CallResource

type CallResource struct {
	// contains filtered or unexported fields
}

CallResource is the list of fields one received call may answer with.

func NewCallResource

func NewCallResource(c *Call) CallResource

NewCallResource snapshots one call for the response.

func (CallResource) ToArray

func (r CallResource) ToArray() map[string]any

ToArray returns the fields that may leave, by name.

func (CallResource) With

func (r CallResource) With() map[string]any

With adds nothing beside the fields.

type CallService

type CallService struct {
	// contains filtered or unexported fields
}

CallService is the use cases of the receiving side's record: reading the calls, processing one again, and the clean. It owns the database handle; every exported method asks the policy before it reads or writes.

func (*CallService) Find

func (s *CallService) Find(ctx context.Context, actor security.Subject, id string) (*Call, error)

Find returns one received call.

func (*CallService) List

func (s *CallService) List(ctx context.Context, actor security.Subject, filter CallFilter, q data.Query) ([]*Call, error)

List returns a page of the tenant's received calls, newest first.

func (*CallService) Process

func (s *CallService) Process(ctx context.Context, actor security.Subject, id string) error

Process queues a stored call for its receiver's Processor again, whatever its status: the call that failed after a fix, or the one to run twice on purpose.

type CallStatus

type CallStatus string

CallStatus is where a received call stands.

const (
	// CallReceived is stored and waiting for the queue.
	CallReceived CallStatus = "received"
	// CallProcessed is handled.
	CallProcessed CallStatus = "processed"
	// CallFailed is the last processing having failed. The queue may still
	// retry it.
	CallFailed CallStatus = "failed"
)

type Config

type Config struct {
	// Prefix is the path the management routes are mounted under. Empty means
	// DefaultPrefix.
	Prefix string

	// PageSize is how many records one page answers with. Zero means
	// DefaultPageSize; anything above MaxPageSize is refused.
	PageSize int

	// Key is the application's key (APP_KEY). The endpoints' signing secrets
	// and extra headers are sealed with a key derived from it for this purpose
	// alone. Required, at least MinSecretBytes long.
	Key []byte

	// Server is the sending side.
	Server Server

	// ReceivePrefix is the path every receiver is mounted under. Empty means
	// DefaultReceivePrefix. The application exempts it from the CSRF check,
	// because a call from another system carries no session:
	//
	//	middleware.CSRFProtect(csrf, sessions.IDFromRequest, middleware.CSRFExcept(webhookModule.ReceivePrefix()+"/"))
	ReceivePrefix string

	// Receivers are the receiving side: webhook-client's configs, one per
	// system that calls this application.
	Receivers []Receiver

	// DeleteAfterDays is how old a received call is when the scheduled clean
	// removes it. Zero means DefaultDeleteAfterDays.
	DeleteAfterDays int

	// CleanSpec is the five-field cron expression the clean runs on. Empty
	// means DefaultCleanSpec; "-" schedules none.
	CleanSpec string
}

Config is what the application passes when it wires this package: the two config files of spatie/laravel-webhook-server and laravel-webhook-client, as one typed struct. A misspelled key in a map is a setting that silently keeps its default; a field that does not exist does not compile.

It names no tenant for the management routes: those sit behind RequireAuth, so their tenant is the one the Grant carries, from the session. A Receiver names its tenant, because a call from another system arrives with no session and its signature is what admits it.

func (Config) Validate

func (c Config) Validate() error

Validate reports what the configuration cannot be used with. It is called by New, so a setting that cannot work fails where it is wired rather than on the first request that needed it.

type Delivery

type Delivery struct {
	model.Model

	ID       string `db:"id"`
	TenantID string `db:"tenant_id"`

	EventID    string `db:"event_id"`
	EventName  string `db:"event_name"`
	EndpointID string `db:"endpoint_id"`
	URL        string `db:"url"`
	// Status is pending, processing, delivered or failed.
	Status   string `db:"status"`
	Attempts int64  `db:"attempts"`
	// Permanent is a failure retrying cannot fix.
	Permanent      Flag       `db:"permanent"`
	ResponseStatus *int64     `db:"response_status"`
	LastError      *string    `db:"last_error"`
	CreatedAt      time.Time  `db:"created_at"`
	UpdatedAt      time.Time  `db:"updated_at"`
	DeliveredAt    *time.Time `db:"delivered_at"`
}

Delivery is the record hesape/webhook keeps of one call to one endpoint: its status, its attempts, the receiver's last answer. This package reads it and never writes it; the worker does.

Deliveries, DeliveryQuery and DeliveryCollection are generated beside this file, in DeliveryQuery.go, by aru model:build.

func (*Delivery) Fresh

func (row *Delivery) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Delivery, error)

Fresh reads the row again, as a new Delivery; it shadows the untyped one the embedded model promotes.

func (*Delivery) Replicate

func (row *Delivery) Replicate(except ...string) (*Delivery, error)

Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.

type DeliveryCollection

type DeliveryCollection []*Delivery

DeliveryCollection is rows of Delivery, as a query returns them.

func DeliveryCollectionOf

func DeliveryCollectionOf(rows model.Rows) DeliveryCollection

DeliveryCollectionOf converts rows of the table of Delivery. A nil input is a nil collection.

func (DeliveryCollection) Contains

func (c DeliveryCollection) Contains(key any) bool

func (DeliveryCollection) Except

func (c DeliveryCollection) Except(keys ...any) DeliveryCollection

func (DeliveryCollection) Find

func (c DeliveryCollection) Find(key any) *Delivery

func (DeliveryCollection) FindOrFail

func (c DeliveryCollection) FindOrFail(key any) (*Delivery, error)

func (DeliveryCollection) Fresh

func (DeliveryCollection) Load

func (c DeliveryCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error

func (DeliveryCollection) LoadCount

func (c DeliveryCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error

func (DeliveryCollection) LoadMissing

func (c DeliveryCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error

func (DeliveryCollection) ModelKeys

func (c DeliveryCollection) ModelKeys() []any

func (DeliveryCollection) Only

func (c DeliveryCollection) Only(keys ...any) DeliveryCollection

func (DeliveryCollection) Pluck

func (c DeliveryCollection) Pluck(column string) []any

func (DeliveryCollection) Rows

func (c DeliveryCollection) Rows() model.Rows

Rows converts the collection back to the core's rows.

func (DeliveryCollection) ToArray

func (c DeliveryCollection) ToArray() []map[string]any

func (DeliveryCollection) Unique

type DeliveryQuery

type DeliveryQuery struct {
	// contains filtered or unexported fields
}

DeliveryQuery is a query on the table of Delivery.

The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Delivery rows or a value; there is no way to run one without a Grant.

func Deliveries

func Deliveries(db model.DB) *DeliveryQuery

Deliveries starts a query on the table of Delivery, through db.

Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.

func (*DeliveryQuery) AddSelect

func (q *DeliveryQuery) AddSelect(columns ...any) *DeliveryQuery

func (*DeliveryQuery) Avg

func (q *DeliveryQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)

func (*DeliveryQuery) Base

func (q *DeliveryQuery) Base() *model.Builder

Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.

func (*DeliveryQuery) Chunk

func (q *DeliveryQuery) Chunk(ctx context.Context, g auth.Grant, count int, callback func(DeliveryCollection, int) (bool, error)) error

func (*DeliveryQuery) Clone

func (q *DeliveryQuery) Clone() *DeliveryQuery

Clone returns an independent copy of the query.

func (*DeliveryQuery) Count

func (q *DeliveryQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)

func (*DeliveryQuery) Create

func (q *DeliveryQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Delivery, error)

func (*DeliveryQuery) Cursor

func (q *DeliveryQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Delivery, error]

func (*DeliveryQuery) CursorPaginate

func (q *DeliveryQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (DeliveryCollection, *pagination.CursorPage, error)

func (*DeliveryQuery) Decrement

func (q *DeliveryQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*DeliveryQuery) Delete

func (q *DeliveryQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)

func (*DeliveryQuery) DoesntExist

func (q *DeliveryQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)

func (*DeliveryQuery) DoesntHave

func (q *DeliveryQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) Each

func (q *DeliveryQuery) Each(ctx context.Context, g auth.Grant, count int, callback func(*Delivery, int) (bool, error)) error

func (*DeliveryQuery) Exists

func (q *DeliveryQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)

func (*DeliveryQuery) Find

func (q *DeliveryQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Delivery, error)

func (*DeliveryQuery) FindMany

func (q *DeliveryQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (DeliveryCollection, error)

func (*DeliveryQuery) FindOrFail

func (q *DeliveryQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Delivery, error)

func (*DeliveryQuery) FindOrNew

func (q *DeliveryQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Delivery, error)

func (*DeliveryQuery) First

func (q *DeliveryQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Delivery, error)

First returns the first row, or nil and no error when there is none.

func (*DeliveryQuery) FirstOrCreate

func (q *DeliveryQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Delivery, error)

func (*DeliveryQuery) FirstOrFail

func (q *DeliveryQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Delivery, error)

func (*DeliveryQuery) FirstOrNew

func (q *DeliveryQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Delivery, error)

func (*DeliveryQuery) FirstWhere

func (q *DeliveryQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Delivery, error)

func (*DeliveryQuery) ForPage

func (q *DeliveryQuery) ForPage(page, perPage int) *DeliveryQuery

func (*DeliveryQuery) ForceCreate

func (q *DeliveryQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Delivery, error)

func (*DeliveryQuery) Get

func (q *DeliveryQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (DeliveryCollection, error)

Get runs the query and returns the rows.

func (*DeliveryQuery) GroupBy

func (q *DeliveryQuery) GroupBy(groups ...any) *DeliveryQuery

func (*DeliveryQuery) Has

func (q *DeliveryQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) Increment

func (q *DeliveryQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*DeliveryQuery) Insert

func (q *DeliveryQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*DeliveryQuery) InsertOrIgnore

func (q *DeliveryQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*DeliveryQuery) Join

func (q *DeliveryQuery) Join(table any, first any, args ...any) *DeliveryQuery

func (*DeliveryQuery) Latest

func (q *DeliveryQuery) Latest(column ...string) *DeliveryQuery

func (*DeliveryQuery) Limit

func (q *DeliveryQuery) Limit(value int) *DeliveryQuery

func (*DeliveryQuery) Max

func (q *DeliveryQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)

func (*DeliveryQuery) Min

func (q *DeliveryQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)

func (*DeliveryQuery) New

func (q *DeliveryQuery) New() (*Delivery, error)

New returns an empty Delivery on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.

func (*DeliveryQuery) Offset

func (q *DeliveryQuery) Offset(value int) *DeliveryQuery

func (*DeliveryQuery) Oldest

func (q *DeliveryQuery) Oldest(column ...string) *DeliveryQuery

func (*DeliveryQuery) OrDoesntHave

func (q *DeliveryQuery) OrDoesntHave(relation string) *DeliveryQuery

func (*DeliveryQuery) OrHas

func (q *DeliveryQuery) OrHas(relation, operator string, count int) *DeliveryQuery

func (*DeliveryQuery) OrWhere

func (q *DeliveryQuery) OrWhere(column any, args ...any) *DeliveryQuery

OrWhere adds an or-where clause; a func(*DeliveryQuery) in place of the column adds a group.

func (*DeliveryQuery) OrWhereDoesntHave

func (q *DeliveryQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) OrWhereHas

func (q *DeliveryQuery) OrWhereHas(relation string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) OrWhereNot

func (q *DeliveryQuery) OrWhereNot(column any, args ...any) *DeliveryQuery

OrWhereNot adds an or-joined negated where clause, or a negated group.

func (*DeliveryQuery) OrWhereRelation

func (q *DeliveryQuery) OrWhereRelation(relation string, column any, args ...any) *DeliveryQuery

func (*DeliveryQuery) OrderBy

func (q *DeliveryQuery) OrderBy(column any, direction ...string) *DeliveryQuery

func (*DeliveryQuery) OrderByDesc

func (q *DeliveryQuery) OrderByDesc(column any) *DeliveryQuery

func (*DeliveryQuery) Paginate

func (q *DeliveryQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (DeliveryCollection, *pagination.LengthAwarePage, error)

func (*DeliveryQuery) Pluck

func (q *DeliveryQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)

func (*DeliveryQuery) Select

func (q *DeliveryQuery) Select(columns ...any) *DeliveryQuery

func (*DeliveryQuery) SelectRaw

func (q *DeliveryQuery) SelectRaw(expression string, bindings ...any) *DeliveryQuery

func (*DeliveryQuery) SimplePaginate

func (q *DeliveryQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (DeliveryCollection, *pagination.Page, error)

func (*DeliveryQuery) Sole

func (q *DeliveryQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Delivery, error)

func (*DeliveryQuery) SoleValue

func (q *DeliveryQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)

func (*DeliveryQuery) Sum

func (q *DeliveryQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)

func (*DeliveryQuery) Touch

func (q *DeliveryQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)

func (*DeliveryQuery) Update

func (q *DeliveryQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)

func (*DeliveryQuery) UpdateOrCreate

func (q *DeliveryQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Delivery, error)

func (*DeliveryQuery) Upsert

func (q *DeliveryQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, uniqueBy, update []string) (int64, error)

func (*DeliveryQuery) Value

func (q *DeliveryQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)

func (*DeliveryQuery) ValueOrFail

func (q *DeliveryQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)

func (*DeliveryQuery) Where

func (q *DeliveryQuery) Where(column any, args ...any) *DeliveryQuery

Where adds a where clause; a func(*DeliveryQuery) in place of the column adds a group.

func (*DeliveryQuery) WhereBelongsTo

func (q *DeliveryQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *DeliveryQuery

func (*DeliveryQuery) WhereBetween

func (q *DeliveryQuery) WhereBetween(column any, from, to any) *DeliveryQuery

func (*DeliveryQuery) WhereColumn

func (q *DeliveryQuery) WhereColumn(first any, args ...any) *DeliveryQuery

func (*DeliveryQuery) WhereDoesntHave

func (q *DeliveryQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) WhereExists

func (q *DeliveryQuery) WhereExists(callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) WhereHas

func (q *DeliveryQuery) WhereHas(relation string, callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) WhereHasCount

func (q *DeliveryQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *DeliveryQuery

func (*DeliveryQuery) WhereIn

func (q *DeliveryQuery) WhereIn(column any, values []any) *DeliveryQuery

func (*DeliveryQuery) WhereKey

func (q *DeliveryQuery) WhereKey(id any) *DeliveryQuery

func (*DeliveryQuery) WhereNot

func (q *DeliveryQuery) WhereNot(column any, args ...any) *DeliveryQuery

WhereNot adds a negated where clause, or a negated group.

func (*DeliveryQuery) WhereNotExists

func (q *DeliveryQuery) WhereNotExists(callback func(*query.Builder)) *DeliveryQuery

func (*DeliveryQuery) WhereNotIn

func (q *DeliveryQuery) WhereNotIn(column any, values []any) *DeliveryQuery

func (*DeliveryQuery) WhereNotNull

func (q *DeliveryQuery) WhereNotNull(columns ...any) *DeliveryQuery

func (*DeliveryQuery) WhereNull

func (q *DeliveryQuery) WhereNull(columns ...any) *DeliveryQuery

func (*DeliveryQuery) WhereRelation

func (q *DeliveryQuery) WhereRelation(relation string, column any, args ...any) *DeliveryQuery

func (*DeliveryQuery) With

func (q *DeliveryQuery) With(relations ...string) *DeliveryQuery

func (*DeliveryQuery) WithAvg

func (q *DeliveryQuery) WithAvg(relation, column string) *DeliveryQuery

func (*DeliveryQuery) WithCount

func (q *DeliveryQuery) WithCount(relations ...string) *DeliveryQuery

func (*DeliveryQuery) WithExists

func (q *DeliveryQuery) WithExists(relation string) *DeliveryQuery

func (*DeliveryQuery) WithMax

func (q *DeliveryQuery) WithMax(relation, column string) *DeliveryQuery

func (*DeliveryQuery) WithMin

func (q *DeliveryQuery) WithMin(relation, column string) *DeliveryQuery

func (*DeliveryQuery) WithSum

func (q *DeliveryQuery) WithSum(relation, column string) *DeliveryQuery

func (*DeliveryQuery) Without

func (q *DeliveryQuery) Without(relations ...string) *DeliveryQuery

func (*DeliveryQuery) WithoutGlobalScope

func (q *DeliveryQuery) WithoutGlobalScope(identifier string) *DeliveryQuery

func (*DeliveryQuery) WithoutGlobalScopes

func (q *DeliveryQuery) WithoutGlobalScopes(identifiers ...string) *DeliveryQuery

type DeliveryResource

type DeliveryResource struct {
	// contains filtered or unexported fields
}

DeliveryResource is the list of fields one delivery may answer with. The headers, the body and the secret reference stay in the record.

func NewDeliveryResource

func NewDeliveryResource(d *Delivery) DeliveryResource

NewDeliveryResource snapshots one delivery for the response.

func (DeliveryResource) ToArray

func (r DeliveryResource) ToArray() map[string]any

ToArray returns the fields that may leave, by name.

func (DeliveryResource) With

func (r DeliveryResource) With() map[string]any

With adds nothing beside the fields.

type Endpoint

type Endpoint struct {
	model.Model

	ID       string `db:"id"`
	TenantID string `db:"tenant_id"`

	// Name is what a person calls the endpoint.
	Name string `db:"name"`
	// URL is the absolute http or https address the calls go to.
	URL string `db:"url"`
	// Events is the comma-separated list of event names it receives; "*"
	// receives every event.
	Events string `db:"events"`
	// Signer is how its calls are signed: SignerArandu or SignerHMAC.
	Signer string `db:"signer"`
	// SignatureHeader is the header a SignerHMAC signature travels in.
	SignatureHeader string `db:"signature_header"`
	// Secret is the sealed signing secret. It never leaves in a response.
	Secret string `db:"secret"`
	// Headers is the sealed JSON object of extra headers, empty when none.
	Headers string `db:"headers"`
	// Active is whether it receives events.
	Active Flag `db:"active"`

	CreatedAt time.Time `db:"created_at"`
	UpdatedAt time.Time `db:"updated_at"`
}

Endpoint is one destination the application sends to: an address, the events it subscribes to, how its calls are signed, and the extra headers they carry. The signing secret and the headers are sealed at rest.

Endpoints, EndpointQuery and EndpointCollection are generated beside this file, in EndpointQuery.go, by aru model:build.

func (Endpoint) EventList

func (e Endpoint) EventList() []string

EventList returns the events the endpoint subscribes to.

func (*Endpoint) Fresh

func (row *Endpoint) Fresh(ctx context.Context, g auth.Grant, with ...string) (*Endpoint, error)

Fresh reads the row again, as a new Endpoint; it shadows the untyped one the embedded model promotes.

func (Endpoint) Receives

func (e Endpoint) Receives(event string) bool

Receives reports whether an event of this name goes to the endpoint.

func (*Endpoint) Replicate

func (row *Endpoint) Replicate(except ...string) (*Endpoint, error)

Replicate returns an unsaved copy of the row without its key; it shadows the untyped one the embedded model promotes.

type EndpointCollection

type EndpointCollection []*Endpoint

EndpointCollection is rows of Endpoint, as a query returns them.

func EndpointCollectionOf

func EndpointCollectionOf(rows model.Rows) EndpointCollection

EndpointCollectionOf converts rows of the table of Endpoint. A nil input is a nil collection.

func (EndpointCollection) Contains

func (c EndpointCollection) Contains(key any) bool

func (EndpointCollection) Except

func (c EndpointCollection) Except(keys ...any) EndpointCollection

func (EndpointCollection) Find

func (c EndpointCollection) Find(key any) *Endpoint

func (EndpointCollection) FindOrFail

func (c EndpointCollection) FindOrFail(key any) (*Endpoint, error)

func (EndpointCollection) Fresh

func (EndpointCollection) Load

func (c EndpointCollection) Load(ctx context.Context, g auth.Grant, relations ...string) error

func (EndpointCollection) LoadCount

func (c EndpointCollection) LoadCount(ctx context.Context, g auth.Grant, relations ...string) error

func (EndpointCollection) LoadMissing

func (c EndpointCollection) LoadMissing(ctx context.Context, g auth.Grant, relations ...string) error

func (EndpointCollection) ModelKeys

func (c EndpointCollection) ModelKeys() []any

func (EndpointCollection) Only

func (c EndpointCollection) Only(keys ...any) EndpointCollection

func (EndpointCollection) Pluck

func (c EndpointCollection) Pluck(column string) []any

func (EndpointCollection) Rows

func (c EndpointCollection) Rows() model.Rows

Rows converts the collection back to the core's rows.

func (EndpointCollection) ToArray

func (c EndpointCollection) ToArray() []map[string]any

func (EndpointCollection) Unique

type EndpointPolicy

type EndpointPolicy struct{}

EndpointPolicy is the only authority over who does what with an Endpoint.

It denies unless the subject of the endpoint's tenant carries the action.

func (EndpointPolicy) Can

Can decides whether the subject may perform the action on the endpoint.

type EndpointQuery

type EndpointQuery struct {
	// contains filtered or unexported fields
}

EndpointQuery is a query on the table of Endpoint.

The methods that build return the query, so they chain. The methods that run take a context and an auth.Grant, and return Endpoint rows or a value; there is no way to run one without a Grant.

func Endpoints

func Endpoints(db model.DB) *EndpointQuery

Endpoints starts a query on the table of Endpoint, through db.

Global scopes are on and, unless the table is Global, every statement is filtered by the tenant of the Grant its terminal takes.

func (*EndpointQuery) AddSelect

func (q *EndpointQuery) AddSelect(columns ...any) *EndpointQuery

func (*EndpointQuery) Avg

func (q *EndpointQuery) Avg(ctx context.Context, g auth.Grant, column any) (any, error)

func (*EndpointQuery) Base

func (q *EndpointQuery) Base() *model.Builder

Base returns the core builder the query forwards to. It is for code in this package -- a local scope in the custom block of model.go -- and for the factory, which builds rows through it.

func (*EndpointQuery) Chunk

func (q *EndpointQuery) Chunk(ctx context.Context, g auth.Grant, count int, callback func(EndpointCollection, int) (bool, error)) error

func (*EndpointQuery) Clone

func (q *EndpointQuery) Clone() *EndpointQuery

Clone returns an independent copy of the query.

func (*EndpointQuery) Count

func (q *EndpointQuery) Count(ctx context.Context, g auth.Grant, columns ...any) (int64, error)

func (*EndpointQuery) Create

func (q *EndpointQuery) Create(ctx context.Context, g auth.Grant, attributes map[string]any) (*Endpoint, error)

func (*EndpointQuery) Cursor

func (q *EndpointQuery) Cursor(ctx context.Context, g auth.Grant) iter.Seq2[*Endpoint, error]

func (*EndpointQuery) CursorPaginate

func (q *EndpointQuery) CursorPaginate(ctx context.Context, g auth.Grant, perPage int, cursor *pagination.Cursor, opts pagination.Options, columns ...any) (EndpointCollection, *pagination.CursorPage, error)

func (*EndpointQuery) Decrement

func (q *EndpointQuery) Decrement(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*EndpointQuery) Delete

func (q *EndpointQuery) Delete(ctx context.Context, g auth.Grant) (int64, error)

func (*EndpointQuery) DoesntExist

func (q *EndpointQuery) DoesntExist(ctx context.Context, g auth.Grant) (bool, error)

func (*EndpointQuery) DoesntHave

func (q *EndpointQuery) DoesntHave(relation, boolean string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) Each

func (q *EndpointQuery) Each(ctx context.Context, g auth.Grant, count int, callback func(*Endpoint, int) (bool, error)) error

func (*EndpointQuery) Exists

func (q *EndpointQuery) Exists(ctx context.Context, g auth.Grant) (bool, error)

func (*EndpointQuery) Find

func (q *EndpointQuery) Find(ctx context.Context, g auth.Grant, id any, columns ...any) (*Endpoint, error)

func (*EndpointQuery) FindMany

func (q *EndpointQuery) FindMany(ctx context.Context, g auth.Grant, ids []any, columns ...any) (EndpointCollection, error)

func (*EndpointQuery) FindOrFail

func (q *EndpointQuery) FindOrFail(ctx context.Context, g auth.Grant, id any, columns ...any) (*Endpoint, error)

func (*EndpointQuery) FindOrNew

func (q *EndpointQuery) FindOrNew(ctx context.Context, g auth.Grant, id any, columns ...any) (*Endpoint, error)

func (*EndpointQuery) First

func (q *EndpointQuery) First(ctx context.Context, g auth.Grant, columns ...any) (*Endpoint, error)

First returns the first row, or nil and no error when there is none.

func (*EndpointQuery) FirstOrCreate

func (q *EndpointQuery) FirstOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Endpoint, error)

func (*EndpointQuery) FirstOrFail

func (q *EndpointQuery) FirstOrFail(ctx context.Context, g auth.Grant, columns ...any) (*Endpoint, error)

func (*EndpointQuery) FirstOrNew

func (q *EndpointQuery) FirstOrNew(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Endpoint, error)

func (*EndpointQuery) FirstWhere

func (q *EndpointQuery) FirstWhere(ctx context.Context, g auth.Grant, column any, args ...any) (*Endpoint, error)

func (*EndpointQuery) ForPage

func (q *EndpointQuery) ForPage(page, perPage int) *EndpointQuery

func (*EndpointQuery) ForceCreate

func (q *EndpointQuery) ForceCreate(ctx context.Context, g auth.Grant, attributes map[string]any) (*Endpoint, error)

func (*EndpointQuery) Get

func (q *EndpointQuery) Get(ctx context.Context, g auth.Grant, columns ...any) (EndpointCollection, error)

Get runs the query and returns the rows.

func (*EndpointQuery) GroupBy

func (q *EndpointQuery) GroupBy(groups ...any) *EndpointQuery

func (*EndpointQuery) Has

func (q *EndpointQuery) Has(relation, operator string, count int, boolean string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) Increment

func (q *EndpointQuery) Increment(ctx context.Context, g auth.Grant, column string, amount any, extra map[string]any) (int64, error)

func (*EndpointQuery) Insert

func (q *EndpointQuery) Insert(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*EndpointQuery) InsertOrIgnore

func (q *EndpointQuery) InsertOrIgnore(ctx context.Context, g auth.Grant, values ...map[string]any) (bool, error)

func (*EndpointQuery) Join

func (q *EndpointQuery) Join(table any, first any, args ...any) *EndpointQuery

func (*EndpointQuery) Latest

func (q *EndpointQuery) Latest(column ...string) *EndpointQuery

func (*EndpointQuery) Limit

func (q *EndpointQuery) Limit(value int) *EndpointQuery

func (*EndpointQuery) Max

func (q *EndpointQuery) Max(ctx context.Context, g auth.Grant, column any) (any, error)

func (*EndpointQuery) Min

func (q *EndpointQuery) Min(ctx context.Context, g auth.Grant, column any) (any, error)

func (*EndpointQuery) New

func (q *EndpointQuery) New() (*Endpoint, error)

New returns an empty Endpoint on the query's connection, not yet saved, filled with what WithAttributes recorded on the query.

func (*EndpointQuery) Offset

func (q *EndpointQuery) Offset(value int) *EndpointQuery

func (*EndpointQuery) Oldest

func (q *EndpointQuery) Oldest(column ...string) *EndpointQuery

func (*EndpointQuery) OrDoesntHave

func (q *EndpointQuery) OrDoesntHave(relation string) *EndpointQuery

func (*EndpointQuery) OrHas

func (q *EndpointQuery) OrHas(relation, operator string, count int) *EndpointQuery

func (*EndpointQuery) OrWhere

func (q *EndpointQuery) OrWhere(column any, args ...any) *EndpointQuery

OrWhere adds an or-where clause; a func(*EndpointQuery) in place of the column adds a group.

func (*EndpointQuery) OrWhereDoesntHave

func (q *EndpointQuery) OrWhereDoesntHave(relation string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) OrWhereHas

func (q *EndpointQuery) OrWhereHas(relation string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) OrWhereNot

func (q *EndpointQuery) OrWhereNot(column any, args ...any) *EndpointQuery

OrWhereNot adds an or-joined negated where clause, or a negated group.

func (*EndpointQuery) OrWhereRelation

func (q *EndpointQuery) OrWhereRelation(relation string, column any, args ...any) *EndpointQuery

func (*EndpointQuery) OrderBy

func (q *EndpointQuery) OrderBy(column any, direction ...string) *EndpointQuery

func (*EndpointQuery) OrderByDesc

func (q *EndpointQuery) OrderByDesc(column any) *EndpointQuery

func (*EndpointQuery) Paginate

func (q *EndpointQuery) Paginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (EndpointCollection, *pagination.LengthAwarePage, error)

func (*EndpointQuery) Pluck

func (q *EndpointQuery) Pluck(ctx context.Context, g auth.Grant, column string) ([]any, error)

func (*EndpointQuery) Select

func (q *EndpointQuery) Select(columns ...any) *EndpointQuery

func (*EndpointQuery) SelectRaw

func (q *EndpointQuery) SelectRaw(expression string, bindings ...any) *EndpointQuery

func (*EndpointQuery) SimplePaginate

func (q *EndpointQuery) SimplePaginate(ctx context.Context, g auth.Grant, perPage, page int, opts pagination.Options, columns ...any) (EndpointCollection, *pagination.Page, error)

func (*EndpointQuery) Sole

func (q *EndpointQuery) Sole(ctx context.Context, g auth.Grant, columns ...any) (*Endpoint, error)

func (*EndpointQuery) SoleValue

func (q *EndpointQuery) SoleValue(ctx context.Context, g auth.Grant, column string) (any, error)

func (*EndpointQuery) Sum

func (q *EndpointQuery) Sum(ctx context.Context, g auth.Grant, column any) (any, error)

func (*EndpointQuery) Touch

func (q *EndpointQuery) Touch(ctx context.Context, g auth.Grant, column ...string) (int64, error)

func (*EndpointQuery) Update

func (q *EndpointQuery) Update(ctx context.Context, g auth.Grant, values map[string]any) (int64, error)

func (*EndpointQuery) UpdateOrCreate

func (q *EndpointQuery) UpdateOrCreate(ctx context.Context, g auth.Grant, attributes, values map[string]any) (*Endpoint, error)

func (*EndpointQuery) Upsert

func (q *EndpointQuery) Upsert(ctx context.Context, g auth.Grant, values []map[string]any, uniqueBy, update []string) (int64, error)

func (*EndpointQuery) Value

func (q *EndpointQuery) Value(ctx context.Context, g auth.Grant, column string) (any, error)

func (*EndpointQuery) ValueOrFail

func (q *EndpointQuery) ValueOrFail(ctx context.Context, g auth.Grant, column string) (any, error)

func (*EndpointQuery) Where

func (q *EndpointQuery) Where(column any, args ...any) *EndpointQuery

Where adds a where clause; a func(*EndpointQuery) in place of the column adds a group.

func (*EndpointQuery) WhereBelongsTo

func (q *EndpointQuery) WhereBelongsTo(relationshipName string, related ...model.Entity) *EndpointQuery

func (*EndpointQuery) WhereBetween

func (q *EndpointQuery) WhereBetween(column any, from, to any) *EndpointQuery

func (*EndpointQuery) WhereColumn

func (q *EndpointQuery) WhereColumn(first any, args ...any) *EndpointQuery

func (*EndpointQuery) WhereDoesntHave

func (q *EndpointQuery) WhereDoesntHave(relation string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) WhereExists

func (q *EndpointQuery) WhereExists(callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) WhereHas

func (q *EndpointQuery) WhereHas(relation string, callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) WhereHasCount

func (q *EndpointQuery) WhereHasCount(relation string, callback func(*query.Builder), operator string, count int) *EndpointQuery

func (*EndpointQuery) WhereIn

func (q *EndpointQuery) WhereIn(column any, values []any) *EndpointQuery

func (*EndpointQuery) WhereKey

func (q *EndpointQuery) WhereKey(id any) *EndpointQuery

func (*EndpointQuery) WhereNot

func (q *EndpointQuery) WhereNot(column any, args ...any) *EndpointQuery

WhereNot adds a negated where clause, or a negated group.

func (*EndpointQuery) WhereNotExists

func (q *EndpointQuery) WhereNotExists(callback func(*query.Builder)) *EndpointQuery

func (*EndpointQuery) WhereNotIn

func (q *EndpointQuery) WhereNotIn(column any, values []any) *EndpointQuery

func (*EndpointQuery) WhereNotNull

func (q *EndpointQuery) WhereNotNull(columns ...any) *EndpointQuery

func (*EndpointQuery) WhereNull

func (q *EndpointQuery) WhereNull(columns ...any) *EndpointQuery

func (*EndpointQuery) WhereRelation

func (q *EndpointQuery) WhereRelation(relation string, column any, args ...any) *EndpointQuery

func (*EndpointQuery) With

func (q *EndpointQuery) With(relations ...string) *EndpointQuery

func (*EndpointQuery) WithAvg

func (q *EndpointQuery) WithAvg(relation, column string) *EndpointQuery

func (*EndpointQuery) WithCount

func (q *EndpointQuery) WithCount(relations ...string) *EndpointQuery

func (*EndpointQuery) WithExists

func (q *EndpointQuery) WithExists(relation string) *EndpointQuery

func (*EndpointQuery) WithMax

func (q *EndpointQuery) WithMax(relation, column string) *EndpointQuery

func (*EndpointQuery) WithMin

func (q *EndpointQuery) WithMin(relation, column string) *EndpointQuery

func (*EndpointQuery) WithSum

func (q *EndpointQuery) WithSum(relation, column string) *EndpointQuery

func (*EndpointQuery) Without

func (q *EndpointQuery) Without(relations ...string) *EndpointQuery

func (*EndpointQuery) WithoutGlobalScope

func (q *EndpointQuery) WithoutGlobalScope(identifier string) *EndpointQuery

func (*EndpointQuery) WithoutGlobalScopes

func (q *EndpointQuery) WithoutGlobalScopes(identifiers ...string) *EndpointQuery

type EndpointRequest

type EndpointRequest struct {
	Name string
	URL  string
	// Events are the event names it receives; AllEvents receives every one.
	Events []string
	// Signer is SignerArandu (empty means it) or SignerHMAC.
	Signer string
	// SignatureHeader is where a SignerHMAC signature goes. Empty means
	// DefaultSignatureHeader.
	SignatureHeader string
	// Headers is "Name: value" per line. On an update, empty keeps the stored
	// headers, which are never shown back.
	Headers string
	Active  bool
}

EndpointRequest is an endpoint as a form or a client sends it.

There is no TenantID here and there must never be one: the tenant comes from the Grant, which comes from the session.

type EndpointResource

type EndpointResource struct {
	// contains filtered or unexported fields
}

EndpointResource is the list of fields one endpoint may answer with. The secret, the sealed headers and the tenant are not among them.

func NewEndpointResource

func NewEndpointResource(e *Endpoint) EndpointResource

NewEndpointResource snapshots one endpoint for the response.

func (EndpointResource) ToArray

func (r EndpointResource) ToArray() map[string]any

ToArray returns the fields that may leave, by name.

func (EndpointResource) With

func (r EndpointResource) With() map[string]any

With adds nothing beside the fields.

type EndpointService

type EndpointService struct {
	// contains filtered or unexported fields
}

EndpointService is the use cases of the sending side: the endpoints, their secrets, the test call and the record of deliveries. It owns the database handle; every exported method asks the policy before it reads or writes.

func (*EndpointService) Create

Create adds an endpoint with a new signing secret, answered in clear this once for the person to give the receiver.

func (*EndpointService) Delete

func (s *EndpointService) Delete(ctx context.Context, actor security.Subject, id string) error

Delete removes an endpoint. Deliveries already queued still go out.

func (*EndpointService) Deliveries

func (s *EndpointService) Deliveries(ctx context.Context, actor security.Subject, id string, limit int) ([]*Delivery, error)

Deliveries returns an endpoint's latest deliveries, newest first: what webhook-server tells through its WebhookCallSucceeded and WebhookCallFailed events, read from the record each attempt leaves.

func (*EndpointService) Find

func (s *EndpointService) Find(ctx context.Context, actor security.Subject, id string) (*Endpoint, error)

Find returns one endpoint, and asks the policy about it once it is read.

func (*EndpointService) HeaderNames

func (s *EndpointService) HeaderNames(ctx context.Context, actor security.Subject, id string) ([]string, error)

HeaderNames returns the names of an endpoint's extra headers, for its form. The values are never shown back.

func (*EndpointService) List

func (s *EndpointService) List(ctx context.Context, actor security.Subject, q data.Query) ([]*Endpoint, error)

List returns a page of the tenant's endpoints, by name.

func (*EndpointService) Secret

func (s *EndpointService) Secret(ctx context.Context, actor security.Subject, id string) (string, error)

Secret returns an endpoint's signing secret, for the panel's copy button.

func (*EndpointService) Test

func (s *EndpointService) Test(ctx context.Context, actor security.Subject, id string) error

Test queues one webhook.test call to an endpoint, whatever it subscribes to and whether or not it is active.

func (*EndpointService) Update

Update changes an endpoint. The secret stays, and empty headers keep the stored ones.

type Flag

type Flag bool

Flag is a yes-or-no stored as 0 or 1, which every engine reads the same.

func (*Flag) Scan

func (f *Flag) Scan(value any) error

Scan reads a flag from whatever the engine answers.

func (Flag) Value

func (f Flag) Value() (driver.Value, error)

Value writes the flag as 0 or 1.

type HMAC

type HMAC struct {
	Header string
	Prefix string
}

HMAC is webhook-client's DefaultSignatureValidator: the header carries the hex HMAC-SHA256 of the body under the shared secret. Prefix is what comes before the hex, such as "sha256=" for a sender that writes one; empty means none.

func (HMAC) Valid

func (v HMAC) Valid(r *http.Request, body []byte, secrets hwebhook.SecretSet) bool

Valid compares the header with the signature of the body under every accepted secret, in constant time.

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module is what the application registers.

func New

func New(cfg Config, db *data.DB, sessions *security.SessionStore) (*Module, error)

New returns the module, or the reason it cannot be built.

sessions is where the management routes take their subject from; nil mounts none of them, for an application that manages endpoints from its own screens through the services. The receivers need no session.

func (*Module) CallService

func (m *Module) CallService() *CallService

CallService is the receiving side's record, for an application that reads it from its own screens. Every method asks the policy.

func (*Module) EndpointService

func (m *Module) EndpointService() *EndpointService

EndpointService is the sending side's use cases, for an application that calls them from its own screens. Every method asks the policy.

func (*Module) Manager

func (m *Module) Manager() *hwebhook.Manager

Manager is hesape/webhook's Manager underneath, for an application that dispatches to endpoints of its own.

func (*Module) Migrations

func (m *Module) Migrations() []foundation.Migration

Migrations declares the schema this module owns: its two tables, and the deliveries table of hesape/webhook underneath. An application that already runs hesape/webhook's Manager has that migration applied, and the migrator skips a name it has run.

func (*Module) Name

func (m *Module) Name() string

Name is the module identifier.

func (*Module) Outgoing

func (m *Module) Outgoing() *Outgoing

Outgoing starts a call built in code, as webhook-server's WebhookCall::create.

func (*Module) Publisher

func (m *Module) Publisher() events.Publisher

Publisher is the events outbox's listener that delivers each event to the tenant's endpoints subscribed to it. The application adds it to its relay's publishers.

func (*Module) ReceivePrefix

func (m *Module) ReceivePrefix() string

ReceivePrefix is the path the receivers answer under, for the application's CSRF exemption.

func (*Module) RegisterJobHandlers

func (m *Module) RegisterJobHandlers(worker *queue.Worker) error

RegisterJobHandlers gives the worker the deliveries and the processing of received calls. The worker is the application's, started by its worker command.

func (*Module) Routes

func (m *Module) Routes(r *fhttp.Router)

Routes registers the receivers, and the management routes behind RequireAuth when the module has a session store.

A receiver is reached by a system with no session: its signature admits it. Its route is registered on r itself, and the guarded group is built after, so no management route lands outside the guard.

func (*Module) Schedule

func (m *Module) Schedule() []foundation.Task

Schedule declares the daily clean of old received calls, as webhook-client's model:prune of WebhookCall, in each receiver's tenant. CleanSpec "-" declares none.

type Outgoing

type Outgoing struct {
	// contains filtered or unexported fields
}

Outgoing is one call built in code, as webhook-server's WebhookCall:

err := webhooks.Outgoing().
	URL("https://example.com/hooks").
	Event("order.paid").
	Payload(map[string]any{"order": id}).
	UseSecret("partners").
	Dispatch(ctx, g)

Dispatch records the delivery and its queued job in one transaction; the worker sends it, signed, and retries it. g carries hwebhook.ActionDispatch for the tenant the call belongs to: the application decided to send, and that decision is the Grant.

func (*Outgoing) Dispatch

func (o *Outgoing) Dispatch(ctx context.Context, g security.Grant) error

Dispatch records the call and queues its delivery.

func (*Outgoing) DispatchIf

func (o *Outgoing) DispatchIf(ctx context.Context, cond bool, g security.Grant) error

DispatchIf dispatches when cond holds, as webhook-server's dispatchIf.

func (*Outgoing) DispatchUnless

func (o *Outgoing) DispatchUnless(ctx context.Context, cond bool, g security.Grant) error

DispatchUnless dispatches unless cond holds.

func (*Outgoing) Event

func (o *Outgoing) Event(name string) *Outgoing

Event names the call. Empty means DefaultEvent.

func (*Outgoing) Payload

func (o *Outgoing) Payload(v any) *Outgoing

Payload sets the body to v, encoded as JSON.

func (*Outgoing) RawBody

func (o *Outgoing) RawBody(body []byte) *Outgoing

RawBody sets the body as it is, as webhook-server's sendRawBody. It is sent as application/json.

func (*Outgoing) SignWithHMAC

func (o *Outgoing) SignWithHMAC(header string) *Outgoing

SignWithHMAC adds webhook-server's DefaultSigner signature -- the hex HMAC-SHA256 of the body -- in the header named, beside the Arandu one. Empty means DefaultSignatureHeader.

func (*Outgoing) URL

func (o *Outgoing) URL(u string) *Outgoing

URL sets the address.

func (*Outgoing) UUID

func (o *Outgoing) UUID(id string) *Outgoing

UUID sets the call's identity. A call dispatched twice with one identity to one address is delivered once. Empty means a new one.

func (*Outgoing) UseSecret

func (o *Outgoing) UseSecret(name string) *Outgoing

UseSecret names the secret of Config.Server.Secrets the call is signed with.

func (*Outgoing) WithHeaders

func (o *Outgoing) WithHeaders(headers map[string]string) *Outgoing

WithHeaders adds extra headers.

type ProcessEverything

type ProcessEverything struct{}

ProcessEverything is webhook-client's ProcessEverythingWebhookProfile.

func (ProcessEverything) ShouldProcess

func (ProcessEverything) ShouldProcess(*http.Request, []byte) bool

ShouldProcess answers true.

type Processor

type Processor interface {
	Process(ctx context.Context, g security.Grant, call *Call) error
}

Processor handles a stored call on the queue: webhook-client's process_webhook_job. g is the Grant of the call's tenant, for whatever the processing reads and writes. An error leaves the call failed, with the message kept, and the queue tries again until the receiver's Tries are spent; nil marks it processed.

type ProcessorFunc

type ProcessorFunc func(ctx context.Context, g security.Grant, call *Call) error

ProcessorFunc adapts a function to Processor.

func (ProcessorFunc) Process

func (f ProcessorFunc) Process(ctx context.Context, g security.Grant, call *Call) error

Process calls f.

type Profile

type Profile interface {
	ShouldProcess(r *http.Request, body []byte) bool
}

Profile decides whether a call whose signature checked is stored and processed: webhook-client's WebhookProfile. A call it turns down is answered like any other and kept nowhere.

type ProfileFunc

type ProfileFunc func(r *http.Request, body []byte) bool

ProfileFunc adapts a function to Profile.

func (ProfileFunc) ShouldProcess

func (f ProfileFunc) ShouldProcess(r *http.Request, body []byte) bool

ShouldProcess calls f.

type Receiver

type Receiver struct {
	// Name identifies the receiver, its route and its calls: lowercase
	// letters, digits and dashes.
	Name string

	// Tenant is whose calls these are. It comes from the application's
	// configuration, never from the request.
	Tenant string

	// Methods are the HTTP methods accepted. Empty means POST.
	Methods []string

	// Secret is the signing secret the sender shares; PreviousSecrets are
	// still accepted while the sender rotates. Required unless Validator is
	// the application's own.
	Secret          string
	PreviousSecrets []string

	// Validator checks the signature. Nil means HMAC{Header:
	// DefaultSignatureHeader}, webhook-client's DefaultSignatureValidator.
	Validator SignatureValidator

	// Profile decides whether a valid call is stored and processed. Nil
	// processes every call.
	Profile Profile

	// Respond answers the sender. Nil answers 200 {"message":"ok"}.
	Respond Responder

	// StoreHeaders are the request headers kept with the call; "*" keeps
	// them all. Empty keeps none.
	StoreHeaders []string

	// Process handles a stored call on the queue: webhook-client's
	// process_webhook_job. Required.
	Process Processor

	// Tries is how many times a call is processed before it stays failed.
	// Zero means DefaultTries.
	Tries int

	// MaxBodyBytes is the largest body read. Zero means DefaultMaxBodyBytes.
	MaxBodyBytes int64

	// OnInvalidSignature is called when a request fails the signature check,
	// as webhook-client's InvalidWebhookSignatureEvent. Optional.
	OnInvalidSignature func(r *http.Request, receiver string)
}

Receiver is one system that calls this application: webhook-client's config entry, mounted at ReceivePrefix + "/" + Name.

type RespondOK

type RespondOK struct{}

RespondOK is webhook-client's DefaultRespondsTo: 200 {"message":"ok"}.

func (RespondOK) Respond

func (RespondOK) Respond(w http.ResponseWriter, _ *http.Request, _ *Call) error

Respond writes the answer.

type Responder

type Responder interface {
	Respond(w http.ResponseWriter, r *http.Request, call *Call) error
}

Responder answers the sender once a call is taken: webhook-client's RespondsToWebhook. call is nil when the Profile turned the call down.

type ResponderFunc

type ResponderFunc func(w http.ResponseWriter, r *http.Request, call *Call) error

ResponderFunc adapts a function to Responder.

func (ResponderFunc) Respond

func (f ResponderFunc) Respond(w http.ResponseWriter, r *http.Request, call *Call) error

Respond calls f.

type Server

type Server struct {
	// Queue is the queue deliveries travel on. Empty means DefaultQueue.
	Queue string

	// UserAgent is sent with every call. Empty means DefaultUserAgent.
	UserAgent string

	// Timeout is how long one call may take. Zero means DefaultTimeout.
	Timeout time.Duration

	// Retention is how long a delivery's record is kept. Zero means
	// hesape/webhook's DefaultRetention.
	Retention time.Duration

	// HeaderPrefix renames the delivery headers, X-Arandu-Signature becoming
	// <HeaderPrefix>-Signature, so a receiver reads the product's name. The
	// signature covers the timestamp, the delivery id and the body, never a
	// header's name, so renaming does not change what is verified. Empty keeps
	// X-Arandu.
	HeaderPrefix string

	// HTTPClient carries the calls. Nil uses hesape's client, which refuses
	// private and loopback addresses on every hop.
	HTTPClient *http.Client

	// Events is the list an endpoint may subscribe to. Empty accepts any
	// event name.
	Events []string

	// Secrets are named signing secrets for calls built with Outgoing, as
	// webhook-server's useSecret. Each is at least MinSecretBytes long.
	Secrets map[string]string
}

Server is the sending side: webhook-server's config.

type SignatureValidator

type SignatureValidator interface {
	Valid(r *http.Request, body []byte, secrets hwebhook.SecretSet) bool
}

SignatureValidator decides whether a received call was signed by the sender that shares the receiver's secret: webhook-client's SignatureValidator. secrets holds the current secret and the ones still accepted during a rotation; body is exactly what arrived.

type SignatureValidatorFunc

type SignatureValidatorFunc func(r *http.Request, body []byte, secrets hwebhook.SecretSet) bool

SignatureValidatorFunc adapts a function to SignatureValidator.

func (SignatureValidatorFunc) Valid

func (f SignatureValidatorFunc) Valid(r *http.Request, body []byte, secrets hwebhook.SecretSet) bool

Valid calls f.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL