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

webhook

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 41 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 dialect, a signing secret and extra headers, both sealed at rest, and optionally a delivery policy of its own. The module owns the delivery: one durable record per message and endpoint, its body frozen, written in the same transaction as the job that makes its first attempt; every attempt signed when it is made, in the endpoint's dialect -- Arandu, webhook-server's HMAC, or Standard Webhooks -- through a transport that refuses internal addresses, retried on the ladder the delivery was recorded with, and kept in the attempt history. The events outbox delivers through the Publisher, Send delivers a message the application built, and a call can 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.
  • Recorded. Every management act -- an endpoint created, changed, removed, imported, turned on or off, a secret shown or rotated, a test, a redelivery, a call processed again -- stores an event in the application's outbox in the same transaction, which arandu-activitylog writes as an entry.

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(), and Module.Send for a message to a stored endpoint
DefaultSigner: hex HMAC-SHA256 of the body in Signature SignWithHMAC(header) on a call, DialectHMAC on an endpoint, with or without the Arandu headers
useTimestamp() always: the Arandu and Standard Webhooks signatures cover a timestamp and the delivery id
tries, ExponentialBackoffStrategy Policy.Tries and Policy.Backoff, frozen on each delivery; by default 5 tries, 5 s to 10 min, and the receiver's Retry-After
timeout_in_seconds, verify_ssl Policy.Timeout; TLS is always verified
WebhookCallSucceededEvent, WebhookCallFailedEvent, FinalWebhookCallFailedEvent the delivery's record and its attempts: EndpointService.Deliveries, Delivery, Attempts and their routes
(none: the application keeps its own endpoints) endpoints per tenant, sealed secret and headers, subscriptions, dialects, a policy each, rotation with an overlap, import, redelivery, turning off after failures, 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, delivery and 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. 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. Each attempt is a row of the delivery's history instead; reading it is how a panel shows "attempt 3 of 7, 503, 2 s".

Install

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

It needs framework v0.56 and hesape v0.56. It requires no hesape newer than the one framework v0.56 is built against, because framework/view converts hesape's view.Page to its own and stops compiling with hesape v0.57 or newer: keep hesape at v0.56 in an application that draws pages with it.

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},
			// Nil is webhook.DefaultPolicy(); see "The delivery policy".
			Policy: nil,
		},
		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 to the activity log through arandu-activitylog's recorder; the worker makes the attempts and processes the received calls:

	// The relay takes one publisher, so the application hands each event to
	// its listeners in turn: the activity log, then the endpoints. The
	// module's own management events are logged by the recorder and skipped
	// by the module's publisher.
	recorder := activitylog.NewEventRecorder(activityLogger)
	publish := events.PublisherFunc(func(ctx context.Context, e events.Stored) error {
		if err := recorder.Publish(ctx, e); err != nil {
			return err
		}
		return webhookModule.Publisher().Publish(ctx, e)
	})
	relay := events.NewRelay(outbox, publish, 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, webhook_server_deliveries, webhook_server_delivery_attempts and webhook_client_calls. The jobs table is the queue's and the outbox table is hesape's events', both migrated by the application: every management act stores its event in the outbox.

Sending

An endpoint is created through EndpointService or the management routes. Its secret -- whsec_ and the standard base64 of 32 random bytes -- 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"},
	Dialect: webhook.DialectStandardWebhooks,
	Active:  true,
})

Every event the outbox relays goes to the active endpoints of its tenant subscribed to its name, or to *, keeping the event's id: an event relayed twice is delivered once. A message the application built goes to one endpoint with Send, its id and its body sent exactly as given on every attempt:

delivery, err := webhookModule.Send(ctx, security.SystemGrant(webhook.DeliveryDispatch, tenant), endpoint.ID, webhook.Message{
	ID:    "msg_2KWPBgLlAfxdpx2AI54pPJ85f4W", // the Webhook-Id; empty makes one
	Event: "invoice.paid",
	Body:  body, // frozen: every attempt and every redelivery sends these bytes
})
Dialects
dialect headers of every attempt
arandu (the default) X-Arandu-Signature: sha256= and the hex HMAC-SHA256 of timestamp.delivery-id.body under the current secret; X-Arandu-Timestamp: the Unix time of the attempt; X-Arandu-Delivery-Id: the delivery, the same on every attempt; User-Agent of the policy
hmac the endpoint's SignatureHeader with the hex HMAC-SHA256 of the body, webhook-server's DefaultSigner, beside the three Arandu headers unless WithoutAranduHeaders
standard-webhooks Webhook-Id: the delivery id; Webhook-Timestamp: the Unix time of the attempt; Webhook-Signature: v1, and the standard base64 HMAC-SHA256 of id.timestamp.body under the key the whsec_ secret encodes, once per secret still signing, newest first, separated by a space. Nothing else of ours: no X-Arandu-*, and Go's own User-Agent when the policy names none

Every attempt is POST with Content-Type: application/json and the endpoint's extra headers. Server.HeaderPrefix renames the Arandu headers, X-Arandu- becoming X-Acme-; the signature covers no header's name, so a receiver verifies the same bytes. hesape's webhook.Verify verifies the Arandu signature, and any Standard Webhooks library the other one.

A call built in code names one of Server.Secrets, and the Grant it is dispatched with carries DeliveryDispatch 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(webhook.DeliveryDispatch, tenant))

DeliveryDispatch has the value of hesape/webhook's ActionDispatch, so a Grant v0.1.0 code issued for that action still dispatches.

The delivery policy

Server.Policy is how every delivery is made, and an endpoint may carry its own (EndpointRequest.Policy). The number of attempts and the waits between them are frozen on each delivery when it is recorded; the rest is read at every attempt. A zero field means zero: start from webhook.DefaultPolicy().

field DefaultPolicy() what it does
Tries 5 attempts in all, the first included
Backoff 5 s, 30 s, 2 min, 10 min the wait before each attempt after the first, its last one repeated; no jitter
Retry RetryTransient transport errors, 408, 425, 429 and 5xx are tried again, anything else is final; RetryEveryFailure tries every failure again
HonorRetryAfter true waits as long as the receiver's Retry-After when that is longer
HTTPSOnly false refuses an http:// address at the form, at dispatch and on a redirect
MaxRedirects 5 0 follows none: the redirect is the answer, a failure
MaxRequestBytes 1 MiB the largest body a delivery is recorded with
MaxResponseBytes 1 MiB how much of an answer is read; the rest is left, and the status decides
Timeout 10 s one attempt, the answer included
UserAgent Arandu-Webhook/1.0 empty sends Go's own
DisableAfter 0 turns an endpoint off once that many of its deliveries in a row gave up; 0 never

A provider whose customers verify Standard Webhooks, for example:

Policy: &webhook.Policy{
	Tries:            7,
	Backoff:          []time.Duration{30 * time.Second, 5 * time.Minute, 30 * time.Minute, 2 * time.Hour, 5 * time.Hour, 10 * time.Hour},
	Retry:            webhook.RetryEveryFailure,
	HTTPSOnly:        true,
	MaxRedirects:     0,
	MaxRequestBytes:  64 << 10,
	MaxResponseBytes: 64 << 10,
	Timeout:          10 * time.Second,
	DisableAfter:     20,
},

Without Server.HTTPClient the attempts go out on hesape's guarded client, whose dialer refuses loopback, private, link-local, multicast and unspecified addresses, behind a dialer of this module that also refuses the shared address space 100.64.0.0/10 and checks every address a name resolves to before it connects to one. No environment proxy is used. A client given in Server.HTTPClient keeps its own transport, which is what a test that delivers to a local server needs.

Deliveries, attempts and redelivery

A delivery is pending while it waits for an attempt, processing while one is in progress, and ends delivered or failed. Its attempt takes a claim with a lease, so two workers, or a worker and a redelivery, never send one attempt twice. EndpointService.Deliveries, Delivery and Attempts read them, each attempt with its number, trigger, status, failure code, the first 500 characters of the answer and its duration.

EndpointService.Redeliver sends a delivery again now: the same id and the same body, under a new timestamp and signature. A delivery with no attempt left is given one more; one whose attempt is in progress is refused with 409.

An endpoint turned off -- by a person with Disable, or by the engine after DisableAfter deliveries in a row gave up -- keeps its deliveries waiting; Enable, or an Update that turns it on, sends them and clears the count, and a delivered one resets the count too. A redelivery and a test go whatever the endpoint's state. The deliveries of a deleted endpoint fail as endpoint_deleted. Settled deliveries and their attempts are removed after Server.Retention, 30 days by default, by the scheduled webhook.deliveries.clean.

Rotating a secret
secret, err := webhookModule.EndpointService().Rotate(ctx, who, endpoint.ID, 24*time.Hour)

The new secret is current at once; the one it replaces keeps signing beside it for the overlap, so a Standard Webhooks attempt carries both signatures, newest first, until it retires. A secret still retiring from an earlier rotation is revoked. The management route rotates with DefaultRotationOverlap, a day.

Importing endpoints from an application

An application moving its own endpoints into this module keeps their ids and secrets, so its receivers notice nothing:

_, err := webhookModule.EndpointService().Import(ctx, migrationSubject, webhook.ImportRequest{
	ID: "ep_01J9...",
	EndpointRequest: webhook.EndpointRequest{
		Name: "Merchant", URL: "https://merchant.example.com/hooks", Events: []string{"*"},
		Dialect: webhook.DialectStandardWebhooks, Active: true,
	},
	Secret:          current,
	PreviousSecrets: []webhook.PreviousSecret{{Secret: retiring, RetiresAt: retiresAt}},
})

The secrets are sealed with this module's key, bound to the tenant and the id. EndpointImport is the data migration's action: Actions() does not offer it, and the subject the migration runs as carries it. Previous secrets already retired are dropped, and at most MaxPreviousSecrets (5) are kept.

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
POST /webhook/endpoints/{id}/rotate webhook.endpoints.rotate EndpointRotate
POST /webhook/endpoints/{id}/enable webhook.endpoints.enable EndpointUpdate
POST /webhook/endpoints/{id}/disable webhook.endpoints.disable EndpointUpdate
GET /webhook/deliveries/{id} webhook.deliveries.show EndpointView
GET /webhook/deliveries/{id}/attempts webhook.deliveries.attempts EndpointView
POST /webhook/deliveries/{id}/redeliver webhook.deliveries.redeliver DeliveryRedeliver
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 twelve an application gives through arandu-permission. EndpointImport is allowed to a subject that carries it and is not offered. CallReceive, CallClean, DeliveryDispatch and DeliveryClean are given to nobody: they are the receiver's, the cleans' and the dispatch'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, DeliveryAttempts(db) a *DeliveryAttemptQuery, and none of them runs without a Grant.

The activity log

Every management act stores one event in the application's outbox, in the transaction of the act, carrying the subject that was authorized and the action; arandu-activitylog's EventRecorder, on the same relay, writes it as an entry. No payload carries a secret, a signature or a header's value.

event when
webhook.endpoint.created, .updated (before and after), .deleted, .imported an endpoint is added, changed, removed or imported
webhook.endpoint.enabled, .disabled it is turned on or off; off by the engine with the reason consecutive_failures
webhook.endpoint.secret_revealed, .secret_rotated its secret is shown or rotated
webhook.endpoint.tested a test call is queued
webhook.delivery.redelivered a delivery is sent again
webhook.call.reprocessed a received call is processed again

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; a Standard Webhooks delivery checked byte for byte against a vector computed outside the module, through a rotation; the retry ladder, the policy's limits, the endpoint turned off after its failures, redelivery and the claim; the internal addresses refused; the management events in the outbox; 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 module owns the delivery: one durable record per message and endpoint, its body frozen, written in the same transaction as the job that makes its first attempt; every attempt signed when it is made, in the endpoint's dialect -- hesape's Arandu scheme, webhook-server's HMAC of the body, or Standard Webhooks -- through a transport that refuses internal addresses, under a Policy that says how many attempts, how far apart, and what counts as a failure. The events outbox delivers through Publisher, Send delivers a message the application built, and Outgoing builds a call in code, as webhook-server's WebhookCall.

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: recording deliveries, Publisher, Send, Outgoing
delivery.go   -> the attempts: claim, request, settle, retry, clean
dialect.go    -> how each dialect signs a request
transport.go  -> the guarded client the attempts go out on
audit.go      -> the management events stored in the outbox
client.go     -> receiving: the pipeline and its processing job
signature.go  -> the signers, the validators and the seal

Index

Constants

View Source
const (
	// EventEndpointCreated is an endpoint added, with its name, address,
	// events and dialect.
	EventEndpointCreated = "webhook.endpoint.created"
	// EventEndpointUpdated is an endpoint changed, with what it was and what
	// it is.
	EventEndpointUpdated = "webhook.endpoint.updated"
	// EventEndpointDeleted is an endpoint removed.
	EventEndpointDeleted = "webhook.endpoint.deleted"
	// EventEndpointEnabled is an endpoint turned on.
	EventEndpointEnabled = "webhook.endpoint.enabled"
	// EventEndpointDisabled is an endpoint turned off, by a person or by the
	// engine after DisableAfter deliveries in a row gave up; the reason says
	// which.
	EventEndpointDisabled = "webhook.endpoint.disabled"
	// EventEndpointImported is an endpoint stored with an id and secrets it
	// already had.
	EventEndpointImported = "webhook.endpoint.imported"
	// EventSecretRevealed is an endpoint's secret read in clear.
	EventSecretRevealed = "webhook.endpoint.secret_revealed"
	// EventSecretRotated is an endpoint given a new secret, with when the
	// previous one retires.
	EventSecretRotated = "webhook.endpoint.secret_rotated"
	// EventEndpointTested is a test call queued to an endpoint.
	EventEndpointTested = "webhook.endpoint.tested"
	// EventDeliveryRedelivered is a delivery sent again by somebody.
	EventDeliveryRedelivered = "webhook.delivery.redelivered"
	// EventCallReprocessed is a received call sent to its processor again.
	EventCallReprocessed = "webhook.call.reprocessed"
)

The events every management act stores in the application's outbox, in the same transaction as the act. The outbox row carries the tenant, the subject that was authorized and the action, so an events.Publisher such as arandu-activitylog's EventRecorder writes who did what to which endpoint without this module depending on it. A payload never carries a secret, a signature or a header's value.

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 under the default
	// policy.
	DefaultUserAgent = "Arandu-Webhook/1.0"
	// DefaultTimeout is how long an outgoing call may take under the default
	// policy.
	DefaultTimeout = 10 * time.Second
	// DefaultRetention is how long a delivery's record and its attempts are
	// kept.
	DefaultRetention = 30 * 24 * time.Hour
	// 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 (
	// MaxTries is the most attempts one delivery may be given.
	MaxTries = 20
	// MaxBackoff is the longest wait between two attempts.
	MaxBackoff = 7 * 24 * time.Hour
	// MaxRedirects is the most redirects a delivery may follow.
	MaxRedirects = 10
	// MaxTimeout is the longest one attempt may take.
	MaxTimeout = 2 * time.Minute
	// MaxRequestBytes is the largest body a delivery may carry.
	MaxRequestBytes = 1 << 20
	// MaxResponseBytes is the most of an answer an attempt reads.
	MaxResponseBytes = 1 << 20
)

The limits a Policy is refused beyond. They keep a policy written by a person, or stored on an endpoint, from asking for something the engine should not do.

View Source
const (
	FailureEndpointDeleted   = "endpoint_deleted"
	FailureSecretUnavailable = "secret_unavailable"
	FailureUnsupportedScheme = "unsupported_scheme"
	FailureRequestTooLarge   = "request_too_large"
	FailureUnsafeDestination = "unsafe_destination"
	FailureTooManyRedirects  = "too_many_redirects"
	FailureTimeout           = "request_timeout"
	FailureTransport         = "transport_failure"
)

The failure codes an attempt records. An HTTP answer outside 2xx is "http_status_" and its status.

View Source
const (
	StandardHeaderID        = "Webhook-Id"
	StandardHeaderTimestamp = "Webhook-Timestamp"
	StandardHeaderSignature = "Webhook-Signature"
)

The Standard Webhooks headers, as Go writes them on the wire.

View Source
const (
	// DialectArandu signs with hesape's scheme: X-Arandu-Signature, sha256=
	// and the hex HMAC-SHA256 of the timestamp, the delivery id and the body,
	// beside X-Arandu-Timestamp and X-Arandu-Delivery-Id. Server.HeaderPrefix
	// renames the three.
	DialectArandu = "arandu"
	// DialectHMAC is webhook-server's DefaultSigner: the hex HMAC-SHA256 of
	// the body, in the endpoint's SignatureHeader, with the Arandu headers
	// beside it unless the endpoint turns them off.
	DialectHMAC = "hmac"
	// DialectStandardWebhooks is the Standard Webhooks specification:
	// Webhook-Id, Webhook-Timestamp and Webhook-Signature, which carries
	// "v1," and the base64 HMAC-SHA256 of id.timestamp.body under the key the
	// whsec_ secret encodes, once per secret still signing, newest first.
	DialectStandardWebhooks = "standard-webhooks"
)

The dialects an endpoint is called in.

View Source
const (
	// SignerArandu is DialectArandu.
	SignerArandu = DialectArandu
	// SignerHMAC is DialectHMAC.
	SignerHMAC = DialectHMAC
)

The signers of the previous release, which are the first two dialects.

View Source
const (
	// DeliveryPending waits for its next attempt.
	DeliveryPending = "pending"
	// DeliveryProcessing is an attempt in progress.
	DeliveryProcessing = "processing"
	// DeliveryDelivered was answered with a 2xx.
	DeliveryDelivered = "delivered"
	// DeliveryFailed gave up: its attempts are spent, or its last failure was
	// one retrying cannot fix.
	DeliveryFailed = "failed"
)

The states of a delivery.

View Source
const (
	// AttemptScheduled is an attempt the delivery's plan made.
	AttemptScheduled = "scheduled"
	// AttemptManual is an attempt somebody asked for: a redelivery.
	AttemptManual = "manual"
)

What started an attempt.

View Source
const (
	// EndpointView is reading one endpoint, its deliveries and their
	// attempts.
	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 on and 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"
	// EndpointRotate is giving an endpoint a new signing secret, the previous
	// one still signing until it retires.
	EndpointRotate security.Action = "webhook.endpoint.rotate"
	// EndpointImport is storing an endpoint with the id and the secrets it
	// already has elsewhere, for an application moving its own endpoints into
	// this module. Actions does not offer it: it belongs to the data
	// migration, which runs with a subject the application builds for it.
	EndpointImport security.Action = "webhook.endpoint.import"
	// DeliveryRedeliver is sending one of an endpoint's deliveries again: the
	// same id and the same body, under a new timestamp and signature.
	DeliveryRedeliver security.Action = "webhook.delivery.redeliver"

	// 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"

	// DeliveryDispatch is recording deliveries and making their attempts, and
	// DeliveryClean removing the records older than the retention. Nobody
	// holds them: the application's code dispatches under a Grant it issues
	// for itself, the events relay and the worker do the same, and the policy
	// refuses them to any subject. DeliveryDispatch has the value of
	// hesape/webhook's ActionDispatch, so a Grant issued for either is one
	// Grant.
	DeliveryDispatch security.Action = "webhook.dispatch"
	DeliveryClean    security.Action = "webhook.delivery.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 DefaultRotationOverlap = 24 * time.Hour

DefaultRotationOverlap is how long a rotated secret keeps signing beside the new one when the management route rotates it: a day, because installing a secret is a deploy on the receiver's side.

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 DeliverJob = "webhook.server.deliver"

DeliverJob is the queued job that makes one attempt of a delivery.

View Source
const DisabledByFailures = "consecutive_failures"

DisabledByFailures is the reason the engine records when an endpoint's deliveries gave up DisableAfter times in a row.

View Source
const MaxPreviousSecrets = 5

MaxPreviousSecrets is how many previous secrets an endpoint keeps signing with at once.

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 StandardSecretPrefix = "whsec_"

StandardSecretPrefix labels a Standard Webhooks secret: what follows it is the standard base64 of the signing key.

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 (
	// ErrInvalidMessage is a message with no event, an id that cannot be
	// one, or a body that is not text.
	ErrInvalidMessage = errors.New("webhook: a message needs an event name, a body of UTF-8 text, and ids of letters, digits, '_', '.', ':' and '-'")
	// ErrPayloadTooLarge is a body above the policy's MaxRequestBytes.
	ErrPayloadTooLarge = errors.New("webhook: the body is larger than the policy's MaxRequestBytes")
	// ErrHTTPSRequired is an http address under a policy that is HTTPSOnly.
	ErrHTTPSRequired = errors.New("webhook: the policy sends to https addresses only")
	// ErrDeliveryIDTaken is a delivery id already used for another event or
	// endpoint.
	ErrDeliveryIDTaken = statusError{/* contains filtered or unexported fields */}
)

The refusals of a delivery asked for in code.

View Source
var (
	// ErrEndpointExists is an import whose id the tenant already has.
	ErrEndpointExists = statusError{/* contains filtered or unexported fields */}
	// ErrDeliveryInProgress is a redelivery of a delivery whose attempt is
	// in progress.
	ErrDeliveryInProgress = statusError{/* contains filtered or unexported fields */}
	// ErrNotRedeliverable is a delivery that has no stored endpoint to be
	// sent to again: one made by Outgoing.
	ErrNotRedeliverable = statusError{/* contains filtered or unexported fields */}
)

The refusals of the endpoint use cases that are not a policy's.

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.

View Source
var ErrStandardSecret = errors.New("webhook: a Standard Webhooks secret is whsec_ and the standard base64 of a 24 to 64 byte key")

ErrStandardSecret is a secret a Standard Webhooks endpoint cannot sign with: it is not whsec_ and the standard base64 of a key of 24 to 64 bytes.

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.

func SignStandard added in v0.2.0

func SignStandard(secrets []string, id string, timestamp int64, body []byte) (string, error)

SignStandard returns the value of a Webhook-Signature header: "v1," and the standard base64 of the HMAC-SHA256 of id.timestamp.body under each secret's key, in the order given, separated by a space.

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 is the delivery's identity: the Delivery-Id of the Arandu dialect
	// and the Webhook-Id of Standard Webhooks, the same on every attempt.
	ID       string `db:"id"`
	TenantID string `db:"tenant_id"`

	// EventID is what the delivery was made for; one event reaches one
	// endpoint once.
	EventID   string `db:"event_id"`
	EventName string `db:"event_name"`
	// EndpointID is the stored endpoint, or "url:" and a digest of the
	// address for a call built with Outgoing.
	EndpointID string `db:"endpoint_id"`
	URL        string `db:"url"`
	// Dialect is the endpoint's, when the delivery was recorded.
	Dialect string `db:"dialect"`
	// Body is the request body, exactly as every attempt sends it, and
	// BodySHA256 its hex SHA-256.
	Body       string `db:"body"`
	BodySHA256 string `db:"body_sha256"`
	// Headers is the sealed JSON object of extra headers, empty when none.
	Headers string `db:"headers"`
	// SecretRef names where the signing secrets are read from at each
	// attempt: the endpoint, or a secret of Server.Secrets.
	SecretRef string `db:"secret_ref"`
	// SignatureHeader and AranduHeaders are the DialectHMAC settings.
	SignatureHeader string `db:"signature_header"`
	AranduHeaders   Flag   `db:"include_arandu_headers"`
	// Status is pending, processing, delivered or failed.
	Status string `db:"status"`
	// Attempts is how many attempts were made, and MaxAttempts how many it
	// gets.
	Attempts    int64 `db:"attempts"`
	MaxAttempts int64 `db:"max_attempts"`
	// Backoff is the JSON list of the waits before each attempt after the
	// first, in milliseconds, frozen when the delivery was recorded.
	Backoff string `db:"backoff"`
	// Permanent is a failure retrying cannot fix.
	Permanent Flag `db:"permanent"`
	// NextAttemptAt is when the next attempt is due, while it is pending.
	NextAttemptAt  *time.Time `db:"next_attempt_at"`
	ResponseStatus *int64     `db:"response_status"`
	LastError      *string    `db:"last_error"`
	// ClaimToken and LeaseUntil are the attempt in progress: whoever holds the
	// token settles the attempt, and nobody else starts one until the lease
	// ends.
	ClaimToken  *string    `db:"claim_token"`
	LeaseUntil  *time.Time `db:"lease_until"`
	CreatedAt   time.Time  `db:"created_at"`
	UpdatedAt   time.Time  `db:"updated_at"`
	DeliveredAt *time.Time `db:"delivered_at"`
}

Delivery is one message to one address, and where its attempts stand: the body frozen when it was recorded, the extra headers sealed beside it, the number of attempts it gets and the waits between them, and the receiver's last answer. The signature is made at each attempt, so neither a secret nor a signature is ever stored with it.

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.

func (Delivery) Spent added in v0.2.0

func (d Delivery) Spent() bool

Spent reports whether the delivery has no attempt left.

func (Delivery) WaitAfter added in v0.2.0

func (d Delivery) WaitAfter(n int64) time.Duration

WaitAfter is how long the delivery waits after its attempt number n failed, before attempt n+1.

func (Delivery) Waits added in v0.2.0

func (d Delivery) Waits() []time.Duration

Waits returns the waits before each attempt after the first, as frozen when the delivery was recorded.

type DeliveryAttempt added in v0.2.0

type DeliveryAttempt struct {
	model.Model

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

	DeliveryID string `db:"delivery_id"`
	EndpointID string `db:"endpoint_id"`
	// Attempt is its number, from 1.
	Attempt int64 `db:"attempt"`
	// Trigger is AttemptScheduled or AttemptManual.
	Trigger string `db:"triggered_by"`
	// Succeeded is whether the receiver answered with a 2xx.
	Succeeded Flag `db:"succeeded"`
	// ResponseStatus is the receiver's status, nil when there was no answer.
	ResponseStatus *int64 `db:"response_status"`
	// Error is the failure's code, empty when it succeeded.
	Error string `db:"error"`
	// Response is the start of the answer's body, as text, for the panel.
	Response string `db:"response"`
	// DurationMS is how long the request took.
	DurationMS int64 `db:"duration_ms"`
	// SignedAt is the timestamp the attempt was signed with.
	SignedAt  time.Time `db:"signed_at"`
	CreatedAt time.Time `db:"created_at"`
}

DeliveryAttempt is one attempt of a delivery: when it was signed, what the receiver answered, how long it took. It is written when the attempt is settled, in the same transaction as the delivery.

DeliveryAttempts, DeliveryAttemptQuery and DeliveryAttemptCollection are generated beside this file, in DeliveryAttemptQuery.go, by aru model:build.

func (*DeliveryAttempt) Fresh added in v0.2.0

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

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

func (*DeliveryAttempt) Replicate added in v0.2.0

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

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

type DeliveryAttemptCollection added in v0.2.0

type DeliveryAttemptCollection []*DeliveryAttempt

DeliveryAttemptCollection is rows of DeliveryAttempt, as a query returns them.

func DeliveryAttemptCollectionOf added in v0.2.0

func DeliveryAttemptCollectionOf(rows model.Rows) DeliveryAttemptCollection

DeliveryAttemptCollectionOf converts rows of the table of DeliveryAttempt. A nil input is a nil collection.

func (DeliveryAttemptCollection) Contains added in v0.2.0

func (c DeliveryAttemptCollection) Contains(key any) bool

func (DeliveryAttemptCollection) Except added in v0.2.0

func (DeliveryAttemptCollection) Find added in v0.2.0

func (DeliveryAttemptCollection) FindOrFail added in v0.2.0

func (c DeliveryAttemptCollection) FindOrFail(key any) (*DeliveryAttempt, error)

func (DeliveryAttemptCollection) Fresh added in v0.2.0

func (DeliveryAttemptCollection) Load added in v0.2.0

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

func (DeliveryAttemptCollection) LoadCount added in v0.2.0

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

func (DeliveryAttemptCollection) LoadMissing added in v0.2.0

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

func (DeliveryAttemptCollection) ModelKeys added in v0.2.0

func (c DeliveryAttemptCollection) ModelKeys() []any

func (DeliveryAttemptCollection) Only added in v0.2.0

func (DeliveryAttemptCollection) Pluck added in v0.2.0

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

func (DeliveryAttemptCollection) Rows added in v0.2.0

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

func (DeliveryAttemptCollection) ToArray added in v0.2.0

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

func (DeliveryAttemptCollection) Unique added in v0.2.0

type DeliveryAttemptQuery added in v0.2.0

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

DeliveryAttemptQuery is a query on the table of DeliveryAttempt.

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

func DeliveryAttempts added in v0.2.0

func DeliveryAttempts(db model.DB) *DeliveryAttemptQuery

DeliveryAttempts starts a query on the table of DeliveryAttempt, 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 (*DeliveryAttemptQuery) AddSelect added in v0.2.0

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

func (*DeliveryAttemptQuery) Avg added in v0.2.0

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

func (*DeliveryAttemptQuery) Base added in v0.2.0

func (q *DeliveryAttemptQuery) 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 (*DeliveryAttemptQuery) Chunk added in v0.2.0

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

func (*DeliveryAttemptQuery) Clone added in v0.2.0

Clone returns an independent copy of the query.

func (*DeliveryAttemptQuery) Count added in v0.2.0

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

func (*DeliveryAttemptQuery) Create added in v0.2.0

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

func (*DeliveryAttemptQuery) Cursor added in v0.2.0

func (*DeliveryAttemptQuery) CursorPaginate added in v0.2.0

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

func (*DeliveryAttemptQuery) Decrement added in v0.2.0

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

func (*DeliveryAttemptQuery) Delete added in v0.2.0

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

func (*DeliveryAttemptQuery) DoesntExist added in v0.2.0

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

func (*DeliveryAttemptQuery) DoesntHave added in v0.2.0

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

func (*DeliveryAttemptQuery) Each added in v0.2.0

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

func (*DeliveryAttemptQuery) Exists added in v0.2.0

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

func (*DeliveryAttemptQuery) Find added in v0.2.0

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

func (*DeliveryAttemptQuery) FindMany added in v0.2.0

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

func (*DeliveryAttemptQuery) FindOrFail added in v0.2.0

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

func (*DeliveryAttemptQuery) FindOrNew added in v0.2.0

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

func (*DeliveryAttemptQuery) First added in v0.2.0

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

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

func (*DeliveryAttemptQuery) FirstOrCreate added in v0.2.0

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

func (*DeliveryAttemptQuery) FirstOrFail added in v0.2.0

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

func (*DeliveryAttemptQuery) FirstOrNew added in v0.2.0

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

func (*DeliveryAttemptQuery) FirstWhere added in v0.2.0

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

func (*DeliveryAttemptQuery) ForPage added in v0.2.0

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

func (*DeliveryAttemptQuery) ForceCreate added in v0.2.0

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

func (*DeliveryAttemptQuery) Get added in v0.2.0

Get runs the query and returns the rows.

func (*DeliveryAttemptQuery) GroupBy added in v0.2.0

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

func (*DeliveryAttemptQuery) Has added in v0.2.0

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

func (*DeliveryAttemptQuery) Increment added in v0.2.0

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

func (*DeliveryAttemptQuery) Insert added in v0.2.0

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

func (*DeliveryAttemptQuery) InsertOrIgnore added in v0.2.0

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

func (*DeliveryAttemptQuery) Join added in v0.2.0

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

func (*DeliveryAttemptQuery) Latest added in v0.2.0

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

func (*DeliveryAttemptQuery) Limit added in v0.2.0

func (*DeliveryAttemptQuery) Max added in v0.2.0

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

func (*DeliveryAttemptQuery) Min added in v0.2.0

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

func (*DeliveryAttemptQuery) New added in v0.2.0

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

func (*DeliveryAttemptQuery) Offset added in v0.2.0

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

func (*DeliveryAttemptQuery) Oldest added in v0.2.0

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

func (*DeliveryAttemptQuery) OrDoesntHave added in v0.2.0

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

func (*DeliveryAttemptQuery) OrHas added in v0.2.0

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

func (*DeliveryAttemptQuery) OrWhere added in v0.2.0

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

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

func (*DeliveryAttemptQuery) OrWhereDoesntHave added in v0.2.0

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

func (*DeliveryAttemptQuery) OrWhereHas added in v0.2.0

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

func (*DeliveryAttemptQuery) OrWhereNot added in v0.2.0

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

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

func (*DeliveryAttemptQuery) OrWhereRelation added in v0.2.0

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

func (*DeliveryAttemptQuery) OrderBy added in v0.2.0

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

func (*DeliveryAttemptQuery) OrderByDesc added in v0.2.0

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

func (*DeliveryAttemptQuery) Paginate added in v0.2.0

func (*DeliveryAttemptQuery) Pluck added in v0.2.0

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

func (*DeliveryAttemptQuery) Select added in v0.2.0

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

func (*DeliveryAttemptQuery) SelectRaw added in v0.2.0

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

func (*DeliveryAttemptQuery) SimplePaginate added in v0.2.0

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

func (*DeliveryAttemptQuery) Sole added in v0.2.0

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

func (*DeliveryAttemptQuery) SoleValue added in v0.2.0

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

func (*DeliveryAttemptQuery) Sum added in v0.2.0

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

func (*DeliveryAttemptQuery) Touch added in v0.2.0

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

func (*DeliveryAttemptQuery) Update added in v0.2.0

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

func (*DeliveryAttemptQuery) UpdateOrCreate added in v0.2.0

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

func (*DeliveryAttemptQuery) Upsert added in v0.2.0

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

func (*DeliveryAttemptQuery) Value added in v0.2.0

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

func (*DeliveryAttemptQuery) ValueOrFail added in v0.2.0

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

func (*DeliveryAttemptQuery) Where added in v0.2.0

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

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

func (*DeliveryAttemptQuery) WhereBelongsTo added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereBetween added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereColumn added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereDoesntHave added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereExists added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereHas added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereHasCount added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereIn added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereKey added in v0.2.0

func (*DeliveryAttemptQuery) WhereNot added in v0.2.0

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

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

func (*DeliveryAttemptQuery) WhereNotExists added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereNotIn added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereNotNull added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereNull added in v0.2.0

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

func (*DeliveryAttemptQuery) WhereRelation added in v0.2.0

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

func (*DeliveryAttemptQuery) With added in v0.2.0

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

func (*DeliveryAttemptQuery) WithAvg added in v0.2.0

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

func (*DeliveryAttemptQuery) WithCount added in v0.2.0

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

func (*DeliveryAttemptQuery) WithExists added in v0.2.0

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

func (*DeliveryAttemptQuery) WithMax added in v0.2.0

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

func (*DeliveryAttemptQuery) WithMin added in v0.2.0

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

func (*DeliveryAttemptQuery) WithSum added in v0.2.0

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

func (*DeliveryAttemptQuery) Without added in v0.2.0

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

func (*DeliveryAttemptQuery) WithoutGlobalScope added in v0.2.0

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

func (*DeliveryAttemptQuery) WithoutGlobalScopes added in v0.2.0

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

type DeliveryAttemptResource added in v0.2.0

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

DeliveryAttemptResource is the list of fields one attempt may answer with.

func NewDeliveryAttemptResource added in v0.2.0

func NewDeliveryAttemptResource(a *DeliveryAttempt) DeliveryAttemptResource

NewDeliveryAttemptResource snapshots one attempt for the response.

func (DeliveryAttemptResource) ToArray added in v0.2.0

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

ToArray returns the fields that may leave, by name.

func (DeliveryAttemptResource) With added in v0.2.0

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

With adds nothing beside the fields.

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 body, the headers 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"`
	// Dialect is how its calls are signed and which headers they carry:
	// DialectArandu, DialectHMAC or DialectStandardWebhooks.
	Dialect string `db:"dialect"`
	// Signer is the column the previous release read the dialect from, kept in step with
	// Dialect for a binary of that release during a rollout: DialectArandu or
	// DialectHMAC, and DialectArandu for a dialect it does not know. Read
	// Dialect.
	Signer string `db:"signer"`
	// SignatureHeader is the header a DialectHMAC signature travels in.
	SignatureHeader string `db:"signature_header"`
	// AranduHeaders is whether a DialectHMAC call also carries the Arandu
	// signature, timestamp and delivery id headers. The other dialects ignore
	// it: an Arandu call always carries them, a Standard Webhooks call never.
	AranduHeaders Flag `db:"include_arandu_headers"`
	// Secret is the sealed signing secret. It never leaves in a response.
	Secret string `db:"secret"`
	// PreviousSecrets is the sealed JSON list of the secrets still signing
	// after a rotation, each until it retires; empty when none.
	PreviousSecrets string `db:"previous_secrets"`
	// Headers is the sealed JSON object of extra headers, empty when none.
	Headers string `db:"headers"`
	// Policy is the JSON of the endpoint's own delivery policy, empty when it
	// follows Server.Policy.
	Policy string `db:"policy"`
	// Active is whether it receives events.
	Active Flag `db:"active"`
	// ConsecutiveFailures is how many of its deliveries in a row gave up.
	ConsecutiveFailures int `db:"consecutive_failures"`
	// DisabledAt and DisabledReason say when and why it was turned off by the
	// engine; both are cleared when it is turned on.
	DisabledAt     *time.Time `db:"disabled_at"`
	DisabledReason string     `db:"disabled_reason"`

	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) Disable added in v0.2.0

func (e *Endpoint) Disable(reason string, now time.Time)

Disable turns the endpoint off, and keeps why when the engine did it.

func (*Endpoint) Enable added in v0.2.0

func (e *Endpoint) Enable(now time.Time)

Enable turns the endpoint on and clears what the engine recorded when it turned it off.

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.

func (Endpoint) ShouldDisable added in v0.2.0

func (e Endpoint) ShouldDisable(disableAfter int) bool

ShouldDisable reports whether the engine turns the endpoint off: it is active, and disableAfter of its deliveries in a row gave up.

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
	// Dialect is DialectArandu (empty means it, unless Signer names one),
	// DialectHMAC or DialectStandardWebhooks.
	Dialect string
	// Signer is the previous release's name of Dialect, read when Dialect is empty.
	Signer string
	// SignatureHeader is where a DialectHMAC signature goes. Empty means
	// DefaultSignatureHeader.
	SignatureHeader string
	// WithoutAranduHeaders sends a DialectHMAC call with its signature
	// header alone, without the Arandu signature, timestamp and delivery id.
	WithoutAranduHeaders bool
	// Headers is "Name: value" per line. On an update, empty keeps the stored
	// headers, which are never shown back.
	Headers string
	Active  bool
	// Policy is the endpoint's own delivery policy. Nil follows Server.Policy
	// on a create, and keeps what the endpoint has on an update.
	Policy *Policy
	// ResetPolicy, on an update, drops the endpoint's own policy, so it
	// follows Server.Policy again.
	ResetPolicy 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, the deliveries and their attempts. It owns the database handle; every exported method asks the policy before it reads or writes, and every act that changes something, or reveals a secret, stores its event in the application's outbox in the same transaction.

func (*EndpointService) Attempts added in v0.2.0

func (s *EndpointService) Attempts(ctx context.Context, actor security.Subject, deliveryID string) ([]*DeliveryAttempt, error)

Attempts returns a delivery's attempts, first first: n of MaxAttempts, what the receiver answered, when, and how long it took.

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. Its deliveries still waiting fail as FailureEndpointDeleted at their next attempt: there is nothing left to sign them with.

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) Delivery added in v0.2.0

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

Delivery returns one delivery of the tenant's endpoints.

func (*EndpointService) Disable added in v0.2.0

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

Disable turns an endpoint off. Its deliveries wait until it is turned on; a redelivery or a test still goes.

func (*EndpointService) Enable added in v0.2.0

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

Enable turns an endpoint on, clears what the engine recorded when it turned it off, and queues again the deliveries it kept waiting.

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) Import added in v0.2.0

Import stores an endpoint an application kept elsewhere, keeping its id and its secrets, sealed with this module's key and bound to the tenant and the id. It is the data migration's: EndpointImport is not among Actions.

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) Redeliver added in v0.2.0

func (s *EndpointService) Redeliver(ctx context.Context, actor security.Subject, deliveryID string) (*Delivery, error)

Redeliver sends a delivery again, now: the same id and the same body, under a new timestamp and signature, whether it was delivered, gave up or is waiting. A delivery that had no attempt left is given one more.

func (*EndpointService) Rotate added in v0.2.0

func (s *EndpointService) Rotate(ctx context.Context, actor security.Subject, id string, overlap time.Duration) (string, error)

Rotate gives an endpoint a new signing secret, answered in clear this once. The secret it had keeps signing beside the new one for overlap -- a Standard Webhooks call then carries both signatures -- and one still retiring from an earlier rotation is revoked. An overlap of zero or less revokes the old secret at once.

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) (*Delivery, error)

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

func (*EndpointService) Update

Update changes an endpoint. The secret stays, and empty headers keep the stored ones. Turning it on here is turning it on with Enable: the count of failures is cleared, and the deliveries it kept waiting go. A change to DialectStandardWebhooks needs a secret that dialect can sign with: rotate first when the endpoint's is not one.

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 ImportRequest added in v0.2.0

type ImportRequest struct {
	// ID is kept as the endpoint's id: up to 64 letters, digits, '_', '.',
	// ':' and '-'.
	ID string
	EndpointRequest
	// Secret is the current signing secret, kept as it is: whsec_ and the
	// standard base64 of the key for DialectStandardWebhooks, at least
	// MinSecretBytes for the others.
	Secret string
	// PreviousSecrets still sign until each retires; the ones already
	// retired are dropped.
	PreviousSecrets []PreviousSecret
	// CreatedAt is when the endpoint was created where it comes from. Zero
	// means now.
	CreatedAt time.Time
}

ImportRequest is an endpoint an application already keeps elsewhere, stored with the id and the secrets it has, so the receivers notice nothing.

type Message added in v0.2.0

type Message struct {
	// ID is the delivery's id: the Delivery-Id of the Arandu dialect and the
	// Webhook-Id of Standard Webhooks, the same on every attempt and every
	// redelivery. Up to 128 letters, digits, '_', '.', ':' and '-'. Empty
	// makes one. One id is one delivery: a message to two endpoints takes
	// two ids.
	ID string
	// EventID is what the delivery is for; a second message with the same
	// EventID to the same endpoint is answered with the first delivery.
	// Empty means ID.
	EventID string
	// Event is the event's name.
	Event string
	// Body is sent exactly as it is, on every attempt. It is UTF-8 text,
	// JSON for every dialect this module speaks.
	Body []byte
}

Message is one delivery asked for in code, to a stored endpoint.

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) Migrations

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

Migrations declares the schema this module owns: the endpoints, the received calls, the deliveries and their attempts. The jobs table is the queue's, and the outbox the management events are stored in is hesape's events, both migrated by the application.

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 active endpoints subscribed to it. The application adds it to its relay's publishers. The module's own management events, which travel on the same outbox, are not delivered.

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 attempts of the deliveries and the processing of received calls. The worker is the application's, started by its worker command, on Server.Queue.

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 cleans: of old received calls, as webhook-client's model:prune of WebhookCall, in each receiver's tenant; and of the deliveries settled longer ago than Server.Retention, with their attempts, in every tenant. CleanSpec "-" declares none.

func (*Module) Send added in v0.2.0

func (m *Module) Send(ctx context.Context, g security.Grant, endpointID string, msg Message) (*Delivery, error)

Send records one message to one of the tenant's endpoints, whatever it subscribes to, and queues its first attempt in the same transaction: the message the application built, its id and its body sent exactly as given on every attempt. g carries DeliveryDispatch for the tenant: the application decided to send, and that decision is the Grant. A second message with the same EventID to the same endpoint answers the first delivery and sends nothing.

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 attempt in one transaction; the worker sends it, signed, under Server.Policy. g carries DeliveryDispatch 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.

func (*Outgoing) WithoutAranduHeaders added in v0.2.0

func (o *Outgoing) WithoutAranduHeaders() *Outgoing

WithoutAranduHeaders sends the HMAC signature SignWithHMAC adds alone, without the Arandu signature, timestamp and delivery id headers.

type Policy added in v0.2.0

type Policy struct {
	// Tries is how many attempts a delivery gets, the first included.
	Tries int
	// Backoff is the wait before each attempt after the first: Backoff[0]
	// before the second, Backoff[1] before the third. When it is shorter than
	// Tries-1 its last wait is repeated. There is no jitter: the next attempt
	// is written on the delivery, so two workers never race for one.
	Backoff []time.Duration
	// Retry is which failures are tried again. Empty means RetryTransient.
	Retry RetryMode
	// HonorRetryAfter waits as long as the receiver's Retry-After asks before
	// the next attempt, when that is longer than Backoff says.
	HonorRetryAfter bool
	// HTTPSOnly refuses an address that is not https, at the endpoint's form
	// and at every attempt.
	HTTPSOnly bool
	// MaxRedirects is how many redirects an attempt follows; 0 follows none,
	// and the redirect itself is then the answer, a failure.
	MaxRedirects int
	// MaxRequestBytes is the largest body a delivery may be recorded with.
	MaxRequestBytes int64
	// MaxResponseBytes is how much of an answer an attempt reads; the rest is
	// left unread, and the answer's status decides the attempt.
	MaxResponseBytes int64
	// Timeout is how long one attempt may take, the answer read included.
	Timeout time.Duration
	// UserAgent is sent with every attempt. Empty sends none of ours: Go's
	// client then says what it is.
	UserAgent string
	// DisableAfter turns a stored endpoint off once this many of its
	// deliveries in a row gave up, and records why. A delivered one resets
	// the count. 0 never turns one off.
	DisableAfter int
}

Policy is how a delivery is made. It is set on Server for every endpoint, and an endpoint may carry its own. Tries and Backoff are frozen on each delivery when it is recorded, so changing a policy neither revives a delivery that gave up nor shortens one already waiting; the rest is read at every attempt.

A Policy is used as written: a zero field means zero, not a default. Start from DefaultPolicy to change one thing.

func DefaultPolicy added in v0.2.0

func DefaultPolicy() Policy

DefaultPolicy is the policy a delivery gets when nobody chose one: 5 attempts, waiting 5 s, 30 s, 2 min and 10 min between them, the receiver's Retry-After honored, 408, 425, 429 and the 5xx tried again and every other answer final, at most 5 redirects, http or https, a body of up to 1 MiB, the first 1 MiB of an answer read, 10 s per attempt, and DefaultUserAgent.

func (Policy) Validate added in v0.2.0

func (p Policy) Validate() error

Validate reports what the policy cannot be used with.

type PreviousSecret added in v0.2.0

type PreviousSecret struct {
	Secret    string    `json:"secret"`
	RetiresAt time.Time `json:"retires_at"`
}

PreviousSecret is a secret that still signs after a rotation, until it retires.

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 RetryMode added in v0.2.0

type RetryMode string

RetryMode says which failed attempts are tried again.

const (
	// RetryTransient tries again what another attempt may fix -- a transport
	// error, 408, 425, 429 and the 5xx -- and gives up at once on every other
	// answer, on an address that is refused and on a body too large. It is
	// the default.
	RetryTransient RetryMode = "transient"
	// RetryEveryFailure tries again every failed attempt, whatever it was,
	// until the tries are spent: any answer outside 2xx is a failure like any
	// other.
	RetryEveryFailure RetryMode = "every-failure"
)

type Server

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

	// Policy is how every delivery is made: the attempts and the waits between
	// them, what counts as a failure, the transport's limits. An endpoint may
	// carry its own. Nil means DefaultPolicy, with UserAgent and Timeout below
	// laid over it.
	Policy *Policy

	// UserAgent is sent with every call when Policy is nil. Empty means
	// DefaultUserAgent. Set Policy.UserAgent instead when Policy is set.
	UserAgent string

	// Timeout is how long one call may take when Policy is nil. Zero means
	// DefaultTimeout. Set Policy.Timeout instead when Policy is set.
	Timeout time.Duration

	// Retention is how long a delivery's record and its attempts are kept.
	// Zero means DefaultRetention.
	Retention time.Duration

	// Now is the clock the deliveries read: the signed timestamps, the next
	// attempt, the lease. Nil is time.Now, and a test replaces it.
	Now func() time.Time

	// 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 guarded client, with a
	// dialer that also refuses the shared address space (100.64.0.0/10): no
	// call reaches a loopback, private, link-local or shared address, on any
	// hop. A client given here keeps its own transport and with it its own
	// answer to where a call may connect, which is what a test that delivers
	// to a local server needs.
	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