Files
job-search/internal/events/events.go
T
bwilliams 155b84caea Added change batches, so clients can catch up on changed opportunities
Every save that stores new opportunities or new releases records them in a
change batch (migration 00008: opportunity_change_batches and
opportunity_change_batch_opportunities; the opportunities table is
unchanged), as does every change of marks. A batch is named by a random
SHA-256 hash, so it can't be guessed, and ordered by its created_at, which
is kept strictly increasing even if the clock goes back.

GET /api/opportunities pages now carry the newest batch, read before the
page, and GET /api/opportunities?changed_after=<batch> returns every
opportunity changed since that batch, unpaged, with the newest batch it
includes. Offset and limit can't be combined with it, and an unknown batch
is 410 Gone, telling the client to reload the whole list.

OpportunitiesUpdated now carries its save's batch, and a "large" flag: over
50 opportunities, new and updated are left empty and only the summary
counts them, so a big catch-up no longer sends tens of thousands of UUIDs.
2026-10-16 15:25:48 +01:00

67 lines
2.8 KiB
Go

// Package events passes events between the parts of JobSearch that cause
// them, such as the fetcher, and the parts that react to them, such as the
// WebSocket hub, so neither depends on the other. It holds the event types
// and their payloads.
package events
import "git.avsdev.uk/Go/job-search/internal/types"
// Event types: an Event's Type, which says what its Payload is.
const (
// OpportunitiesUpdated is published when a save stores new
// opportunities or new releases of stored ones. Its payload is an
// OpportunitiesUpdatedPayload.
OpportunitiesUpdated = "OpportunitiesUpdated"
// OpportunityMarksUpdated is published when an opportunity's marks are
// changed over the API. Its payload is the opportunity's
// types.OpportunityMarks after the change, which include its UUID.
OpportunityMarksUpdated = "OpportunityMarksUpdated"
// ClassificationsUpdated is published when a save brings classification
// codes into use (some opportunity has them) or takes them out of use.
// Its payload is a ClassificationsUpdatedPayload.
ClassificationsUpdated = "ClassificationsUpdated"
)
// ---------- OpportunitiesUpdated ----------
// MaxListedOpportunities is the most opportunities an
// OpportunitiesUpdatedPayload lists by UUID; more make it Large.
const MaxListedOpportunities = 50
// OpportunitiesUpdatedPayload lists the opportunities a save changed by UUID:
// New were stored for the first time, and Updated were already stored and
// gained at least one new release. Summary counts them.
//
// When there are more than MaxListedOpportunities, Large is true and New and
// Updated are empty, to keep the event small; Summary still counts them.
// Neither is ever null.
//
// Batch is the hash of the store's change batch recording them. A client
// gets them, with any later changes, from GET /api/opportunities with
// changed_after set to the last batch it has seen.
type OpportunitiesUpdatedPayload struct {
New []string `json:"new"`
Updated []string `json:"updated"`
Large bool `json:"large"`
Summary OpportunitiesSummary `json:"summary"`
Batch string `json:"batch"`
}
// OpportunitiesSummary counts the opportunities in an
// OpportunitiesUpdatedPayload.
type OpportunitiesSummary struct {
New int `json:"new"`
Updated int `json:"updated"`
}
// ---------- ClassificationsUpdated ----------
// ClassificationsUpdatedPayload is how the classification codes in use
// changed, so a client's list of them can be kept up to date without asking
// for it again: Added are the codes now in use, in full, and Removed the ids
// of those no longer in use. Neither is ever null.
type ClassificationsUpdatedPayload struct {
Added []types.ClassificationCode `json:"added"`
Removed []int64 `json:"removed"`
}