Files
webhooker/internal/database/model_totals.go
T
clawbot 1adb625a7a
check / check (push) Successful in 3m8s
Show each entrypoint's last event and event count on the webhook page (closes #393)
Each entrypoint in the webhook page's entrypoint list shows when the last event arrived on its URL (relative, with the full UTC time on hover), or "never" if none ever has, and how many events arrived on it within the webhook's retention period. The last event is kept per entrypoint in a new EntrypointTotals row in the event database, written in the transaction that stores the event, so retention leaves it in place. The count is one query per page, grouped by entrypoint, over a new events index on entrypoint_id, deleted_at, resubmitted_from_id and created_at. Resubmitted copies did not arrive on the URL and count in neither. Pre-1.0: the table and index go into the schema in place.

Model: opus-5-5
2026-10-02 20:52:16 +00:00

137 lines
4.2 KiB
Go

package database
import (
"fmt"
"time"
"gorm.io/gorm"
)
// The running totals in a webhook's event database keep the webhook
// page's lifetime figures right after retention has removed the rows
// they count, and let the page show them without counting every row.
// Each total changes in the transaction that writes or deletes the
// rows it counts.
// EventTotals is the single row counting a webhook's events: every
// event ever stored, how many of them retention has deleted, and when
// the newest arrived, which retention leaves as it is.
type EventTotals struct {
ID int64 `gorm:"primaryKey"`
Events int64 `gorm:"not null"`
EventsRemoved int64 `gorm:"not null"`
// LastEventAt is when the newest event arrived, or nil before the
// first.
LastEventAt *time.Time
}
// TableName names the table AddEventTotals updates.
func (EventTotals) TableName() string {
return "event_totals"
}
// TargetTotals is one row per target counting its deliveries: every
// delivery ever created, how many became delivered and how many
// failed, and how many deliveries and failed deliveries retention has
// deleted. The webhook's delivery figures are these rows summed.
type TargetTotals struct {
TargetID string `gorm:"type:uuid;primaryKey"`
Deliveries int64 `gorm:"not null"`
Delivered int64 `gorm:"not null"`
Failed int64 `gorm:"not null"`
DeliveriesRemoved int64 `gorm:"not null"`
FailedRemoved int64 `gorm:"not null"`
}
// TableName names the table AddTargetTotals updates.
func (TargetTotals) TableName() string {
return "target_totals"
}
// EntrypointTotals is one row per entrypoint, created by the first
// event that arrives on its URL: when the newest such event arrived,
// which retention leaves as it is. A resubmitted copy did not arrive
// on the URL and does not change it.
type EntrypointTotals struct {
EntrypointID string `gorm:"type:uuid;primaryKey"`
LastEventAt time.Time `gorm:"not null"`
}
// TableName names the table AddEntrypointTotals updates.
func (EntrypointTotals) TableName() string {
return "entrypoint_totals"
}
// AddEventTotals adds each count in add to the webhook's event totals,
// and records add.LastEventAt as when the newest event arrived if it is
// set. Call it on the transaction that writes or deletes the events it
// counts.
func AddEventTotals(tx *gorm.DB, add EventTotals) error {
err := tx.Exec(
`UPDATE event_totals SET
events = events + ?,
events_removed = events_removed + ?,
last_event_at = coalesce(?, last_event_at)`,
add.Events, add.EventsRemoved, add.LastEventAt,
).Error
if err != nil {
return fmt.Errorf("adding to event totals: %w", err)
}
return nil
}
// AddTargetTotals adds each count in add to the totals of the target
// add.TargetID names, creating its row the first time. Call it on the
// transaction that writes or deletes the deliveries it counts.
func AddTargetTotals(tx *gorm.DB, add TargetTotals) error {
err := tx.Exec(
`INSERT INTO target_totals (target_id, deliveries, delivered,
failed, deliveries_removed, failed_removed)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT (target_id) DO UPDATE SET
deliveries = deliveries + excluded.deliveries,
delivered = delivered + excluded.delivered,
failed = failed + excluded.failed,
deliveries_removed =
deliveries_removed + excluded.deliveries_removed,
failed_removed = failed_removed + excluded.failed_removed`,
add.TargetID, add.Deliveries, add.Delivered,
add.Failed, add.DeliveriesRemoved, add.FailedRemoved,
).Error
if err != nil {
return fmt.Errorf(
"adding to totals of target %s: %w", add.TargetID, err,
)
}
return nil
}
// AddEntrypointTotals records add.LastEventAt as when the newest event
// arrived on the URL of the entrypoint add.EntrypointID names, creating
// its row the first time. Call it on the transaction that stores the
// event.
func AddEntrypointTotals(tx *gorm.DB, add EntrypointTotals) error {
err := tx.Exec(
`INSERT INTO entrypoint_totals (entrypoint_id, last_event_at)
VALUES (?, ?)
ON CONFLICT (entrypoint_id) DO UPDATE SET
last_event_at = excluded.last_event_at`,
add.EntrypointID, add.LastEventAt,
).Error
if err != nil {
return fmt.Errorf(
"adding to totals of entrypoint %s: %w",
add.EntrypointID, err,
)
}
return nil
}