package handlers import ( "fmt" "time" "github.com/dustin/go-humanize" "gorm.io/gorm" "sneak.berlin/go/webhooker/internal/database" ) // EntrypointView is the display-safe projection of an entrypoint for // the UI, in the same way delivery.TargetView is one for a target. type EntrypointView struct { ID string Path string Description string Active bool // Events is how many events arrived through the entrypoint within // the webhook's retention period. LastEvent is when the newest of // them arrived, relative, and LastEventUTC the full time; both are // empty when none did. Events int64 LastEvent string LastEventUTC string } // entrypointEvents is one entrypoint's row read by addEntrypointEvents. type entrypointEvents struct { EntrypointID string Events int64 LastEventAt time.Time } // NewEntrypointViews projects entrypoints for rendering. func NewEntrypointViews( entrypoints []database.Entrypoint, ) []EntrypointView { views := make([]EntrypointView, 0, len(entrypoints)) for i := range entrypoints { e := &entrypoints[i] views = append(views, EntrypointView{ ID: e.ID, Path: e.Path, Description: e.Description, Active: e.Active, }) } return views } // addEntrypointEvents fills in each view's event figures from the // webhook's event database, in one query over the events' // entrypoint_id index. Only events received since the webhook's // retention cutoff are read. func addEntrypointEvents( webhookDB *gorm.DB, webhook *database.Webhook, views []EntrypointView, now time.Time, ) error { ids := make([]string, len(views)) for i := range views { ids[i] = views[i].ID } // max(created_at) picks each entrypoint's newest row, and SQLite // takes the bare created_at beside it from that row. Unlike max(), // the plain column keeps its datetime type, so the driver returns // it as a time. query := webhookDB.Model(&database.Event{}). Select("entrypoint_id, count(*) AS events, "+ "max(created_at), created_at AS last_event_at"). Where("entrypoint_id IN ?", ids) cutoff, finite := webhook.RetentionCutoff(now) if finite { query = query.Where("created_at >= ?", cutoff) } var rows []entrypointEvents err := query.Group("entrypoint_id").Find(&rows).Error if err != nil { return fmt.Errorf("counting events by entrypoint: %w", err) } byID := make(map[string]entrypointEvents, len(rows)) for _, row := range rows { byID[row.EntrypointID] = row } for i := range views { row, found := byID[views[i].ID] if !found { continue } views[i].Events = row.Events views[i].LastEvent = humanize.Time(row.LastEventAt) views[i].LastEventUTC = row.LastEventAt.UTC().Format(time.DateTime) + " UTC" } return nil }