// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: queries.sql package db import ( "context" "encoding/json" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) const addChannelConfigScopes = `-- name: AddChannelConfigScopes :exec INSERT INTO channel_config_scopes (key_fingerprint, region_slug) SELECT unnest($1::bytea[]), NULLIF(unnest($2::text[]), '') ` type AddChannelConfigScopesParams struct { Fingerprints [][]byte `json:"fingerprints"` Regions []string `json:"regions"` } // An empty region places the channel Beacon-wide. func (q *Queries) AddChannelConfigScopes(ctx context.Context, arg AddChannelConfigScopesParams) error { _, err := q.db.Exec(ctx, addChannelConfigScopes, arg.Fingerprints, arg.Regions) return err } const addChannelMembers = `-- name: AddChannelMembers :exec INSERT INTO meshmapper_channel_members (iata, key_fingerprint) SELECT $1, unnest($2::bytea[]) ON CONFLICT (iata, key_fingerprint) DO NOTHING ` type AddChannelMembersParams struct { Iata string `json:"iata"` Fingerprints [][]byte `json:"fingerprints"` } func (q *Queries) AddChannelMembers(ctx context.Context, arg AddChannelMembersParams) error { _, err := q.db.Exec(ctx, addChannelMembers, arg.Iata, arg.Fingerprints) return err } const addIATAs = `-- name: AddIATAs :exec INSERT INTO iata_codes (iata) SELECT unnest($1::bpchar[]) ON CONFLICT (iata) DO NOTHING ` func (q *Queries) AddIATAs(ctx context.Context, iatas []string) error { _, err := q.db.Exec(ctx, addIATAs, iatas) return err } const addRegionIATAs = `-- name: AddRegionIATAs :exec INSERT INTO region_iatas (region_id, iata) SELECT $1, unnest($2::bpchar[]) ON CONFLICT (region_id, iata) DO NOTHING ` type AddRegionIATAsParams struct { RegionID int32 `json:"region_id"` Iatas []string `json:"iatas"` } func (q *Queries) AddRegionIATAs(ctx context.Context, arg AddRegionIATAsParams) error { _, err := q.db.Exec(ctx, addRegionIATAs, arg.RegionID, arg.Iatas) return err } const ambiguousPrefixes = `-- name: AmbiguousPrefixes :many SELECT iata::text AS iata, 1::int AS len, prefix_1 AS prefix FROM node_short_ids GROUP BY iata, prefix_1 HAVING COUNT(*) > 1 UNION ALL SELECT iata::text, 2::int, prefix_2 FROM node_short_ids GROUP BY iata, prefix_2 HAVING COUNT(*) > 1 UNION ALL SELECT iata::text, 3::int, prefix_3 FROM node_short_ids GROUP BY iata, prefix_3 HAVING COUNT(*) > 1 UNION ALL SELECT iata::text, 4::int, prefix_4 FROM node_short_ids GROUP BY iata, prefix_4 HAVING COUNT(*) > 1 ` type AmbiguousPrefixesRow struct { Iata string `json:"iata"` Len int32 `json:"len"` Prefix []byte `json:"prefix"` } // Hop prefixes that match >1 node in an IATA, per width. Computed once per reconfirm run. func (q *Queries) AmbiguousPrefixes(ctx context.Context) ([]AmbiguousPrefixesRow, error) { rows, err := q.db.Query(ctx, ambiguousPrefixes) if err != nil { return nil, err } defer rows.Close() items := []AmbiguousPrefixesRow{} for rows.Next() { var i AmbiguousPrefixesRow if err := rows.Scan(&i.Iata, &i.Len, &i.Prefix); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const createAccount = `-- name: CreateAccount :one INSERT INTO accounts (name) VALUES ($1) ON CONFLICT (name) WHERE deactivated_at IS NULL DO NOTHING RETURNING id, name, created_at, deactivated_at ` func (q *Queries) CreateAccount(ctx context.Context, name string) (Account, error) { row := q.db.QueryRow(ctx, createAccount, name) var i Account err := row.Scan( &i.ID, &i.Name, &i.CreatedAt, &i.DeactivatedAt, ) return i, err } const deactivateAccount = `-- name: DeactivateAccount :one WITH target AS MATERIALIZED ( SELECT a.id, a.deactivated_at FROM accounts a WHERE a.id = $1 FOR UPDATE ), changed AS ( UPDATE accounts a SET deactivated_at = NOW() FROM target t WHERE a.id = t.id AND t.deactivated_at IS NULL RETURNING a.id ) SELECT EXISTS(SELECT 1 FROM target) AS found, EXISTS(SELECT 1 FROM changed) AS deactivated ` type DeactivateAccountRow struct { Found bool `json:"found"` Deactivated bool `json:"deactivated"` } // Lock the current row before deciding the outcome, including when another // deactivation commits while this statement is waiting for its row lock. func (q *Queries) DeactivateAccount(ctx context.Context, id uuid.UUID) (DeactivateAccountRow, error) { row := q.db.QueryRow(ctx, deactivateAccount, id) var i DeactivateAccountRow err := row.Scan(&i.Found, &i.Deactivated) return i, err } const deleteAllChannelMembers = `-- name: DeleteAllChannelMembers :exec DELETE FROM meshmapper_channel_members ` func (q *Queries) DeleteAllChannelMembers(ctx context.Context) error { _, err := q.db.Exec(ctx, deleteAllChannelMembers) return err } const deleteChannelConfigScopes = `-- name: DeleteChannelConfigScopes :exec DELETE FROM channel_config_scopes ` func (q *Queries) DeleteChannelConfigScopes(ctx context.Context) error { _, err := q.db.Exec(ctx, deleteChannelConfigScopes) return err } const deleteChannelMembersNotIn = `-- name: DeleteChannelMembersNotIn :exec DELETE FROM meshmapper_channel_members WHERE iata = $1 AND NOT (key_fingerprint = ANY($2::bytea[])) ` type DeleteChannelMembersNotInParams struct { Iata string `json:"iata"` Keep [][]byte `json:"keep"` } func (q *Queries) DeleteChannelMembersNotIn(ctx context.Context, arg DeleteChannelMembersNotInParams) error { _, err := q.db.Exec(ctx, deleteChannelMembersNotIn, arg.Iata, arg.Keep) return err } const deleteOldChannelIATAs = `-- name: DeleteOldChannelIATAs :exec DELETE FROM channel_iatas WHERE last_heard < $1 ` // Keeps the channel IATA filter in step with packet retention. func (q *Queries) DeleteOldChannelIATAs(ctx context.Context, lastHeard pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, deleteOldChannelIATAs, lastHeard) return err } const deleteOldNodes = `-- name: DeleteOldNodes :exec DELETE FROM nodes WHERE last_seen < $1 AND id NOT IN (SELECT owner_node_id FROM observer_owners WHERE owner_node_id IS NOT NULL) ` // Deletes nodes not seen since the given cutoff. node_iatas and node_neighbors cascade- // delete via FK. Excludes nodes referenced by observer_owners.owner_node_id -- that FK has // no ON DELETE action, so deleting one directly would fail the whole statement anyway, and // an operator manually recorded ownership for that node, so leave it alone even if stale. // known_routes.node_ids is a plain UUID[] with no FK; a deleted node's id can be left // dangling in old routes there, but ReconfirmTask already prunes stale/ambiguous routes // periodically and will clean those up on its own schedule. func (q *Queries) DeleteOldNodes(ctx context.Context, lastSeen pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, deleteOldNodes, lastSeen) return err } const deleteOldObservers = `-- name: DeleteOldObservers :many WITH expired AS ( SELECT o.id FROM observers o WHERE o.last_seen < $1 AND (o.last_status_at IS NULL OR o.last_status_at < $1) AND NOT EXISTS (SELECT 1 FROM packet_observations po WHERE po.observer_id = o.id) AND NOT EXISTS (SELECT 1 FROM observer_telemetry ot WHERE ot.observer_id = o.id) AND NOT EXISTS (SELECT 1 FROM observer_owners oo WHERE oo.observer_id = o.id) ORDER BY o.last_seen, o.id LIMIT 1000 FOR UPDATE OF o SKIP LOCKED ) DELETE FROM observers o USING expired e WHERE o.id = e.id RETURNING o.id ` // Opt-in age-out: preserve retained history and manually recorded ownership. // Bound deletions per cleanup tick and skip observers being updated by ingest. func (q *Queries) DeleteOldObservers(ctx context.Context, lastSeen pgtype.Timestamptz) ([]uuid.UUID, error) { rows, err := q.db.Query(ctx, deleteOldObservers, lastSeen) if err != nil { return nil, err } defer rows.Close() items := []uuid.UUID{} for rows.Next() { var id uuid.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const deleteOldPackets = `-- name: DeleteOldPackets :one WITH victims AS MATERIALIZED ( SELECT vp.packet_hash, vp.last_heard_at FROM packets vp WHERE vp.last_heard_at < $1::timestamptz ORDER BY vp.last_heard_at, vp.packet_hash LIMIT $2::integer FOR UPDATE OF vp SKIP LOCKED ), deleted AS ( DELETE FROM packets dp USING victims v WHERE dp.packet_hash = v.packet_hash RETURNING dp.last_heard_at ), mark AS ( UPDATE analytics_raw_state SET raw_deleted_before = GREATEST(raw_deleted_before, (SELECT max(last_heard_at) FROM deleted)) WHERE EXISTS (SELECT 1 FROM deleted) ) SELECT count(*) FROM deleted ` type DeleteOldPacketsParams struct { Cutoff pgtype.Timestamptz `json:"cutoff"` BatchSize int32 `json:"batch_size"` } // Deletes one bounded cohort (observations cascade). SKIP LOCKED leaves packets an in-flight // observation insert holds. raw_deleted_before tells the rollup which hours lost raw rows. func (q *Queries) DeleteOldPackets(ctx context.Context, arg DeleteOldPacketsParams) (int64, error) { row := q.db.QueryRow(ctx, deleteOldPackets, arg.Cutoff, arg.BatchSize) var count int64 err := row.Scan(&count) return count, err } const deleteOldRoutes = `-- name: DeleteOldRoutes :execrows WITH expired AS ( SELECT r.iata, r.path_key FROM known_routes r WHERE r.last_seen < GREATEST($1::timestamptz, $2::timestamptz) AND (r.last_seen < $1 OR (r.observation_count < $3 AND r.last_seen < $2)) LIMIT $4 FOR UPDATE OF r SKIP LOCKED ) DELETE FROM known_routes kr USING expired e WHERE kr.iata = e.iata AND kr.path_key = e.path_key ` type DeleteOldRoutesParams struct { RetentionCutoff pgtype.Timestamptz `json:"retention_cutoff"` GraceCutoff pgtype.Timestamptz `json:"grace_cutoff"` MinObservations int64 `json:"min_observations"` BatchSize int32 `json:"batch_size"` } // One batch of routes past retention, or past grace with too few observations. // GREATEST keeps the scan on idx_known_routes_last_seen. func (q *Queries) DeleteOldRoutes(ctx context.Context, arg DeleteOldRoutesParams) (int64, error) { result, err := q.db.Exec(ctx, deleteOldRoutes, arg.RetentionCutoff, arg.GraceCutoff, arg.MinObservations, arg.BatchSize, ) if err != nil { return 0, err } return result.RowsAffected(), nil } const deleteOldTelemetry = `-- name: DeleteOldTelemetry :exec DELETE FROM observer_telemetry WHERE reported_at < $1 ` // Deletes telemetry rows older than the given cutoff. Called by the cleanup goroutine. func (q *Queries) DeleteOldTelemetry(ctx context.Context, reportedAt pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, deleteOldTelemetry, reportedAt) return err } const deleteOldTraceIATAs = `-- name: DeleteOldTraceIATAs :exec DELETE FROM trace_iatas WHERE last_heard < $1 ` // Keeps the trace IATA filter in step with packet retention. func (q *Queries) DeleteOldTraceIATAs(ctx context.Context, lastHeard pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, deleteOldTraceIATAs, lastHeard) return err } const deleteOldTraceTags = `-- name: DeleteOldTraceTags :exec DELETE FROM trace_tags WHERE last_heard_at < $1 ` func (q *Queries) DeleteOldTraceTags(ctx context.Context, lastHeardAt pgtype.Timestamptz) error { _, err := q.db.Exec(ctx, deleteOldTraceTags, lastHeardAt) return err } const deleteRegionIATAsNotIn = `-- name: DeleteRegionIATAsNotIn :exec DELETE FROM region_iatas WHERE region_id = $1 AND NOT (iata = ANY($2::bpchar[])) ` type DeleteRegionIATAsNotInParams struct { RegionID int32 `json:"region_id"` Keep []string `json:"keep"` } func (q *Queries) DeleteRegionIATAsNotIn(ctx context.Context, arg DeleteRegionIATAsNotInParams) error { _, err := q.db.Exec(ctx, deleteRegionIATAsNotIn, arg.RegionID, arg.Keep) return err } const getAccount = `-- name: GetAccount :one SELECT id, name, created_at, deactivated_at FROM accounts WHERE id = $1 ` func (q *Queries) GetAccount(ctx context.Context, id uuid.UUID) (Account, error) { row := q.db.QueryRow(ctx, getAccount, id) var i Account err := row.Scan( &i.ID, &i.Name, &i.CreatedAt, &i.DeactivatedAt, ) return i, err } const getChannelByID = `-- name: GetChannelByID :one SELECT id, channel_hash, key_fingerprint, name, hashtag, is_hashtag, is_public, key_known, first_seen, last_seen, message_count FROM channels WHERE id = $1 ` func (q *Queries) GetChannelByID(ctx context.Context, id int32) (Channel, error) { row := q.db.QueryRow(ctx, getChannelByID, id) var i Channel err := row.Scan( &i.ID, &i.ChannelHash, &i.KeyFingerprint, &i.Name, &i.Hashtag, &i.IsHashtag, &i.IsPublic, &i.KeyKnown, &i.FirstSeen, &i.LastSeen, &i.MessageCount, ) return i, err } const getHourlyStats = `-- name: GetHourlyStats :many SELECT iata, hour, observation_count FROM analytics_hourly_iata_observations WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR iata = ANY($1::bpchar[])) AND hour >= $2::timestamptz ORDER BY iata, hour ` type GetHourlyStatsParams struct { Iatas []string `json:"iatas"` Since pgtype.Timestamptz `json:"since"` } type GetHourlyStatsRow struct { Iata string `json:"iata"` Hour pgtype.Timestamptz `json:"hour"` ObservationCount int64 `json:"observation_count"` } // ============================================================ // STATS // ============================================================ func (q *Queries) GetHourlyStats(ctx context.Context, arg GetHourlyStatsParams) ([]GetHourlyStatsRow, error) { rows, err := q.db.Query(ctx, getHourlyStats, arg.Iatas, arg.Since) if err != nil { return nil, err } defer rows.Close() items := []GetHourlyStatsRow{} for rows.Next() { var i GetHourlyStatsRow if err := rows.Scan(&i.Iata, &i.Hour, &i.ObservationCount); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getIATA = `-- name: GetIATA :one SELECT iata, display_name, approx_lat, approx_lng, added_at, border FROM iata_codes WHERE iata = $1 ` func (q *Queries) GetIATA(ctx context.Context, iata string) (IataCode, error) { row := q.db.QueryRow(ctx, getIATA, iata) var i IataCode err := row.Scan( &i.Iata, &i.DisplayName, &i.ApproxLat, &i.ApproxLng, &i.AddedAt, &i.Border, ) return i, err } const getIATABorder = `-- name: GetIATABorder :one SELECT COALESCE(z.feature, i.border)::jsonb AS border FROM iata_codes i LEFT JOIN meshmapper_zone_boundaries z ON z.iata = i.iata WHERE i.iata = $1 ` // An imported MeshMapper boundary overrides the configured one. border is NULL // when neither exists; a missing row (unknown IATA) is sql.ErrNoRows, same // not-found distinction GetIATA already makes. func (q *Queries) GetIATABorder(ctx context.Context, iata string) (json.RawMessage, error) { row := q.db.QueryRow(ctx, getIATABorder, iata) var border json.RawMessage err := row.Scan(&border) return border, err } const getKnownRoutesByNodes = `-- name: GetKnownRoutesByNodes :many SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count FROM known_routes kr WHERE kr.node_ids && $1::uuid[] AND kr.iata = ANY($2::bpchar[]) AND ($1::uuid[])[array_position($2::bpchar[], kr.iata)] = ANY(kr.node_ids) ORDER BY hop_count ASC, last_seen DESC LIMIT $3 ` type GetKnownRoutesByNodesParams struct { Nodes []uuid.UUID `json:"nodes"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type GetKnownRoutesByNodesRow struct { ID int64 `json:"id"` NodeIds []uuid.UUID `json:"node_ids"` HashPrefix [][]byte `json:"hash_prefix"` Iata string `json:"iata"` HopCount int32 `json:"hop_count"` FirstSeen pgtype.Timestamptz `json:"first_seen"` LastSeen pgtype.Timestamptz `json:"last_seen"` ObservationCount int64 `json:"observation_count"` } // Routes containing @nodes[i] in IATA @iatas[i] (aligned, one node per IATA). The && // drives the node_ids GIN index; the indexed check keeps LIMIT counting real matches. func (q *Queries) GetKnownRoutesByNodes(ctx context.Context, arg GetKnownRoutesByNodesParams) ([]GetKnownRoutesByNodesRow, error) { rows, err := q.db.Query(ctx, getKnownRoutesByNodes, arg.Nodes, arg.Iatas, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []GetKnownRoutesByNodesRow{} for rows.Next() { var i GetKnownRoutesByNodesRow if err := rows.Scan( &i.ID, &i.NodeIds, &i.HashPrefix, &i.Iata, &i.HopCount, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getNeighborLinks = `-- name: GetNeighborLinks :many SELECT node_id, neighbor_id, last_seen FROM node_neighbors WHERE (node_id = ANY($1::uuid[]) AND neighbor_id = ANY($2::uuid[])) OR (node_id = ANY($2::uuid[]) AND neighbor_id = ANY($1::uuid[])) ` type GetNeighborLinksParams struct { ANodes []uuid.UUID `json:"a_nodes"` BNodes []uuid.UUID `json:"b_nodes"` } type GetNeighborLinksRow struct { NodeID uuid.UUID `json:"node_id"` NeighborID uuid.UUID `json:"neighbor_id"` LastSeen pgtype.Timestamptz `json:"last_seen"` } // Neighbor rows between @a_nodes and @b_nodes in either orientation: ingest writes // adverts as (sender, forwarder) but traces as (receiver, sender). Callers normalise. func (q *Queries) GetNeighborLinks(ctx context.Context, arg GetNeighborLinksParams) ([]GetNeighborLinksRow, error) { rows, err := q.db.Query(ctx, getNeighborLinks, arg.ANodes, arg.BNodes) if err != nil { return nil, err } defer rows.Close() items := []GetNeighborLinksRow{} for rows.Next() { var i GetNeighborLinksRow if err := rows.Scan(&i.NodeID, &i.NeighborID, &i.LastSeen); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getNodeByID = `-- name: GetNodeByID :one SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.location_source, n.last_advert_at, n.supports_multibyte_paths, n.supports_multibyte_traces, n.default_scope_id, n.min_firmware_version, n.first_seen, n.last_seen, n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, n.metadata, n.device_clock_drift_seconds, ts.name AS default_scope_name, EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer, (SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id, (SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC) FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas, (SELECT COUNT(DISTINCT nn.neighbor_id) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count FROM nodes n LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id WHERE n.id = $1 ` type GetNodeByIDRow struct { ID uuid.UUID `json:"id"` PublicKey []byte `json:"public_key"` NodeType int16 `json:"node_type"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` LocationSource *string `json:"location_source"` LastAdvertAt pgtype.Timestamptz `json:"last_advert_at"` SupportsMultibytePaths bool `json:"supports_multibyte_paths"` SupportsMultibyteTraces bool `json:"supports_multibyte_traces"` DefaultScopeID *int32 `json:"default_scope_id"` MinFirmwareVersion *string `json:"min_firmware_version"` FirstSeen pgtype.Timestamptz `json:"first_seen"` LastSeen pgtype.Timestamptz `json:"last_seen"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioSf *int16 `json:"radio_sf"` RadioBwKhz *float32 `json:"radio_bw_khz"` Metadata []byte `json:"metadata"` DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"` DefaultScopeName *string `json:"default_scope_name"` IsObserver bool `json:"is_observer"` ObserverID uuid.UUID `json:"observer_id"` Iatas []byte `json:"iatas"` KnownNeighborCount int64 `json:"known_neighbor_count"` } func (q *Queries) GetNodeByID(ctx context.Context, id uuid.UUID) (GetNodeByIDRow, error) { row := q.db.QueryRow(ctx, getNodeByID, id) var i GetNodeByIDRow err := row.Scan( &i.ID, &i.PublicKey, &i.NodeType, &i.Name, &i.Latitude, &i.Longitude, &i.LocationSource, &i.LastAdvertAt, &i.SupportsMultibytePaths, &i.SupportsMultibyteTraces, &i.DefaultScopeID, &i.MinFirmwareVersion, &i.FirstSeen, &i.LastSeen, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.Metadata, &i.DeviceClockDriftSeconds, &i.DefaultScopeName, &i.IsObserver, &i.ObserverID, &i.Iatas, &i.KnownNeighborCount, ) return i, err } const getNodeByPubkey = `-- name: GetNodeByPubkey :one SELECT id FROM nodes WHERE public_key = $1 ` func (q *Queries) GetNodeByPubkey(ctx context.Context, publicKey []byte) (uuid.UUID, error) { row := q.db.QueryRow(ctx, getNodeByPubkey, publicKey) var id uuid.UUID err := row.Scan(&id) return id, err } const getNodeNeighbors = `-- name: GetNodeNeighbors :many SELECT n.id, n.public_key, n.name, n.node_type, n.latitude, n.longitude, nn.iata, nn.observation_count, nn.first_seen, nn.last_seen, nn.snr FROM node_neighbors nn JOIN nodes n ON n.id = nn.neighbor_id WHERE nn.node_id = $1 ORDER BY nn.last_seen DESC ` type GetNodeNeighborsRow struct { ID uuid.UUID `json:"id"` PublicKey []byte `json:"public_key"` Name *string `json:"name"` NodeType int16 `json:"node_type"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` Iata string `json:"iata"` ObservationCount int64 `json:"observation_count"` FirstSeen pgtype.Timestamptz `json:"first_seen"` LastSeen pgtype.Timestamptz `json:"last_seen"` Snr *float32 `json:"snr"` } // Returns the neighbors of a node with details, ordered by most recently seen. func (q *Queries) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]GetNodeNeighborsRow, error) { rows, err := q.db.Query(ctx, getNodeNeighbors, nodeID) if err != nil { return nil, err } defer rows.Close() items := []GetNodeNeighborsRow{} for rows.Next() { var i GetNodeNeighborsRow if err := rows.Scan( &i.ID, &i.PublicKey, &i.Name, &i.NodeType, &i.Latitude, &i.Longitude, &i.Iata, &i.ObservationCount, &i.FirstSeen, &i.LastSeen, &i.Snr, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getNodesByIDs = `-- name: GetNodesByIDs :many SELECT id, public_key, name, latitude, longitude FROM nodes WHERE id = ANY($1::uuid[]) ` type GetNodesByIDsRow struct { ID uuid.UUID `json:"id"` PublicKey []byte `json:"public_key"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` } func (q *Queries) GetNodesByIDs(ctx context.Context, dollar_1 []uuid.UUID) ([]GetNodesByIDsRow, error) { rows, err := q.db.Query(ctx, getNodesByIDs, dollar_1) if err != nil { return nil, err } defer rows.Close() items := []GetNodesByIDsRow{} for rows.Next() { var i GetNodesByIDsRow if err := rows.Scan( &i.ID, &i.PublicKey, &i.Name, &i.Latitude, &i.Longitude, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getNodesByPubkeys = `-- name: GetNodesByPubkeys :many SELECT id, public_key, name, latitude, longitude FROM nodes WHERE public_key = ANY($1::bytea[]) ` type GetNodesByPubkeysRow struct { ID uuid.UUID `json:"id"` PublicKey []byte `json:"public_key"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` } func (q *Queries) GetNodesByPubkeys(ctx context.Context, pubkeys [][]byte) ([]GetNodesByPubkeysRow, error) { rows, err := q.db.Query(ctx, getNodesByPubkeys, pubkeys) if err != nil { return nil, err } defer rows.Close() items := []GetNodesByPubkeysRow{} for rows.Next() { var i GetNodesByPubkeysRow if err := rows.Scan( &i.ID, &i.PublicKey, &i.Name, &i.Latitude, &i.Longitude, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverActivityHourly = `-- name: GetObserverActivityHourly :many WITH src AS ( SELECT a.hour AS t, a.observations, a.airtime_ms, a.airtime_n, a.snr_sum, a.snr_n, a.snr_min, a.rssi_sum, a.rssi_n FROM analytics_hourly_observer_activity a WHERE a.observer_id = $1 AND a.hour >= $2::timestamptz AND a.hour < LEAST($4::timestamptz, $5::timestamptz) UNION ALL SELECT o.heard_at, 1::bigint, o.airtime_ms, (o.airtime_ms IS NOT NULL)::int::bigint, s.snr, (s.snr IS NOT NULL)::int::bigint, s.snr, s.rssi::bigint, (s.rssi IS NOT NULL)::int::bigint FROM packet_observations o CROSS JOIN LATERAL (SELECT CASE WHEN NOT (COALESCE(o.rssi, 0) = 0 AND COALESCE(o.snr, 0) = 0) THEN o.snr END AS snr, CASE WHEN NOT (COALESCE(o.rssi, 0) = 0 AND COALESCE(o.snr, 0) = 0) THEN o.rssi END AS rssi) s WHERE o.observer_id = $1 AND o.heard_at >= GREATEST($2::timestamptz, $5::timestamptz) AND o.heard_at < $4::timestamptz ) SELECT date_bin($3::interval, t, TIMESTAMPTZ 'epoch')::timestamptz AS bucket, SUM(observations)::bigint AS observations, COALESCE(SUM(airtime_ms), 0)::real AS airtime_ms, SUM(airtime_n)::bigint AS airtime_n, COALESCE(SUM(snr_sum), 0)::real AS snr_sum, SUM(snr_n)::bigint AS snr_n, COALESCE(MIN(snr_min), 0)::real AS snr_min, COALESCE(SUM(rssi_sum), 0)::bigint AS rssi_sum, SUM(rssi_n)::bigint AS rssi_n FROM src GROUP BY 1 ORDER BY 1 ` type GetObserverActivityHourlyParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 pgtype.Timestamptz `json:"column_2"` Column3 pgtype.Interval `json:"column_3"` Until pgtype.Timestamptz `json:"until"` TailStart pgtype.Timestamptz `json:"tail_start"` } type GetObserverActivityHourlyRow struct { Bucket pgtype.Timestamptz `json:"bucket"` Observations int64 `json:"observations"` AirtimeMs float32 `json:"airtime_ms"` AirtimeN int64 `json:"airtime_n"` SnrSum float32 `json:"snr_sum"` SnrN int64 `json:"snr_n"` SnrMin float32 `json:"snr_min"` RssiSum int64 `json:"rssi_sum"` RssiN int64 `json:"rssi_n"` } // Hour-or-coarser buckets summed from the hourly rollup; same COALESCE-plus-count shape as the raw query. // Hours from @tail_start on aren't rolled yet and read raw rows. func (q *Queries) GetObserverActivityHourly(ctx context.Context, arg GetObserverActivityHourlyParams) ([]GetObserverActivityHourlyRow, error) { rows, err := q.db.Query(ctx, getObserverActivityHourly, arg.ObserverID, arg.Column2, arg.Column3, arg.Until, arg.TailStart, ) if err != nil { return nil, err } defer rows.Close() items := []GetObserverActivityHourlyRow{} for rows.Next() { var i GetObserverActivityHourlyRow if err := rows.Scan( &i.Bucket, &i.Observations, &i.AirtimeMs, &i.AirtimeN, &i.SnrSum, &i.SnrN, &i.SnrMin, &i.RssiSum, &i.RssiN, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverActivityHourlyPayloadTypes = `-- name: GetObserverActivityHourlyPayloadTypes :many WITH src AS ( SELECT a.payload_type, a.observations AS n FROM analytics_hourly_observer_activity a WHERE a.observer_id = $1 AND a.hour >= $2::timestamptz AND a.hour < LEAST($3::timestamptz, $4::timestamptz) UNION ALL SELECT COALESCE(o.payload_type, -1)::smallint, 1::bigint FROM packet_observations o WHERE o.observer_id = $1 AND o.heard_at >= GREATEST($2::timestamptz, $4::timestamptz) AND o.heard_at < $3::timestamptz ) SELECT payload_type, SUM(n)::bigint AS count FROM src GROUP BY payload_type ORDER BY count DESC ` type GetObserverActivityHourlyPayloadTypesParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 pgtype.Timestamptz `json:"column_2"` Until pgtype.Timestamptz `json:"until"` TailStart pgtype.Timestamptz `json:"tail_start"` } type GetObserverActivityHourlyPayloadTypesRow struct { PayloadType int16 `json:"payload_type"` Count int64 `json:"count"` } // Same rollup/raw-tail split as GetObserverActivityHourly. func (q *Queries) GetObserverActivityHourlyPayloadTypes(ctx context.Context, arg GetObserverActivityHourlyPayloadTypesParams) ([]GetObserverActivityHourlyPayloadTypesRow, error) { rows, err := q.db.Query(ctx, getObserverActivityHourlyPayloadTypes, arg.ObserverID, arg.Column2, arg.Until, arg.TailStart, ) if err != nil { return nil, err } defer rows.Close() items := []GetObserverActivityHourlyPayloadTypesRow{} for rows.Next() { var i GetObserverActivityHourlyPayloadTypesRow if err := rows.Scan(&i.PayloadType, &i.Count); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverActivityLiveSummary = `-- name: GetObserverActivityLiveSummary :one WITH latest AS ( SELECT heard_at FROM packet_observations WHERE observer_id = $1::uuid AND heard_at <= $2::timestamptz ORDER BY heard_at DESC LIMIT 1 ), hourly AS ( SELECT COUNT(*)::bigint AS n FROM packet_observations WHERE observer_id = $1::uuid AND heard_at >= $3::timestamptz AND heard_at < $4::timestamptz ) SELECT (SELECT heard_at FROM latest)::timestamptz AS latest_recorded_at, hourly.n AS last_complete_hour FROM hourly ` type GetObserverActivityLiveSummaryParams struct { ObserverID uuid.UUID `json:"observer_id"` GeneratedAt pgtype.Timestamptz `json:"generated_at"` HourStart pgtype.Timestamptz `json:"hour_start"` HourEnd pgtype.Timestamptz `json:"hour_end"` } type GetObserverActivityLiveSummaryRow struct { LatestRecordedAt pgtype.Timestamptz `json:"latest_recorded_at"` LastCompleteHour int64 `json:"last_complete_hour"` } // Two indexed ranges, bounded to one observer; no legacy presence counters. func (q *Queries) GetObserverActivityLiveSummary(ctx context.Context, arg GetObserverActivityLiveSummaryParams) (GetObserverActivityLiveSummaryRow, error) { row := q.db.QueryRow(ctx, getObserverActivityLiveSummary, arg.ObserverID, arg.GeneratedAt, arg.HourStart, arg.HourEnd, ) var i GetObserverActivityLiveSummaryRow err := row.Scan(&i.LatestRecordedAt, &i.LastCompleteHour) return i, err } const getObserverActivityRaw = `-- name: GetObserverActivityRaw :many SELECT date_bin($3::interval, heard_at, TIMESTAMPTZ 'epoch')::timestamptz AS bucket, COUNT(*)::bigint AS observations, COALESCE(SUM(airtime_ms), 0)::real AS airtime_ms, COUNT(airtime_ms)::bigint AS airtime_n, COALESCE(AVG(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS snr_avg, COALESCE(MIN(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS snr_min, COUNT(snr) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0))::bigint AS snr_n, COALESCE(AVG(rssi) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0)), 0)::real AS rssi_avg, COUNT(rssi) FILTER (WHERE NOT (COALESCE(rssi, 0) = 0 AND COALESCE(snr, 0) = 0))::bigint AS rssi_n FROM packet_observations WHERE observer_id = $1 AND heard_at >= $2::timestamptz AND heard_at < $4::timestamptz GROUP BY bucket ORDER BY bucket ` type GetObserverActivityRawParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 pgtype.Timestamptz `json:"column_2"` Column3 pgtype.Interval `json:"column_3"` Until pgtype.Timestamptz `json:"until"` } type GetObserverActivityRawRow struct { Bucket pgtype.Timestamptz `json:"bucket"` Observations int64 `json:"observations"` AirtimeMs float32 `json:"airtime_ms"` AirtimeN int64 `json:"airtime_n"` SnrAvg float32 `json:"snr_avg"` SnrMin float32 `json:"snr_min"` SnrN int64 `json:"snr_n"` RssiAvg float32 `json:"rssi_avg"` RssiN int64 `json:"rssi_n"` } // Sub-hour activity buckets straight off idx_observations_observer; no join to packets. // Aggregates are COALESCEd and paired with a count column: sqlc types a cast expression as // NOT NULL, so the counts are what tell the store a bucket had no costed or no signal rows. func (q *Queries) GetObserverActivityRaw(ctx context.Context, arg GetObserverActivityRawParams) ([]GetObserverActivityRawRow, error) { rows, err := q.db.Query(ctx, getObserverActivityRaw, arg.ObserverID, arg.Column2, arg.Column3, arg.Until, ) if err != nil { return nil, err } defer rows.Close() items := []GetObserverActivityRawRow{} for rows.Next() { var i GetObserverActivityRawRow if err := rows.Scan( &i.Bucket, &i.Observations, &i.AirtimeMs, &i.AirtimeN, &i.SnrAvg, &i.SnrMin, &i.SnrN, &i.RssiAvg, &i.RssiN, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverActivityRawPayloadTypes = `-- name: GetObserverActivityRawPayloadTypes :many SELECT payload_type, COUNT(*)::bigint AS count FROM packet_observations WHERE observer_id = $1 AND heard_at >= $2::timestamptz AND heard_at < $3::timestamptz GROUP BY payload_type ORDER BY count DESC ` type GetObserverActivityRawPayloadTypesParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 pgtype.Timestamptz `json:"column_2"` Until pgtype.Timestamptz `json:"until"` } type GetObserverActivityRawPayloadTypesRow struct { PayloadType *int16 `json:"payload_type"` Count int64 `json:"count"` } func (q *Queries) GetObserverActivityRawPayloadTypes(ctx context.Context, arg GetObserverActivityRawPayloadTypesParams) ([]GetObserverActivityRawPayloadTypesRow, error) { rows, err := q.db.Query(ctx, getObserverActivityRawPayloadTypes, arg.ObserverID, arg.Column2, arg.Until) if err != nil { return nil, err } defer rows.Close() items := []GetObserverActivityRawPayloadTypesRow{} for rows.Next() { var i GetObserverActivityRawPayloadTypesRow if err := rows.Scan(&i.PayloadType, &i.Count); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverBrokers = `-- name: GetObserverBrokers :many SELECT broker_name, last_seen, last_packet_at FROM observer_brokers WHERE observer_id = $1 ORDER BY last_seen DESC ` type GetObserverBrokersRow struct { BrokerName string `json:"broker_name"` LastSeen pgtype.Timestamptz `json:"last_seen"` LastPacketAt pgtype.Timestamptz `json:"last_packet_at"` } func (q *Queries) GetObserverBrokers(ctx context.Context, observerID uuid.UUID) ([]GetObserverBrokersRow, error) { rows, err := q.db.Query(ctx, getObserverBrokers, observerID) if err != nil { return nil, err } defer rows.Close() items := []GetObserverBrokersRow{} for rows.Next() { var i GetObserverBrokersRow if err := rows.Scan(&i.BrokerName, &i.LastSeen, &i.LastPacketAt); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverByID = `-- name: GetObserverByID :one SELECT id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at FROM observers WHERE id = $1 ` func (q *Queries) GetObserverByID(ctx context.Context, id uuid.UUID) (Observer, error) { row := q.db.QueryRow(ctx, getObserverByID, id) var i Observer err := row.Scan( &i.ID, &i.PublicKey, &i.DisplayName, &i.ObserverType, &i.SoftwareVersion, &i.HardwareModel, &i.FirmwareVersion, &i.FirmwareBuild, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.RadioCr, &i.BatteryLevel, &i.UptimeSeconds, &i.StatusMetadata, &i.LastStatusAt, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, &i.Metadata, &i.RegionScope, &i.LastIata, &i.LastIataAt, ) return i, err } const getObserverByPubkey = `-- name: GetObserverByPubkey :one SELECT id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at FROM observers WHERE public_key = $1 ` func (q *Queries) GetObserverByPubkey(ctx context.Context, publicKey []byte) (Observer, error) { row := q.db.QueryRow(ctx, getObserverByPubkey, publicKey) var i Observer err := row.Scan( &i.ID, &i.PublicKey, &i.DisplayName, &i.ObserverType, &i.SoftwareVersion, &i.HardwareModel, &i.FirmwareVersion, &i.FirmwareBuild, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.RadioCr, &i.BatteryLevel, &i.UptimeSeconds, &i.StatusMetadata, &i.LastStatusAt, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, &i.Metadata, &i.RegionScope, &i.LastIata, &i.LastIataAt, ) return i, err } const getObserverLastIATA = `-- name: GetObserverLastIATA :one SELECT COALESCE(last_iata, '')::text FROM observers WHERE id = $1 ` func (q *Queries) GetObserverLastIATA(ctx context.Context, id uuid.UUID) (string, error) { row := q.db.QueryRow(ctx, getObserverLastIATA, id) var column_1 string err := row.Scan(&column_1) return column_1, err } const getObserverRadio = `-- name: GetObserverRadio :one SELECT radio_freq_mhz, radio_bw_khz, radio_sf, radio_cr FROM observers WHERE id = $1 ` type GetObserverRadioRow struct { RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioBwKhz *float32 `json:"radio_bw_khz"` RadioSf *int16 `json:"radio_sf"` RadioCr *int16 `json:"radio_cr"` } func (q *Queries) GetObserverRadio(ctx context.Context, id uuid.UUID) (GetObserverRadioRow, error) { row := q.db.QueryRow(ctx, getObserverRadio, id) var i GetObserverRadioRow err := row.Scan( &i.RadioFreqMhz, &i.RadioBwKhz, &i.RadioSf, &i.RadioCr, ) return i, err } const getObserverScopes = `-- name: GetObserverScopes :many SELECT ts.name FROM observer_scopes os JOIN transport_scopes ts ON ts.id = os.scope_id WHERE os.observer_id = $1 ORDER BY ts.name ` func (q *Queries) GetObserverScopes(ctx context.Context, observerID uuid.UUID) ([]string, error) { rows, err := q.db.Query(ctx, getObserverScopes, observerID) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, err } items = append(items, name) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getObserverTelemetry = `-- name: GetObserverTelemetry :many SELECT id, reported_at, battery_voltage_mv, airtime_tx_secs, airtime_rx_secs, noise_floor_db, uptime_seconds, queue_length, debug_flags, receive_errors FROM observer_telemetry WHERE observer_id = $1 AND ($2::timestamptz IS NULL OR reported_at >= $2) AND ($3::timestamptz IS NULL OR reported_at <= $3) AND ($4 = 0 OR id > $4) ORDER BY reported_at ASC ` type GetObserverTelemetryParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 pgtype.Timestamptz `json:"column_2"` Column3 pgtype.Timestamptz `json:"column_3"` Column4 interface{} `json:"column_4"` } type GetObserverTelemetryRow struct { ID int64 `json:"id"` ReportedAt pgtype.Timestamptz `json:"reported_at"` BatteryVoltageMv *int32 `json:"battery_voltage_mv"` AirtimeTxSecs *float32 `json:"airtime_tx_secs"` AirtimeRxSecs *float32 `json:"airtime_rx_secs"` NoiseFloorDb *float32 `json:"noise_floor_db"` UptimeSeconds *int64 `json:"uptime_seconds"` QueueLength *int32 `json:"queue_length"` DebugFlags *int32 `json:"debug_flags"` ReceiveErrors *int32 `json:"receive_errors"` } func (q *Queries) GetObserverTelemetry(ctx context.Context, arg GetObserverTelemetryParams) ([]GetObserverTelemetryRow, error) { rows, err := q.db.Query(ctx, getObserverTelemetry, arg.ObserverID, arg.Column2, arg.Column3, arg.Column4, ) if err != nil { return nil, err } defer rows.Close() items := []GetObserverTelemetryRow{} for rows.Next() { var i GetObserverTelemetryRow if err := rows.Scan( &i.ID, &i.ReportedAt, &i.BatteryVoltageMv, &i.AirtimeTxSecs, &i.AirtimeRxSecs, &i.NoiseFloorDb, &i.UptimeSeconds, &i.QueueLength, &i.DebugFlags, &i.ReceiveErrors, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getPacketByHash = `-- name: GetPacketByHash :one SELECT p.packet_hash, p.payload_type, p.payload_version, p.route_type, p.transport_codes_present, p.region_code, p.sub_region_code, p.scope_id, p.origin_pubkey, p.raw_payload, p.raw_header, p.parsed_payload, p.decrypted, p.channel_hash, p.trace_tag, p.first_heard_at, p.last_heard_at, p.observation_count, ts.name AS scope_name, cm.sender_name AS cm_sender_name, cm.content AS cm_content, cm.sent_at AS cm_sent_at FROM packets p LEFT JOIN transport_scopes ts ON ts.id = p.scope_id LEFT JOIN channel_messages cm ON cm.packet_hash = p.packet_hash WHERE p.packet_hash = $1 ` type GetPacketByHashRow struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` PayloadVersion int16 `json:"payload_version"` RouteType int16 `json:"route_type"` TransportCodesPresent *bool `json:"transport_codes_present"` RegionCode *int32 `json:"region_code"` SubRegionCode *int32 `json:"sub_region_code"` ScopeID *int32 `json:"scope_id"` OriginPubkey []byte `json:"origin_pubkey"` RawPayload []byte `json:"raw_payload"` RawHeader []byte `json:"raw_header"` ParsedPayload []byte `json:"parsed_payload"` Decrypted *bool `json:"decrypted"` ChannelHash []byte `json:"channel_hash"` TraceTag []byte `json:"trace_tag"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` ObservationCount int64 `json:"observation_count"` ScopeName *string `json:"scope_name"` CmSenderName *string `json:"cm_sender_name"` CmContent *string `json:"cm_content"` CmSentAt pgtype.Timestamptz `json:"cm_sent_at"` } func (q *Queries) GetPacketByHash(ctx context.Context, packetHash []byte) (GetPacketByHashRow, error) { row := q.db.QueryRow(ctx, getPacketByHash, packetHash) var i GetPacketByHashRow err := row.Scan( &i.PacketHash, &i.PayloadType, &i.PayloadVersion, &i.RouteType, &i.TransportCodesPresent, &i.RegionCode, &i.SubRegionCode, &i.ScopeID, &i.OriginPubkey, &i.RawPayload, &i.RawHeader, &i.ParsedPayload, &i.Decrypted, &i.ChannelHash, &i.TraceTag, &i.FirstHeardAt, &i.LastHeardAt, &i.ObservationCount, &i.ScopeName, &i.CmSenderName, &i.CmContent, &i.CmSentAt, ) return i, err } const getPacketsByTraceTag = `-- name: GetPacketsByTraceTag :many SELECT encode(p.packet_hash, 'hex') AS packet_hash_hex, p.route_type, p.first_heard_at, p.last_heard_at, p.parsed_payload, p.scope_id, ts.name AS scope_name, ARRAY( SELECT po.iata FROM packet_observations po WHERE po.packet_hash = p.packet_hash GROUP BY po.iata ORDER BY MIN(po.heard_at), po.iata )::bpchar[] AS iatas FROM packets p LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE p.trace_tag = decode($1, 'hex') ORDER BY p.first_heard_at ASC ` type GetPacketsByTraceTagRow struct { PacketHashHex string `json:"packet_hash_hex"` RouteType int16 `json:"route_type"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` ParsedPayload []byte `json:"parsed_payload"` ScopeID *int32 `json:"scope_id"` ScopeName *string `json:"scope_name"` Iatas []string `json:"iatas"` } // Return distinct observation IATAs in first-heard order for path resolution, // without fetching full observations separately for every trace packet. func (q *Queries) GetPacketsByTraceTag(ctx context.Context, decode string) ([]GetPacketsByTraceTagRow, error) { rows, err := q.db.Query(ctx, getPacketsByTraceTag, decode) if err != nil { return nil, err } defer rows.Close() items := []GetPacketsByTraceTagRow{} for rows.Next() { var i GetPacketsByTraceTagRow if err := rows.Scan( &i.PacketHashHex, &i.RouteType, &i.FirstHeardAt, &i.LastHeardAt, &i.ParsedPayload, &i.ScopeID, &i.ScopeName, &i.Iatas, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getRadioPresets = `-- name: GetRadioPresets :many SELECT preset, iata, source_type, count FROM mv_radio_presets WHERE ($1::text = '' OR preset = $1::text) AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[])) ORDER BY preset, iata, source_type ` type GetRadioPresetsParams struct { Column1 string `json:"column_1"` Column2 []string `json:"column_2"` } func (q *Queries) GetRadioPresets(ctx context.Context, arg GetRadioPresetsParams) ([]MvRadioPreset, error) { rows, err := q.db.Query(ctx, getRadioPresets, arg.Column1, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []MvRadioPreset{} for rows.Next() { var i MvRadioPreset if err := rows.Scan( &i.Preset, &i.Iata, &i.SourceType, &i.Count, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getRegion = `-- name: GetRegion :one SELECT id, slug, name, description, center_lat, center_lng, zoom_level FROM regions WHERE id = $1 ` type GetRegionRow struct { ID int32 `json:"id"` Slug string `json:"slug"` Name string `json:"name"` Description *string `json:"description"` CenterLat *float64 `json:"center_lat"` CenterLng *float64 `json:"center_lng"` ZoomLevel *int32 `json:"zoom_level"` } func (q *Queries) GetRegion(ctx context.Context, id int32) (GetRegionRow, error) { row := q.db.QueryRow(ctx, getRegion, id) var i GetRegionRow err := row.Scan( &i.ID, &i.Slug, &i.Name, &i.Description, &i.CenterLat, &i.CenterLng, &i.ZoomLevel, ) return i, err } const getRegionBySlug = `-- name: GetRegionBySlug :one SELECT id, slug, name, description, center_lat, center_lng, zoom_level FROM regions WHERE slug = $1 ` type GetRegionBySlugRow struct { ID int32 `json:"id"` Slug string `json:"slug"` Name string `json:"name"` Description *string `json:"description"` CenterLat *float64 `json:"center_lat"` CenterLng *float64 `json:"center_lng"` ZoomLevel *int32 `json:"zoom_level"` } func (q *Queries) GetRegionBySlug(ctx context.Context, slug string) (GetRegionBySlugRow, error) { row := q.db.QueryRow(ctx, getRegionBySlug, slug) var i GetRegionBySlugRow err := row.Scan( &i.ID, &i.Slug, &i.Name, &i.Description, &i.CenterLat, &i.CenterLng, &i.ZoomLevel, ) return i, err } const getRegionIATAs = `-- name: GetRegionIATAs :many SELECT iata FROM region_iatas WHERE region_id = $1 ORDER BY iata ` func (q *Queries) GetRegionIATAs(ctx context.Context, regionID int32) ([]string, error) { rows, err := q.db.Query(ctx, getRegionIATAs, regionID) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var iata string if err := rows.Scan(&iata); err != nil { return nil, err } items = append(items, iata) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getScopeByName = `-- name: GetScopeByName :one SELECT ts.name, COALESCE((SELECT SUM(s.packets) FROM analytics_hourly_scope_sets s WHERE s.scope_id = ts.id), 0)::bigint AS packet_count, (SELECT COUNT(*) FROM observer_scopes os WHERE os.scope_id = ts.id)::bigint AS observer_count, (SELECT COUNT(*) FROM nodes n WHERE n.default_scope_id = ts.id)::bigint AS node_count, cardinality(i.iatas)::bigint AS iata_count, i.iatas FROM transport_scopes ts CROSS JOIN LATERAL ( SELECT COALESCE(array_agg(DISTINCT x.iata::text ORDER BY x.iata::text), '{}')::text[] AS iatas FROM analytics_hourly_scope_sets s CROSS JOIN LATERAL unnest(s.iatas) AS x(iata) WHERE s.scope_id = ts.id ) i WHERE ts.name = $1 ` type GetScopeByNameRow struct { Name string `json:"name"` PacketCount int64 `json:"packet_count"` ObserverCount int64 `json:"observer_count"` NodeCount int64 `json:"node_count"` IataCount int64 `json:"iata_count"` Iatas []string `json:"iatas"` } // Packets and IATAs cover every retained rollup hour; IATAs are those that heard the // scope's own packets. Observer and node counts are current memberships. func (q *Queries) GetScopeByName(ctx context.Context, name string) (GetScopeByNameRow, error) { row := q.db.QueryRow(ctx, getScopeByName, name) var i GetScopeByNameRow err := row.Scan( &i.Name, &i.PacketCount, &i.ObserverCount, &i.NodeCount, &i.IataCount, &i.Iatas, ) return i, err } const getScopeCatalogue = `-- name: GetScopeCatalogue :one SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_scope_catalogues WHERE iata = $1 AND url = $2 ` type GetScopeCatalogueParams struct { Iata string `json:"iata"` Url string `json:"url"` } func (q *Queries) GetScopeCatalogue(ctx context.Context, arg GetScopeCatalogueParams) (MeshmapperScopeCatalogue, error) { row := q.db.QueryRow(ctx, getScopeCatalogue, arg.Iata, arg.Url) var i MeshmapperScopeCatalogue err := row.Scan( &i.Iata, &i.Url, &i.Payload, &i.Etag, &i.CheckedAt, &i.AttemptedAt, &i.NextAttempt, &i.LastError, ) return i, err } const getScopeNames = `-- name: GetScopeNames :many SELECT name FROM transport_scopes ORDER BY name ` func (q *Queries) GetScopeNames(ctx context.Context) ([]string, error) { rows, err := q.db.Query(ctx, getScopeNames) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, err } items = append(items, name) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getScopeStats = `-- name: GetScopeStats :many WITH packet_counts AS ( SELECT s.scope_id, SUM(s.packets)::bigint AS packet_count FROM analytics_hourly_scope_sets s WHERE s.hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR s.iatas && $2::bpchar[]) GROUP BY s.scope_id ), observer_counts AS ( SELECT os.scope_id, COUNT(*)::bigint AS observer_count FROM observer_scopes os JOIN observers o ON o.id = os.observer_id WHERE COALESCE(cardinality($2::bpchar[]), 0) = 0 OR o.last_iata = ANY($2::bpchar[]) GROUP BY os.scope_id ), node_counts AS ( SELECT n.default_scope_id AS scope_id, COUNT(*)::bigint AS node_count FROM nodes n WHERE n.default_scope_id IS NOT NULL AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[]))) GROUP BY n.default_scope_id ) SELECT ts.name, COALESCE(pc.packet_count, 0)::bigint AS packet_count, COALESCE(oc.observer_count, 0)::bigint AS observer_count, COALESCE(nc.node_count, 0)::bigint AS node_count FROM transport_scopes ts LEFT JOIN packet_counts pc ON pc.scope_id = ts.id LEFT JOIN observer_counts oc ON oc.scope_id = ts.id LEFT JOIN node_counts nc ON nc.scope_id = ts.id ORDER BY ts.name ` type GetScopeStatsParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` } type GetScopeStatsRow struct { Name string `json:"name"` PacketCount int64 `json:"packet_count"` ObserverCount int64 `json:"observer_count"` NodeCount int64 `json:"node_count"` } // Packets since the given hour come from the IATA-set rollup (counted once per hour heard). // Observer and node counts are current memberships; observers filter by their latest IATA. func (q *Queries) GetScopeStats(ctx context.Context, arg GetScopeStatsParams) ([]GetScopeStatsRow, error) { rows, err := q.db.Query(ctx, getScopeStats, arg.Since, arg.Iatas) if err != nil { return nil, err } defer rows.Close() items := []GetScopeStatsRow{} for rows.Next() { var i GetScopeStatsRow if err := rows.Scan( &i.Name, &i.PacketCount, &i.ObserverCount, &i.NodeCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getScopeStatsHourly = `-- name: GetScopeStatsHourly :many WITH pk AS ( SELECT s.scope_id, s.hour, SUM(s.packets)::bigint AS n FROM analytics_hourly_scope_sets s WHERE s.hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR s.iatas && $2::bpchar[]) GROUP BY s.scope_id, s.hour ), ob AS ( SELECT o.scope_id, o.hour, COUNT(DISTINCT o.observer_id)::bigint AS n FROM analytics_hourly_scope_observers o WHERE o.hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR o.iata = ANY($2::bpchar[])) GROUP BY o.scope_id, o.hour ), nd AS ( SELECT d.scope_id, d.hour, COUNT(DISTINCT d.origin_pubkey)::bigint AS n FROM analytics_hourly_scope_nodes d WHERE d.hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR d.iata = ANY($2::bpchar[])) GROUP BY d.scope_id, d.hour ), k AS ( SELECT scope_id, hour FROM pk UNION SELECT scope_id, hour FROM ob UNION SELECT scope_id, hour FROM nd ) SELECT ts.name, k.hour, COALESCE(pk.n, 0)::bigint AS packets, COALESCE(ob.n, 0)::bigint AS observers, COALESCE(nd.n, 0)::bigint AS nodes FROM k JOIN transport_scopes ts ON ts.id = k.scope_id LEFT JOIN pk ON pk.scope_id = k.scope_id AND pk.hour = k.hour LEFT JOIN ob ON ob.scope_id = k.scope_id AND ob.hour = k.hour LEFT JOIN nd ON nd.scope_id = k.scope_id AND nd.hour = k.hour WHERE COALESCE(pk.n, 0) > 0 OR COALESCE(ob.n, 0) > 0 OR COALESCE(nd.n, 0) > 0 ORDER BY ts.name, k.hour ` type GetScopeStatsHourlyParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` } type GetScopeStatsHourlyRow struct { Name string `json:"name"` Hour pgtype.Timestamptz `json:"hour"` Packets int64 `json:"packets"` Observers int64 `json:"observers"` Nodes int64 `json:"nodes"` } // GetScopeStats packet counts split by hour (same window and IATA-set filter), with the // distinct observers and advertising nodes active in each scope that hour. Empty hours omitted. func (q *Queries) GetScopeStatsHourly(ctx context.Context, arg GetScopeStatsHourlyParams) ([]GetScopeStatsHourlyRow, error) { rows, err := q.db.Query(ctx, getScopeStatsHourly, arg.Since, arg.Iatas) if err != nil { return nil, err } defer rows.Close() items := []GetScopeStatsHourlyRow{} for rows.Next() { var i GetScopeStatsHourlyRow if err := rows.Scan( &i.Name, &i.Hour, &i.Packets, &i.Observers, &i.Nodes, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsClockDrift = `-- name: GetStatsClockDrift :many WITH page AS ( SELECT n.id, n.name, n.node_type, n.device_clock_drift_seconds, n.last_advert_at FROM nodes n WHERE n.node_type IN (2, 3) AND n.device_clock_drift_seconds IS NOT NULL AND ABS(n.device_clock_drift_seconds) > $1::int AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[]))) ORDER BY ABS(n.device_clock_drift_seconds) DESC LIMIT $3 ) SELECT n.id, n.name, n.node_type, n.device_clock_drift_seconds, n.last_advert_at, (SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC) FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas FROM page n ORDER BY ABS(n.device_clock_drift_seconds) DESC ` type GetStatsClockDriftParams struct { Column1 int32 `json:"column_1"` Column2 []string `json:"column_2"` Limit int32 `json:"limit"` } type GetStatsClockDriftRow struct { ID uuid.UUID `json:"id"` Name *string `json:"name"` NodeType int16 `json:"node_type"` DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"` LastAdvertAt pgtype.Timestamptz `json:"last_advert_at"` Iatas []byte `json:"iatas"` } // Repeaters/room servers (node_type 2/3) whose current advert-derived clock drift exceeds // the given threshold in magnitude, worst first. Not time-windowed -- reflects each node's // latest measured drift, not an aggregate over a period. // Select the filtered page before aggregating its IATA memberships. func (q *Queries) GetStatsClockDrift(ctx context.Context, arg GetStatsClockDriftParams) ([]GetStatsClockDriftRow, error) { rows, err := q.db.Query(ctx, getStatsClockDrift, arg.Column1, arg.Column2, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []GetStatsClockDriftRow{} for rows.Next() { var i GetStatsClockDriftRow if err := rows.Scan( &i.ID, &i.Name, &i.NodeType, &i.DeviceClockDriftSeconds, &i.LastAdvertAt, &i.Iatas, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsNodeTypes = `-- name: GetStatsNodeTypes :many SELECT n.node_type, COUNT(DISTINCT n.id)::bigint AS count FROM nodes n LEFT JOIN node_iatas ni ON ni.node_id = n.id WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR ni.iata = ANY($1::bpchar[])) GROUP BY n.node_type ORDER BY count DESC ` type GetStatsNodeTypesRow struct { NodeType int16 `json:"node_type"` Count int64 `json:"count"` } // Returns node counts grouped by type, optionally filtered by IATA. func (q *Queries) GetStatsNodeTypes(ctx context.Context, dollar_1 []string) ([]GetStatsNodeTypesRow, error) { rows, err := q.db.Query(ctx, getStatsNodeTypes, dollar_1) if err != nil { return nil, err } defer rows.Close() items := []GetStatsNodeTypesRow{} for rows.Next() { var i GetStatsNodeTypesRow if err := rows.Scan(&i.NodeType, &i.Count); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsPayloadBreakdown = `-- name: GetStatsPayloadBreakdown :many SELECT payload_type, SUM(count)::bigint AS count FROM analytics_hourly_payload_breakdown WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR iata = ANY($1::bpchar[])) AND hour >= $2::timestamptz GROUP BY payload_type ORDER BY count DESC ` type GetStatsPayloadBreakdownParams struct { Iatas []string `json:"iatas"` Since pgtype.Timestamptz `json:"since"` } type GetStatsPayloadBreakdownRow struct { PayloadType int16 `json:"payload_type"` Count int64 `json:"count"` } // Payload-type observation counts since the given hour, from the hourly rollup. func (q *Queries) GetStatsPayloadBreakdown(ctx context.Context, arg GetStatsPayloadBreakdownParams) ([]GetStatsPayloadBreakdownRow, error) { rows, err := q.db.Query(ctx, getStatsPayloadBreakdown, arg.Iatas, arg.Since) if err != nil { return nil, err } defer rows.Close() items := []GetStatsPayloadBreakdownRow{} for rows.Next() { var i GetStatsPayloadBreakdownRow if err := rows.Scan(&i.PayloadType, &i.Count); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsTopAdvertisers = `-- name: GetStatsTopAdvertisers :many WITH ranked AS ( SELECT origin_pubkey, SUM(advert_packets)::bigint AS advert_count, SUM(flood_packets)::bigint AS flood_advert_count, SUM(direct_packets)::bigint AS direct_advert_count FROM analytics_hourly_advert_sets WHERE hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iatas && $2::bpchar[]) GROUP BY origin_pubkey ORDER BY advert_count DESC, origin_pubkey LIMIT $3 ), heard AS ( SELECT h.origin_pubkey, MAX(h.last_heard) AS last_heard, MAX(h.iata) AS iata, MAX(h.name) AS name, MAX(h.node_type) AS node_type FROM analytics_hourly_advert_hearings h WHERE h.hour >= $1::timestamptz AND h.origin_pubkey IN (SELECT origin_pubkey FROM ranked) AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR h.iata = ANY($2::bpchar[])) GROUP BY h.origin_pubkey ) SELECT n.id AS node_id, encode(r.origin_pubkey, 'hex') AS public_key, COALESCE(n.name, h.name, '')::text AS name, COALESCE(n.node_type, h.node_type, 0)::smallint AS node_type, r.advert_count, r.flood_advert_count, r.direct_advert_count, h.last_heard::timestamptz AS last_heard, COALESCE(h.iata, '')::bpchar AS iata FROM ranked r LEFT JOIN heard h ON h.origin_pubkey = r.origin_pubkey LEFT JOIN nodes n ON n.public_key = r.origin_pubkey ORDER BY r.advert_count DESC, r.origin_pubkey ` type GetStatsTopAdvertisersParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type GetStatsTopAdvertisersRow struct { NodeID pgtype.UUID `json:"node_id"` PublicKey string `json:"public_key"` Name string `json:"name"` NodeType int16 `json:"node_type"` AdvertCount int64 `json:"advert_count"` FloodAdvertCount int64 `json:"flood_advert_count"` DirectAdvertCount int64 `json:"direct_advert_count"` LastHeard pgtype.Timestamptz `json:"last_heard"` Iata string `json:"iata"` } // Top N advertisers since the given hour. Each ADVERT packet counts once per hour heard, // however many of the requested IATAs heard it (IATA-set rollup). Live names win. func (q *Queries) GetStatsTopAdvertisers(ctx context.Context, arg GetStatsTopAdvertisersParams) ([]GetStatsTopAdvertisersRow, error) { rows, err := q.db.Query(ctx, getStatsTopAdvertisers, arg.Since, arg.Iatas, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []GetStatsTopAdvertisersRow{} for rows.Next() { var i GetStatsTopAdvertisersRow if err := rows.Scan( &i.NodeID, &i.PublicKey, &i.Name, &i.NodeType, &i.AdvertCount, &i.FloodAdvertCount, &i.DirectAdvertCount, &i.LastHeard, &i.Iata, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsTopObservers = `-- name: GetStatsTopObservers :many WITH ranked AS ( SELECT observer_id, SUM(observation_count)::bigint AS observation_count, MAX(iata) AS iata, MAX(display_name) AS display_name, MAX(observer_type) AS observer_type FROM analytics_hourly_observer_identity WHERE hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[])) GROUP BY observer_id ORDER BY observation_count DESC, observer_id LIMIT $3 ) SELECT r.observer_id AS id, COALESCE(o.display_name, r.display_name, '')::text AS display_name, COALESCE(o.observer_type, r.observer_type, '')::text AS observer_type, r.observation_count, r.iata::bpchar AS iata FROM ranked r LEFT JOIN observers o ON o.id = r.observer_id ORDER BY r.observation_count DESC, r.observer_id ` type GetStatsTopObserversParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type GetStatsTopObserversRow struct { ID uuid.UUID `json:"id"` DisplayName string `json:"display_name"` ObserverType string `json:"observer_type"` ObservationCount int64 `json:"observation_count"` Iata string `json:"iata"` } // Top N observers since the given hour. Counts sum across matched IATAs; iata is a // representative one. Live names win over the rolled snapshot. func (q *Queries) GetStatsTopObservers(ctx context.Context, arg GetStatsTopObserversParams) ([]GetStatsTopObserversRow, error) { rows, err := q.db.Query(ctx, getStatsTopObservers, arg.Since, arg.Iatas, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []GetStatsTopObserversRow{} for rows.Next() { var i GetStatsTopObserversRow if err := rows.Scan( &i.ID, &i.DisplayName, &i.ObserverType, &i.ObservationCount, &i.Iata, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getStatsTopTalkers = `-- name: GetStatsTopTalkers :many SELECT sender_name, SUM(messages)::bigint AS message_count, MAX(last_sent)::timestamptz AS last_sent FROM analytics_hourly_talker_sets WHERE hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iatas && $2::bpchar[]) GROUP BY sender_name ORDER BY message_count DESC, sender_name LIMIT $3 ` type GetStatsTopTalkersParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type GetStatsTopTalkersRow struct { SenderName string `json:"sender_name"` MessageCount int64 `json:"message_count"` LastSent pgtype.Timestamptz `json:"last_sent"` } // Top N senders since the given hour. Each message counts once per hour heard, however many // of the requested IATAs heard it (IATA-set rollup). func (q *Queries) GetStatsTopTalkers(ctx context.Context, arg GetStatsTopTalkersParams) ([]GetStatsTopTalkersRow, error) { rows, err := q.db.Query(ctx, getStatsTopTalkers, arg.Since, arg.Iatas, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []GetStatsTopTalkersRow{} for rows.Next() { var i GetStatsTopTalkersRow if err := rows.Scan(&i.SenderName, &i.MessageCount, &i.LastSent); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getTopNodes = `-- name: GetTopNodes :many WITH ranked AS ( SELECT origin_pubkey, SUM(observations)::bigint AS observation_count, MAX(last_heard) AS last_heard, MAX(iata) AS iata, MAX(name) AS name, MAX(node_type) AS node_type FROM analytics_hourly_advert_hearings WHERE hour >= $1::timestamptz AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR iata = ANY($2::bpchar[])) GROUP BY origin_pubkey ORDER BY observation_count DESC, origin_pubkey LIMIT $3 ) SELECT n.id AS node_id, encode(r.origin_pubkey, 'hex') AS public_key, COALESCE(n.name, r.name, '')::text AS name, COALESCE(n.node_type, r.node_type, 0)::smallint AS node_type, r.iata::bpchar AS iata, r.observation_count, r.last_heard::timestamptz AS last_heard FROM ranked r LEFT JOIN nodes n ON n.public_key = r.origin_pubkey ORDER BY r.observation_count DESC, r.origin_pubkey ` type GetTopNodesParams struct { Since pgtype.Timestamptz `json:"since"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type GetTopNodesRow struct { NodeID pgtype.UUID `json:"node_id"` PublicKey string `json:"public_key"` Name string `json:"name"` NodeType int16 `json:"node_type"` Iata string `json:"iata"` ObservationCount int64 `json:"observation_count"` LastHeard pgtype.Timestamptz `json:"last_heard"` } // Nodes by ADVERT hearings since the given hour. Live names win over the rolled snapshot; // node_id is NULL once the node row is gone. iata is a representative one. func (q *Queries) GetTopNodes(ctx context.Context, arg GetTopNodesParams) ([]GetTopNodesRow, error) { rows, err := q.db.Query(ctx, getTopNodes, arg.Since, arg.Iatas, arg.RowLimit) if err != nil { return nil, err } defer rows.Close() items := []GetTopNodesRow{} for rows.Next() { var i GetTopNodesRow if err := rows.Scan( &i.NodeID, &i.PublicKey, &i.Name, &i.NodeType, &i.Iata, &i.ObservationCount, &i.LastHeard, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getTransportScopeByName = `-- name: GetTransportScopeByName :one SELECT id FROM transport_scopes WHERE name = $1 ` func (q *Queries) GetTransportScopeByName(ctx context.Context, name string) (int32, error) { row := q.db.QueryRow(ctx, getTransportScopeByName, name) var id int32 err := row.Scan(&id) return id, err } const getTransportScopes = `-- name: GetTransportScopes :many SELECT name, transport_key, key_fingerprint FROM transport_scopes WHERE NOT imported_only ORDER BY name ` type GetTransportScopesRow struct { Name string `json:"name"` TransportKey []byte `json:"transport_key"` KeyFingerprint []byte `json:"key_fingerprint"` } func (q *Queries) GetTransportScopes(ctx context.Context) ([]GetTransportScopesRow, error) { rows, err := q.db.Query(ctx, getTransportScopes) if err != nil { return nil, err } defer rows.Close() items := []GetTransportScopesRow{} for rows.Next() { var i GetTransportScopesRow if err := rows.Scan(&i.Name, &i.TransportKey, &i.KeyFingerprint); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const insertChannelMessage = `-- name: InsertChannelMessage :one WITH inserted AS ( INSERT INTO channel_messages (channel_id, packet_hash, sender_name, content, sent_at) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (packet_hash) DO NOTHING RETURNING id, packet_hash, channel_id ), bump AS ( UPDATE channels c SET message_count = c.message_count + 1 FROM inserted WHERE c.id = inserted.channel_id ), dirty AS ( INSERT INTO analytics_dirty_hours (hour) SELECT DISTINCT date_trunc('hour', po.heard_at, 'UTC') FROM packet_observations po JOIN inserted i ON i.packet_hash = po.packet_hash WHERE $6::boolean ON CONFLICT (hour) DO UPDATE SET enqueued_at = now() ) SELECT inserted.id, ts.name AS scope_name, p.transport_codes_present FROM inserted JOIN packets p ON p.packet_hash = inserted.packet_hash LEFT JOIN transport_scopes ts ON ts.id = p.scope_id ` type InsertChannelMessageParams struct { ChannelID int32 `json:"channel_id"` PacketHash []byte `json:"packet_hash"` SenderName *string `json:"sender_name"` Content *string `json:"content"` SentAt pgtype.Timestamptz `json:"sent_at"` Column6 bool `json:"column_6"` } type InsertChannelMessageRow struct { ID int64 `json:"id"` ScopeName *string `json:"scope_name"` TransportCodesPresent *bool `json:"transport_codes_present"` } // ============================================================ // CHANNEL MESSAGES // ============================================================ // Read the immutable first-packet scope in the same statement as insertion. // A later reception's transport code must not give live and historical messages different tags. // message_count is a lifetime count, bumped only for a new message. A historical (backfilled) // message queues its observation hours for a re-roll in the same statement, so the two can't diverge. func (q *Queries) InsertChannelMessage(ctx context.Context, arg InsertChannelMessageParams) (InsertChannelMessageRow, error) { row := q.db.QueryRow(ctx, insertChannelMessage, arg.ChannelID, arg.PacketHash, arg.SenderName, arg.Content, arg.SentAt, arg.Column6, ) var i InsertChannelMessageRow err := row.Scan(&i.ID, &i.ScopeName, &i.TransportCodesPresent) return i, err } const insertObservation = `-- name: InsertObservation :one WITH ins AS ( INSERT INTO packet_observations ( packet_hash, observer_id, iata, heard_at, path_length_byte, hash_size, hop_count, path_bytes, rssi, snr, propagation_time_ms, radio_freq_mhz, spread_factor, bandwidth_khz, coding_rate, source_broker, payload_type, airtime_ms ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18 ) ON CONFLICT (packet_hash, observer_id) DO NOTHING RETURNING packet_hash ), bump AS ( UPDATE packets p SET observation_count = p.observation_count + 1 FROM ins WHERE p.packet_hash = ins.packet_hash RETURNING p.observation_count ) SELECT EXISTS (SELECT 1 FROM ins) AS inserted, COALESCE((SELECT b.observation_count FROM bump b), (SELECT pk.observation_count FROM packets pk WHERE pk.packet_hash = $1), 0)::bigint AS observation_count ` type InsertObservationParams struct { PacketHash []byte `json:"packet_hash"` ObserverID uuid.UUID `json:"observer_id"` Iata string `json:"iata"` HeardAt pgtype.Timestamptz `json:"heard_at"` PathLengthByte int16 `json:"path_length_byte"` HashSize int16 `json:"hash_size"` HopCount int16 `json:"hop_count"` PathBytes []byte `json:"path_bytes"` Rssi *int16 `json:"rssi"` Snr *float32 `json:"snr"` PropagationTimeMs *int32 `json:"propagation_time_ms"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` SpreadFactor *int16 `json:"spread_factor"` BandwidthKhz *float32 `json:"bandwidth_khz"` CodingRate *int16 `json:"coding_rate"` SourceBroker *string `json:"source_broker"` PayloadType *int16 `json:"payload_type"` AirtimeMs *float32 `json:"airtime_ms"` } type InsertObservationRow struct { Inserted bool `json:"inserted"` ObservationCount int64 `json:"observation_count"` } // ============================================================ // PACKET OBSERVATIONS // ============================================================ // Bumps packets.observation_count only for a new row; a duplicate returns the current count. func (q *Queries) InsertObservation(ctx context.Context, arg InsertObservationParams) (InsertObservationRow, error) { row := q.db.QueryRow(ctx, insertObservation, arg.PacketHash, arg.ObserverID, arg.Iata, arg.HeardAt, arg.PathLengthByte, arg.HashSize, arg.HopCount, arg.PathBytes, arg.Rssi, arg.Snr, arg.PropagationTimeMs, arg.RadioFreqMhz, arg.SpreadFactor, arg.BandwidthKhz, arg.CodingRate, arg.SourceBroker, arg.PayloadType, arg.AirtimeMs, ) var i InsertObservationRow err := row.Scan(&i.Inserted, &i.ObservationCount) return i, err } const insertObserverTelemetry = `-- name: InsertObserverTelemetry :exec INSERT INTO observer_telemetry ( observer_id, reported_at, battery_voltage_mv, airtime_tx_secs, airtime_rx_secs, noise_floor_db, uptime_seconds, queue_length, debug_flags, receive_errors ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (observer_id, reported_at) DO NOTHING ` type InsertObserverTelemetryParams struct { ObserverID uuid.UUID `json:"observer_id"` ReportedAt pgtype.Timestamptz `json:"reported_at"` BatteryVoltageMv *int32 `json:"battery_voltage_mv"` AirtimeTxSecs *float32 `json:"airtime_tx_secs"` AirtimeRxSecs *float32 `json:"airtime_rx_secs"` NoiseFloorDb *float32 `json:"noise_floor_db"` UptimeSeconds *int64 `json:"uptime_seconds"` QueueLength *int32 `json:"queue_length"` DebugFlags *int32 `json:"debug_flags"` ReceiveErrors *int32 `json:"receive_errors"` } // Inserts a telemetry snapshot for an observer. The reported_at timestamp should // be truncated to the configured resolution before calling to ensure deduplication. func (q *Queries) InsertObserverTelemetry(ctx context.Context, arg InsertObserverTelemetryParams) error { _, err := q.db.Exec(ctx, insertObserverTelemetry, arg.ObserverID, arg.ReportedAt, arg.BatteryVoltageMv, arg.AirtimeTxSecs, arg.AirtimeRxSecs, arg.NoiseFloorDb, arg.UptimeSeconds, arg.QueueLength, arg.DebugFlags, arg.ReceiveErrors, ) return err } const listAccounts = `-- name: ListAccounts :many SELECT id, name, created_at, deactivated_at FROM accounts ORDER BY created_at DESC, id DESC ` func (q *Queries) ListAccounts(ctx context.Context) ([]Account, error) { rows, err := q.db.Query(ctx, listAccounts) if err != nil { return nil, err } defer rows.Close() items := []Account{} for rows.Next() { var i Account if err := rows.Scan( &i.ID, &i.Name, &i.CreatedAt, &i.DeactivatedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listAllChannelMessages = `-- name: ListAllChannelMessages :many SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present, p.observation_count AS observation_count FROM channel_messages cm JOIN channels c ON c.id = cm.channel_id JOIN packet_observations po ON po.packet_hash = cm.packet_hash JOIN packets p ON p.packet_hash = cm.packet_hash LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE ($1::timestamptz IS NULL OR cm.sent_at >= $1) AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR po.iata = ANY($2::bpchar[])) AND ($3::text = '' OR ts.name = $3::text) AND ($4 = 0 OR cm.id < $4) ORDER BY cm.id DESC LIMIT $5 ` type ListAllChannelMessagesParams struct { Column1 pgtype.Timestamptz `json:"column_1"` Column2 []string `json:"column_2"` Column3 string `json:"column_3"` Column4 interface{} `json:"column_4"` Limit int32 `json:"limit"` } type ListAllChannelMessagesRow struct { ID int64 `json:"id"` ChannelID int32 `json:"channel_id"` PacketHash []byte `json:"packet_hash"` SenderName *string `json:"sender_name"` SenderPubkey []byte `json:"sender_pubkey"` Content *string `json:"content"` SentAt pgtype.Timestamptz `json:"sent_at"` PacketHashHex string `json:"packet_hash_hex"` ChannelHash []byte `json:"channel_hash"` ScopeName *string `json:"scope_name"` TransportCodesPresent *bool `json:"transport_codes_present"` ObservationCount int64 `json:"observation_count"` } // Returns all messages across all channels with optional time, IATA, scope and cursor filters. // Pass empty string for iata or scope to skip those filters. // Pass cursor=0 to start from the beginning. func (q *Queries) ListAllChannelMessages(ctx context.Context, arg ListAllChannelMessagesParams) ([]ListAllChannelMessagesRow, error) { rows, err := q.db.Query(ctx, listAllChannelMessages, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Limit, ) if err != nil { return nil, err } defer rows.Close() items := []ListAllChannelMessagesRow{} for rows.Next() { var i ListAllChannelMessagesRow if err := rows.Scan( &i.ID, &i.ChannelID, &i.PacketHash, &i.SenderName, &i.SenderPubkey, &i.Content, &i.SentAt, &i.PacketHashHex, &i.ChannelHash, &i.ScopeName, &i.TransportCodesPresent, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChannelCatalogues = `-- name: ListChannelCatalogues :many SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_channel_catalogues ORDER BY iata, attempted_at ` func (q *Queries) ListChannelCatalogues(ctx context.Context) ([]MeshmapperChannelCatalogue, error) { rows, err := q.db.Query(ctx, listChannelCatalogues) if err != nil { return nil, err } defer rows.Close() items := []MeshmapperChannelCatalogue{} for rows.Next() { var i MeshmapperChannelCatalogue if err := rows.Scan( &i.Iata, &i.Url, &i.Payload, &i.Etag, &i.CheckedAt, &i.AttemptedAt, &i.NextAttempt, &i.LastError, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChannelMessages = `-- name: ListChannelMessages :many SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present, p.observation_count AS observation_count FROM channel_messages cm JOIN channels c ON c.id = cm.channel_id JOIN packet_observations po ON po.packet_hash = cm.packet_hash JOIN packets p ON p.packet_hash = cm.packet_hash LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE cm.channel_id = $1 AND ($2::timestamptz IS NULL OR cm.sent_at >= $2) AND (COALESCE(cardinality($3::bpchar[]), 0) = 0 OR po.iata = ANY($3::bpchar[])) AND ($4::text = '' OR ts.name = $4::text) AND ($5::bigint = 0 OR cm.id < $5::bigint) ORDER BY cm.id DESC LIMIT $6 ` type ListChannelMessagesParams struct { ChannelID int32 `json:"channel_id"` Column2 pgtype.Timestamptz `json:"column_2"` Column3 []string `json:"column_3"` Column4 string `json:"column_4"` Column5 int64 `json:"column_5"` Limit int32 `json:"limit"` } type ListChannelMessagesRow struct { ID int64 `json:"id"` ChannelID int32 `json:"channel_id"` PacketHash []byte `json:"packet_hash"` SenderName *string `json:"sender_name"` SenderPubkey []byte `json:"sender_pubkey"` Content *string `json:"content"` SentAt pgtype.Timestamptz `json:"sent_at"` PacketHashHex string `json:"packet_hash_hex"` ChannelHash []byte `json:"channel_hash"` ScopeName *string `json:"scope_name"` TransportCodesPresent *bool `json:"transport_codes_present"` ObservationCount int64 `json:"observation_count"` } // Returns messages for a channel identified by integer ID. // Pass a zero/null timestamp for since to return all messages up to limit. // Pass empty string for iata to skip IATA filtering. // Pass cursor=0 to start from the beginning. func (q *Queries) ListChannelMessages(ctx context.Context, arg ListChannelMessagesParams) ([]ListChannelMessagesRow, error) { rows, err := q.db.Query(ctx, listChannelMessages, arg.ChannelID, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Limit, ) if err != nil { return nil, err } defer rows.Close() items := []ListChannelMessagesRow{} for rows.Next() { var i ListChannelMessagesRow if err := rows.Scan( &i.ID, &i.ChannelID, &i.PacketHash, &i.SenderName, &i.SenderPubkey, &i.Content, &i.SentAt, &i.PacketHashHex, &i.ChannelHash, &i.ScopeName, &i.TransportCodesPresent, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChannelMessagesByHash = `-- name: ListChannelMessagesByHash :many SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, c.channel_hash, ts.name AS scope_name, p.transport_codes_present, p.observation_count AS observation_count FROM channel_messages cm JOIN channels c ON c.id = cm.channel_id JOIN packet_observations po ON po.packet_hash = cm.packet_hash JOIN packets p ON p.packet_hash = cm.packet_hash LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE c.channel_hash = $1 AND ($2::timestamptz IS NULL OR cm.sent_at >= $2) AND (COALESCE(cardinality($3::bpchar[]), 0) = 0 OR po.iata = ANY($3::bpchar[])) AND ($4::text = '' OR ts.name = $4::text) AND ($5::bigint = 0 OR cm.id < $5::bigint) ORDER BY cm.id DESC LIMIT $6 ` type ListChannelMessagesByHashParams struct { ChannelHash []byte `json:"channel_hash"` Column2 pgtype.Timestamptz `json:"column_2"` Column3 []string `json:"column_3"` Column4 string `json:"column_4"` Column5 int64 `json:"column_5"` Limit int32 `json:"limit"` } type ListChannelMessagesByHashRow struct { ID int64 `json:"id"` ChannelID int32 `json:"channel_id"` PacketHash []byte `json:"packet_hash"` SenderName *string `json:"sender_name"` SenderPubkey []byte `json:"sender_pubkey"` Content *string `json:"content"` SentAt pgtype.Timestamptz `json:"sent_at"` ChannelHash []byte `json:"channel_hash"` ScopeName *string `json:"scope_name"` TransportCodesPresent *bool `json:"transport_codes_present"` ObservationCount int64 `json:"observation_count"` } // Returns messages for all channels matching a hash byte. // May return messages from multiple channels if the hash collides across different keys. // Pass empty string for iata or scope to skip those filters. // Pass cursor=0 to start from the beginning. func (q *Queries) ListChannelMessagesByHash(ctx context.Context, arg ListChannelMessagesByHashParams) ([]ListChannelMessagesByHashRow, error) { rows, err := q.db.Query(ctx, listChannelMessagesByHash, arg.ChannelHash, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Limit, ) if err != nil { return nil, err } defer rows.Close() items := []ListChannelMessagesByHashRow{} for rows.Next() { var i ListChannelMessagesByHashRow if err := rows.Scan( &i.ID, &i.ChannelID, &i.PacketHash, &i.SenderName, &i.SenderPubkey, &i.Content, &i.SentAt, &i.ChannelHash, &i.ScopeName, &i.TransportCodesPresent, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChannels = `-- name: ListChannels :many SELECT c.id, c.channel_hash, c.key_fingerprint, c.name, c.hashtag, c.is_hashtag, c.is_public, c.key_known, c.first_seen, c.last_seen, c.message_count FROM channels c WHERE ($1::bytea IS NULL OR c.channel_hash = $1) AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR EXISTS (SELECT 1 FROM meshmapper_channel_members m WHERE m.key_fingerprint = c.key_fingerprint AND m.iata::bpchar = ANY($2::bpchar[])) OR EXISTS (SELECT 1 FROM channel_config_scopes s WHERE s.key_fingerprint = c.key_fingerprint AND (s.region_slug IS NULL OR s.region_slug IN ( SELECT r.slug FROM regions r JOIN region_iatas ri ON ri.region_id = r.id WHERE ri.iata = ANY($2::bpchar[]))))) AND ($3::boolean IS NULL OR COALESCE(c.key_known, false) = $3) AND ($4::timestamptz IS NULL OR c.last_seen < $4) ORDER BY c.last_seen DESC, c.id DESC LIMIT $5 ` type ListChannelsParams struct { ChannelHash []byte `json:"channel_hash"` Iatas []string `json:"iatas"` KeyKnown *bool `json:"key_known"` CursorTs pgtype.Timestamptz `json:"cursor_ts"` PageLimit int32 `json:"page_limit"` } // Channels ordered by last seen, optionally filtered by hash and/or IATAs. // A channel belongs to an IATA when MeshMapper lists it there or config scopes it // to a region containing it (or Beacon-wide). NULL hash / empty array / NULL key_known skip those filters. // Pass cursor=0 to start from the beginning (cursor is last_seen epoch ms). func (q *Queries) ListChannels(ctx context.Context, arg ListChannelsParams) ([]Channel, error) { rows, err := q.db.Query(ctx, listChannels, arg.ChannelHash, arg.Iatas, arg.KeyKnown, arg.CursorTs, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() items := []Channel{} for rows.Next() { var i Channel if err := rows.Scan( &i.ID, &i.ChannelHash, &i.KeyFingerprint, &i.Name, &i.Hashtag, &i.IsHashtag, &i.IsPublic, &i.KeyKnown, &i.FirstSeen, &i.LastSeen, &i.MessageCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listChannelsAfter = `-- name: ListChannelsAfter :many SELECT c.id, c.channel_hash, c.key_fingerprint, c.name, c.hashtag, c.is_hashtag, c.is_public, c.key_known, c.first_seen, c.last_seen, c.message_count FROM channels c WHERE (c.last_seen, c.id) < ($1::timestamptz, $2::integer) AND ($3::bytea IS NULL OR c.channel_hash = $3) AND (COALESCE(cardinality($4::bpchar[]), 0) = 0 OR EXISTS (SELECT 1 FROM meshmapper_channel_members m WHERE m.key_fingerprint = c.key_fingerprint AND m.iata::bpchar = ANY($4::bpchar[])) OR EXISTS (SELECT 1 FROM channel_config_scopes s WHERE s.key_fingerprint = c.key_fingerprint AND (s.region_slug IS NULL OR s.region_slug IN ( SELECT r.slug FROM regions r JOIN region_iatas ri ON ri.region_id = r.id WHERE ri.iata = ANY($4::bpchar[]))))) AND ($5::boolean IS NULL OR COALESCE(c.key_known, false) = $5) ORDER BY c.last_seen DESC, c.id DESC LIMIT $6 ` type ListChannelsAfterParams struct { CursorTs pgtype.Timestamptz `json:"cursor_ts"` CursorID int32 `json:"cursor_id"` ChannelHash []byte `json:"channel_hash"` Iatas []string `json:"iatas"` KeyKnown *bool `json:"key_known"` PageLimit int32 `json:"page_limit"` } // Keep the non-null tuple boundary separate from the legacy optional cursor so // generic prepared plans can seek directly into the composite ordered index. func (q *Queries) ListChannelsAfter(ctx context.Context, arg ListChannelsAfterParams) ([]Channel, error) { rows, err := q.db.Query(ctx, listChannelsAfter, arg.CursorTs, arg.CursorID, arg.ChannelHash, arg.Iatas, arg.KeyKnown, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() items := []Channel{} for rows.Next() { var i Channel if err := rows.Scan( &i.ID, &i.ChannelHash, &i.KeyFingerprint, &i.Name, &i.Hashtag, &i.IsHashtag, &i.IsPublic, &i.KeyKnown, &i.FirstSeen, &i.LastSeen, &i.MessageCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listHeardIATAs = `-- name: ListHeardIATAs :many SELECT DISTINCT last_iata::text AS iata FROM observers WHERE last_iata IS NOT NULL ORDER BY 1 ` func (q *Queries) ListHeardIATAs(ctx context.Context) ([]string, error) { rows, err := q.db.Query(ctx, listHeardIATAs) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var iata string if err := rows.Scan(&iata); err != nil { return nil, err } items = append(items, iata) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listIATAs = `-- name: ListIATAs :many SELECT iata, display_name, approx_lat, approx_lng, added_at, border FROM iata_codes ORDER BY iata ` func (q *Queries) ListIATAs(ctx context.Context) ([]IataCode, error) { rows, err := q.db.Query(ctx, listIATAs) if err != nil { return nil, err } defer rows.Close() items := []IataCode{} for rows.Next() { var i IataCode if err := rows.Scan( &i.Iata, &i.DisplayName, &i.ApproxLat, &i.ApproxLng, &i.AddedAt, &i.Border, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listKnownRoutes = `-- name: ListKnownRoutes :many SELECT r.id, r.node_ids, r.hash_prefix, r.iata, r.hop_count, r.first_seen, r.last_seen, r.observation_count FROM (( SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count FROM known_routes WHERE $1 = '' AND ($2 = 0 OR hop_count = $2) AND ($3::timestamptz IS NULL OR date_trunc('milliseconds', last_seen, 'UTC') < $3 OR ($5::bigint > 0 AND date_trunc('milliseconds', last_seen, 'UTC') = $3 AND id < $5)) ORDER BY date_trunc('milliseconds', last_seen, 'UTC') DESC, id DESC LIMIT $4 ) UNION ALL ( SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count FROM known_routes WHERE $1 <> '' AND iata::text = $1 AND iata >= $1::bpchar AND iata <= $1::bpchar AND ($2 = 0 OR hop_count = $2) AND ($3::timestamptz IS NULL OR date_trunc('milliseconds', last_seen, 'UTC') < $3 OR ($5::bigint > 0 AND date_trunc('milliseconds', last_seen, 'UTC') = $3 AND id < $5)) ORDER BY iata, date_trunc('milliseconds', last_seen, 'UTC') DESC, id DESC LIMIT $4 )) r ORDER BY date_trunc('milliseconds', r.last_seen, 'UTC') DESC, r.id DESC LIMIT $4 ` type ListKnownRoutesParams struct { Column1 interface{} `json:"column_1"` Column2 interface{} `json:"column_2"` Column3 pgtype.Timestamptz `json:"column_3"` Limit int32 `json:"limit"` Column5 int64 `json:"column_5"` } type ListKnownRoutesRow struct { ID int64 `json:"id"` NodeIds []uuid.UUID `json:"node_ids"` HashPrefix [][]byte `json:"hash_prefix"` Iata string `json:"iata"` HopCount int32 `json:"hop_count"` FirstSeen pgtype.Timestamptz `json:"first_seen"` LastSeen pgtype.Timestamptz `json:"last_seen"` ObservationCount int64 `json:"observation_count"` } // Only one branch runs. Keep the IATA range ordered by the composite index: // generic plans can otherwise prefer scanning the global timestamp index. // The text equality preserves exact input matching, including trailing spaces. // Pages by (last_seen to the ms, id) so routes sharing the cursor's millisecond // aren't skipped; cursor id 0 keeps the plain timestamp cursor. func (q *Queries) ListKnownRoutes(ctx context.Context, arg ListKnownRoutesParams) ([]ListKnownRoutesRow, error) { rows, err := q.db.Query(ctx, listKnownRoutes, arg.Column1, arg.Column2, arg.Column3, arg.Limit, arg.Column5, ) if err != nil { return nil, err } defer rows.Close() items := []ListKnownRoutesRow{} for rows.Next() { var i ListKnownRoutesRow if err := rows.Scan( &i.ID, &i.NodeIds, &i.HashPrefix, &i.Iata, &i.HopCount, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listMessagesAfterID = `-- name: ListMessagesAfterID :many SELECT DISTINCT ON (cm.id) cm.id, cm.channel_id, cm.packet_hash, cm.sender_name, cm.sender_pubkey, cm.content, cm.sent_at, encode(cm.packet_hash, 'hex') as packet_hash_hex, c.channel_hash, ts.name AS scope_name, p.transport_codes_present, p.observation_count AS observation_count FROM channel_messages cm JOIN channels c ON c.id = cm.channel_id JOIN packet_observations po ON po.packet_hash = cm.packet_hash JOIN packets p ON p.packet_hash = cm.packet_hash LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE cm.id > $1 AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR po.iata = ANY($2::bpchar[])) AND ($3::text = '' OR ts.name = $3::text) ORDER BY cm.id ASC LIMIT $4 ` type ListMessagesAfterIDParams struct { ID int64 `json:"id"` Column2 []string `json:"column_2"` Column3 string `json:"column_3"` Limit int32 `json:"limit"` } type ListMessagesAfterIDRow struct { ID int64 `json:"id"` ChannelID int32 `json:"channel_id"` PacketHash []byte `json:"packet_hash"` SenderName *string `json:"sender_name"` SenderPubkey []byte `json:"sender_pubkey"` Content *string `json:"content"` SentAt pgtype.Timestamptz `json:"sent_at"` PacketHashHex string `json:"packet_hash_hex"` ChannelHash []byte `json:"channel_hash"` ScopeName *string `json:"scope_name"` TransportCodesPresent *bool `json:"transport_codes_present"` ObservationCount int64 `json:"observation_count"` } // Returns messages after the given message ID, ordered oldest first. // Used for WS reconnect backfill. func (q *Queries) ListMessagesAfterID(ctx context.Context, arg ListMessagesAfterIDParams) ([]ListMessagesAfterIDRow, error) { rows, err := q.db.Query(ctx, listMessagesAfterID, arg.ID, arg.Column2, arg.Column3, arg.Limit, ) if err != nil { return nil, err } defer rows.Close() items := []ListMessagesAfterIDRow{} for rows.Next() { var i ListMessagesAfterIDRow if err := rows.Scan( &i.ID, &i.ChannelID, &i.PacketHash, &i.SenderName, &i.SenderPubkey, &i.Content, &i.SentAt, &i.PacketHashHex, &i.ChannelHash, &i.ScopeName, &i.TransportCodesPresent, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNodeObservations = `-- name: ListNodeObservations :many SELECT po.id, encode(po.packet_hash, 'hex') AS packet_hash_hex, p.payload_type, po.iata, po.heard_at, po.rssi, po.snr, po.hop_count FROM packet_observations po JOIN packets p ON p.packet_hash = po.packet_hash JOIN nodes n ON n.public_key = p.origin_pubkey WHERE n.id = $1 AND ($2 = 0 OR po.id < $2) ORDER BY po.id DESC LIMIT $3 ` type ListNodeObservationsParams struct { ID uuid.UUID `json:"id"` Column2 interface{} `json:"column_2"` Limit int32 `json:"limit"` } type ListNodeObservationsRow struct { ID int64 `json:"id"` PacketHashHex string `json:"packet_hash_hex"` PayloadType int16 `json:"payload_type"` Iata string `json:"iata"` HeardAt pgtype.Timestamptz `json:"heard_at"` Rssi *int16 `json:"rssi"` Snr *float32 `json:"snr"` HopCount int16 `json:"hop_count"` } func (q *Queries) ListNodeObservations(ctx context.Context, arg ListNodeObservationsParams) ([]ListNodeObservationsRow, error) { rows, err := q.db.Query(ctx, listNodeObservations, arg.ID, arg.Column2, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []ListNodeObservationsRow{} for rows.Next() { var i ListNodeObservationsRow if err := rows.Scan( &i.ID, &i.PacketHashHex, &i.PayloadType, &i.Iata, &i.HeardAt, &i.Rssi, &i.Snr, &i.HopCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listNodes = `-- name: ListNodes :many WITH page AS ( SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_seen, n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, ts.name AS default_scope_name FROM nodes n LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id WHERE ($1 = 0 OR n.node_type = $1) AND (COALESCE(cardinality($2::bpchar[]), 0) = 0 OR n.id IN (SELECT node_id FROM node_iatas WHERE iata = ANY($2::bpchar[]))) AND ( $3::text = 'any' OR ($3::text = 'true' AND n.supports_multibyte_paths = TRUE) OR ($3::text = 'false' AND n.supports_multibyte_paths = FALSE) ) AND ( $4::text = 'any' OR ($4::text = 'true' AND n.supports_multibyte_traces = TRUE) OR ($4::text = 'false' AND n.supports_multibyte_traces = FALSE) ) AND ($5::bytea IS NULL OR n.public_key = $5) AND ($6 = '' OR n.name ILIKE '%' || $6 || '%') AND ($7::timestamptz IS NULL OR n.last_seen < $7) AND ($9::text = '' OR ts.name = $9::text) AND ($11::text = '' OR encode(n.public_key, 'hex') ILIKE $11 || '%') ORDER BY n.last_seen DESC LIMIT $8 ) SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_seen, n.radio_freq_mhz, n.radio_sf, n.radio_bw_khz, n.default_scope_name, (SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC) FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas, EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer, (SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id, (SELECT COUNT(DISTINCT nn.neighbor_id) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count, (CASE WHEN $10::bool THEN (SELECT COALESCE(array_agg(DISTINCT nn.neighbor_id), '{}'::uuid[]) FROM node_neighbors nn WHERE nn.node_id = n.id) ELSE NULL END)::uuid[] AS neighbor_ids FROM page n ORDER BY n.last_seen DESC ` type ListNodesParams struct { Column1 interface{} `json:"column_1"` Column2 []string `json:"column_2"` Column3 string `json:"column_3"` Column4 string `json:"column_4"` Column5 []byte `json:"column_5"` Column6 interface{} `json:"column_6"` Column7 pgtype.Timestamptz `json:"column_7"` Limit int32 `json:"limit"` Column9 string `json:"column_9"` Column10 bool `json:"column_10"` Column11 string `json:"column_11"` } type ListNodesRow struct { ID uuid.UUID `json:"id"` PublicKey []byte `json:"public_key"` NodeType int16 `json:"node_type"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` LastSeen pgtype.Timestamptz `json:"last_seen"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioSf *int16 `json:"radio_sf"` RadioBwKhz *float32 `json:"radio_bw_khz"` DefaultScopeName *string `json:"default_scope_name"` Iatas []byte `json:"iatas"` IsObserver bool `json:"is_observer"` ObserverID uuid.UUID `json:"observer_id"` KnownNeighborCount int64 `json:"known_neighbor_count"` NeighborIds []uuid.UUID `json:"neighbor_ids"` } // Limit the filtered node page before enriching IATA membership and neighbours. func (q *Queries) ListNodes(ctx context.Context, arg ListNodesParams) ([]ListNodesRow, error) { rows, err := q.db.Query(ctx, listNodes, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Column6, arg.Column7, arg.Limit, arg.Column9, arg.Column10, arg.Column11, ) if err != nil { return nil, err } defer rows.Close() items := []ListNodesRow{} for rows.Next() { var i ListNodesRow if err := rows.Scan( &i.ID, &i.PublicKey, &i.NodeType, &i.Name, &i.Latitude, &i.Longitude, &i.LastSeen, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.DefaultScopeName, &i.Iatas, &i.IsObserver, &i.ObserverID, &i.KnownNeighborCount, &i.NeighborIds, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listObservationsForPacket = `-- name: ListObservationsForPacket :many SELECT po.id, po.packet_hash, po.observer_id, po.iata, po.heard_at, po.path_length_byte, po.hash_size, po.hop_count, po.path_bytes, po.rssi, po.snr, po.propagation_time_ms, po.radio_freq_mhz, po.spread_factor, po.bandwidth_khz, po.coding_rate, po.source_broker, po.payload_type, po.airtime_ms, o.display_name AS observer_name FROM packet_observations po LEFT JOIN observers o ON o.id = po.observer_id WHERE po.packet_hash = $1 ORDER BY po.heard_at ASC ` type ListObservationsForPacketRow struct { ID int64 `json:"id"` PacketHash []byte `json:"packet_hash"` ObserverID uuid.UUID `json:"observer_id"` Iata string `json:"iata"` HeardAt pgtype.Timestamptz `json:"heard_at"` PathLengthByte int16 `json:"path_length_byte"` HashSize int16 `json:"hash_size"` HopCount int16 `json:"hop_count"` PathBytes []byte `json:"path_bytes"` Rssi *int16 `json:"rssi"` Snr *float32 `json:"snr"` PropagationTimeMs *int32 `json:"propagation_time_ms"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` SpreadFactor *int16 `json:"spread_factor"` BandwidthKhz *float32 `json:"bandwidth_khz"` CodingRate *int16 `json:"coding_rate"` SourceBroker *string `json:"source_broker"` PayloadType *int16 `json:"payload_type"` AirtimeMs *float32 `json:"airtime_ms"` ObserverName *string `json:"observer_name"` } func (q *Queries) ListObservationsForPacket(ctx context.Context, packetHash []byte) ([]ListObservationsForPacketRow, error) { rows, err := q.db.Query(ctx, listObservationsForPacket, packetHash) if err != nil { return nil, err } defer rows.Close() items := []ListObservationsForPacketRow{} for rows.Next() { var i ListObservationsForPacketRow if err := rows.Scan( &i.ID, &i.PacketHash, &i.ObserverID, &i.Iata, &i.HeardAt, &i.PathLengthByte, &i.HashSize, &i.HopCount, &i.PathBytes, &i.Rssi, &i.Snr, &i.PropagationTimeMs, &i.RadioFreqMhz, &i.SpreadFactor, &i.BandwidthKhz, &i.CodingRate, &i.SourceBroker, &i.PayloadType, &i.AirtimeMs, &i.ObserverName, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listObserverAdverts = `-- name: ListObserverAdverts :many SELECT po.id, encode(po.packet_hash, 'hex') AS packet_hash_hex, p.payload_type, po.iata, po.heard_at, po.rssi, po.snr, po.hop_count, n.name AS node_name, COALESCE(encode(p.origin_pubkey, 'hex'), '')::text AS node_public_key FROM packet_observations po JOIN packets p ON p.packet_hash = po.packet_hash LEFT JOIN nodes n ON n.public_key = p.origin_pubkey WHERE po.observer_id = $1 AND p.payload_type = 4 AND ($2 = 0 OR po.id > $2) ORDER BY po.id ASC LIMIT $3 ` type ListObserverAdvertsParams struct { ObserverID uuid.UUID `json:"observer_id"` Column2 interface{} `json:"column_2"` Limit int32 `json:"limit"` } type ListObserverAdvertsRow struct { ID int64 `json:"id"` PacketHashHex string `json:"packet_hash_hex"` PayloadType int16 `json:"payload_type"` Iata string `json:"iata"` HeardAt pgtype.Timestamptz `json:"heard_at"` Rssi *int16 `json:"rssi"` Snr *float32 `json:"snr"` HopCount int16 `json:"hop_count"` NodeName *string `json:"node_name"` NodePublicKey string `json:"node_public_key"` } // Returns advert packets (payload_type=4) heard by a specific observer. // Pass cursor=0 to start from the beginning, or the last seen id for pagination. // Keep missing-origin adverts; the generated key field expects a string, not NULL. func (q *Queries) ListObserverAdverts(ctx context.Context, arg ListObserverAdvertsParams) ([]ListObserverAdvertsRow, error) { rows, err := q.db.Query(ctx, listObserverAdverts, arg.ObserverID, arg.Column2, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []ListObserverAdvertsRow{} for rows.Next() { var i ListObserverAdvertsRow if err := rows.Scan( &i.ID, &i.PacketHashHex, &i.PayloadType, &i.Iata, &i.HeardAt, &i.Rssi, &i.Snr, &i.HopCount, &i.NodeName, &i.NodePublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listObservers = `-- name: ListObservers :many SELECT o.id, o.display_name, o.observer_type, o.last_status_at, o.radio_freq_mhz, o.radio_sf, o.radio_bw_khz, array_remove(array_agg(DISTINCT ts.name ORDER BY ts.name), NULL)::text[] AS scopes, COALESCE(CASE WHEN GREATEST(COALESCE(o.last_status_at, o.last_seen), o.last_seen) > NOW() - INTERVAL '5 minutes' THEN 'online' ELSE 'offline' END, 'offline')::text AS status, COALESCE(o.last_iata, '')::text AS iata FROM observers o LEFT JOIN observer_brokers ob ON ob.observer_id = o.id LEFT JOIN observer_scopes os ON os.observer_id = o.id LEFT JOIN transport_scopes ts ON ts.id = os.scope_id WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR o.last_iata = ANY($1::bpchar[])) AND ($2 = '' OR o.observer_type = $2) AND ($3 = '' OR ob.broker_name = $3) AND ($4 = '' OR CASE WHEN GREATEST(COALESCE(o.last_status_at, o.last_seen), o.last_seen) > NOW() - INTERVAL '5 minutes' THEN 'online' ELSE 'offline' END = $4) AND ($5 = '' OR o.display_name ILIKE '%' || $5 || '%') AND ($6::timestamptz IS NULL OR o.last_seen < $6) AND ($8::text = '' OR EXISTS ( SELECT 1 FROM observer_scopes os2 JOIN transport_scopes ts2 ON ts2.id = os2.scope_id WHERE os2.observer_id = o.id AND ts2.name = $8::text )) GROUP BY o.id ORDER BY o.last_seen DESC LIMIT $7 ` type ListObserversParams struct { Column1 []string `json:"column_1"` Column2 interface{} `json:"column_2"` Column3 interface{} `json:"column_3"` Column4 interface{} `json:"column_4"` Column5 interface{} `json:"column_5"` Column6 pgtype.Timestamptz `json:"column_6"` Limit int32 `json:"limit"` Column8 string `json:"column_8"` } type ListObserversRow struct { ID uuid.UUID `json:"id"` DisplayName *string `json:"display_name"` ObserverType *string `json:"observer_type"` LastStatusAt pgtype.Timestamptz `json:"last_status_at"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioSf *int16 `json:"radio_sf"` RadioBwKhz *float32 `json:"radio_bw_khz"` Scopes []string `json:"scopes"` Status string `json:"status"` Iata string `json:"iata"` } // Pass cursor=0 to start from the beginning, or the last seen observer's rownum for pagination. // Note: observers use UUID PKs so we order by last_seen and use a keyset on last_seen+id. func (q *Queries) ListObservers(ctx context.Context, arg ListObserversParams) ([]ListObserversRow, error) { rows, err := q.db.Query(ctx, listObservers, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Column6, arg.Limit, arg.Column8, ) if err != nil { return nil, err } defer rows.Close() items := []ListObserversRow{} for rows.Next() { var i ListObserversRow if err := rows.Scan( &i.ID, &i.DisplayName, &i.ObserverType, &i.LastStatusAt, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.Scopes, &i.Status, &i.Iata, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPackets = `-- name: ListPackets :many SELECT p.packet_hash, p.payload_type, COALESCE(CASE WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string' THEN p.parsed_payload #>> '{appData,name}' WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK' AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string' AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$' THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum') WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING') AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string' AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$' THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag') END, '')::text AS summary, p.route_type, p.first_heard_at, p.last_heard_at, p.scope_id, ts.name AS scope_name, p.observation_count AS observation_count, -- sqlc loses LATERAL nullability; these scalar defaults are ignored when observer_id is NULL. po.observer_id AS latest_observer_id, o.display_name AS latest_observer_name, COALESCE(po.iata, ''::bpchar) AS latest_observer_iata, COALESCE(po.path_length_byte, 0::smallint) AS latest_observer_path_length_byte, COALESCE(po.hash_size, 0::smallint) AS latest_observer_hash_size, COALESCE(po.hop_count, 0::smallint) AS latest_observer_hop_count, po.path_bytes AS latest_observer_path_bytes, p.raw_payload, p.origin_pubkey FROM packets p LEFT JOIN LATERAL ( SELECT observer_id, iata, path_length_byte, hash_size, hop_count, path_bytes FROM packet_observations WHERE packet_hash = p.packet_hash ORDER BY heard_at DESC LIMIT 1 ) po ON true LEFT JOIN observers o ON o.id = po.observer_id LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE (COALESCE(cardinality($1::smallint[]), 0) = 0 OR p.payload_type = ANY($1::smallint[])) AND (COALESCE(cardinality($2::smallint[]), 0) = 0 OR p.route_type = ANY($2::smallint[])) AND ($3::timestamptz IS NULL OR p.first_heard_at >= $3) AND ($4::timestamptz IS NULL OR p.first_heard_at <= $4) AND ($5::timestamptz IS NULL OR p.last_heard_at < $5) AND (COALESCE(cardinality($7::text[]), 0) = 0 OR ts.name = ANY($7::text[])) ORDER BY p.last_heard_at DESC LIMIT $6 ` type ListPacketsParams struct { Column1 []int16 `json:"column_1"` Column2 []int16 `json:"column_2"` Column3 pgtype.Timestamptz `json:"column_3"` Column4 pgtype.Timestamptz `json:"column_4"` Column5 pgtype.Timestamptz `json:"column_5"` Limit int32 `json:"limit"` Column7 []string `json:"column_7"` } type ListPacketsRow struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` Summary string `json:"summary"` RouteType int16 `json:"route_type"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` ScopeID *int32 `json:"scope_id"` ScopeName *string `json:"scope_name"` ObservationCount int64 `json:"observation_count"` LatestObserverID uuid.UUID `json:"latest_observer_id"` LatestObserverName *string `json:"latest_observer_name"` LatestObserverIata string `json:"latest_observer_iata"` LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"` LatestObserverHashSize int16 `json:"latest_observer_hash_size"` LatestObserverHopCount int16 `json:"latest_observer_hop_count"` LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"` RawPayload []byte `json:"raw_payload"` OriginPubkey []byte `json:"origin_pubkey"` } // Returns packets with the latest observation rolled in for display. // Pass cursor=0 to start from the beginning. IATA-filtered requests are // served by ListPacketsByIATAs instead. func (q *Queries) ListPackets(ctx context.Context, arg ListPacketsParams) ([]ListPacketsRow, error) { rows, err := q.db.Query(ctx, listPackets, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Limit, arg.Column7, ) if err != nil { return nil, err } defer rows.Close() items := []ListPacketsRow{} for rows.Next() { var i ListPacketsRow if err := rows.Scan( &i.PacketHash, &i.PayloadType, &i.Summary, &i.RouteType, &i.FirstHeardAt, &i.LastHeardAt, &i.ScopeID, &i.ScopeName, &i.ObservationCount, &i.LatestObserverID, &i.LatestObserverName, &i.LatestObserverIata, &i.LatestObserverPathLengthByte, &i.LatestObserverHashSize, &i.LatestObserverHopCount, &i.LatestObserverPathBytes, &i.RawPayload, &i.OriginPubkey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPacketsAfterID = `-- name: ListPacketsAfterID :many SELECT p.packet_hash, p.payload_type, COALESCE(CASE WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string' THEN p.parsed_payload #>> '{appData,name}' WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK' AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string' AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$' THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum') WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING') AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string' AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$' THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag') END, '')::text AS summary, p.route_type, p.first_heard_at, p.last_heard_at, p.observation_count AS observation_count, po.observer_id AS latest_observer_id, o.display_name AS latest_observer_name, po.iata AS latest_observer_iata, po.path_length_byte AS latest_observer_path_length_byte, po.hash_size AS latest_observer_hash_size, po.hop_count AS latest_observer_hop_count, po.path_bytes AS latest_observer_path_bytes, p.raw_payload, p.origin_pubkey, ts.name AS scope_name FROM packets p JOIN packet_observations po ON po.packet_hash = p.packet_hash LEFT JOIN observers o ON o.id = po.observer_id LEFT JOIN transport_scopes ts ON ts.id = p.scope_id WHERE po.id > $1 AND ($2::smallint = -1 OR p.payload_type = $2::smallint) AND ($3::smallint = -1 OR p.route_type = $3::smallint) AND (COALESCE(cardinality($4::bpchar[]), 0) = 0 OR po.iata = ANY($4::bpchar[])) AND ($5::text = '' OR ts.name = $5::text) ORDER BY po.id ASC LIMIT $6 ` type ListPacketsAfterIDParams struct { ID int64 `json:"id"` Column2 int16 `json:"column_2"` Column3 int16 `json:"column_3"` Column4 []string `json:"column_4"` Column5 string `json:"column_5"` Limit int32 `json:"limit"` } type ListPacketsAfterIDRow struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` Summary string `json:"summary"` RouteType int16 `json:"route_type"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` ObservationCount int64 `json:"observation_count"` LatestObserverID uuid.UUID `json:"latest_observer_id"` LatestObserverName *string `json:"latest_observer_name"` LatestObserverIata string `json:"latest_observer_iata"` LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"` LatestObserverHashSize int16 `json:"latest_observer_hash_size"` LatestObserverHopCount int16 `json:"latest_observer_hop_count"` LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"` RawPayload []byte `json:"raw_payload"` OriginPubkey []byte `json:"origin_pubkey"` ScopeName *string `json:"scope_name"` } // Returns packets with observations after the given observation ID, ordered oldest first. // Used for WS reconnect backfill. Pass afterObservationId=0 to start from the beginning. func (q *Queries) ListPacketsAfterID(ctx context.Context, arg ListPacketsAfterIDParams) ([]ListPacketsAfterIDRow, error) { rows, err := q.db.Query(ctx, listPacketsAfterID, arg.ID, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Limit, ) if err != nil { return nil, err } defer rows.Close() items := []ListPacketsAfterIDRow{} for rows.Next() { var i ListPacketsAfterIDRow if err := rows.Scan( &i.PacketHash, &i.PayloadType, &i.Summary, &i.RouteType, &i.FirstHeardAt, &i.LastHeardAt, &i.ObservationCount, &i.LatestObserverID, &i.LatestObserverName, &i.LatestObserverIata, &i.LatestObserverPathLengthByte, &i.LatestObserverHashSize, &i.LatestObserverHopCount, &i.LatestObserverPathBytes, &i.RawPayload, &i.OriginPubkey, &i.ScopeName, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPacketsByIATAs = `-- name: ListPacketsByIATAs :many WITH scanned AS ( SELECT req.iata AS req_iata, hits.packet_hash, hits.heard_at FROM unnest($1::bpchar[]) AS req(iata) CROSS JOIN LATERAL ( SELECT po3.packet_hash, po3.heard_at FROM packet_observations po3 JOIN packets p2 ON p2.packet_hash = po3.packet_hash WHERE po3.iata = req.iata AND po3.heard_at < COALESCE($2::timestamptz, 'infinity'::timestamptz) AND (COALESCE(cardinality($3::smallint[]), 0) = 0 OR p2.payload_type = ANY($3::smallint[])) AND (COALESCE(cardinality($4::smallint[]), 0) = 0 OR p2.route_type = ANY($4::smallint[])) AND ($5::timestamptz IS NULL OR p2.first_heard_at >= $5) AND ($6::timestamptz IS NULL OR p2.first_heard_at <= $6) AND (COALESCE(cardinality($7::text[]), 0) = 0 OR EXISTS ( SELECT 1 FROM transport_scopes ts2 WHERE ts2.id = p2.scope_id AND ts2.name = ANY($7::text[]))) ORDER BY po3.heard_at DESC LIMIT $8 ) hits ), saturation AS ( SELECT COUNT(*) > 0 AS scan_saturated, MAX(floor_ts)::timestamptz AS scan_floor FROM ( SELECT MIN(heard_at) AS floor_ts FROM scanned GROUP BY req_iata HAVING COUNT(*) >= $8 ) filled ), page AS ( SELECT scanned.packet_hash, MAX(scanned.heard_at)::timestamptz AS site_heard_at FROM scanned GROUP BY scanned.packet_hash HAVING ($2::timestamptz IS NULL OR NOT EXISTS ( SELECT 1 FROM packet_observations px WHERE px.packet_hash = scanned.packet_hash AND px.iata = ANY($1::bpchar[]) AND px.heard_at >= $2)) ORDER BY site_heard_at DESC LIMIT $9 ) SELECT p.packet_hash, p.payload_type, COALESCE(CASE WHEN p.payload_type = 4 AND jsonb_typeof(p.parsed_payload #> '{appData,name}') = 'string' THEN p.parsed_payload #>> '{appData,name}' WHEN p.payload_type = 3 AND p.parsed_payload ->> 'type' = 'ACK' AND jsonb_typeof(p.parsed_payload -> 'checksum') = 'string' AND p.parsed_payload ->> 'checksum' ~ '^[0-9a-fA-F]{8}$' THEN 'ACK ' || lower(p.parsed_payload ->> 'checksum') WHEN p.payload_type = 9 AND p.parsed_payload ->> 'type' IN ('TRACE', 'PING') AND jsonb_typeof(p.parsed_payload -> 'traceTag') = 'string' AND p.parsed_payload ->> 'traceTag' ~ '^[0-9a-fA-F]{8}$' THEN (p.parsed_payload ->> 'type') || ' ' || lower(p.parsed_payload ->> 'traceTag') END, '')::text AS summary, p.route_type, p.first_heard_at, p.last_heard_at, p.scope_id, ts.name AS scope_name, sh.site_heard_at, sat.scan_saturated, sat.scan_floor, p.observation_count AS observation_count, -- sqlc loses LATERAL nullability; these scalar defaults are ignored when observer_id is NULL. po.observer_id AS latest_observer_id, o.display_name AS latest_observer_name, COALESCE(po.iata, ''::bpchar) AS latest_observer_iata, COALESCE(po.path_length_byte, 0::smallint) AS latest_observer_path_length_byte, COALESCE(po.hash_size, 0::smallint) AS latest_observer_hash_size, COALESCE(po.hop_count, 0::smallint) AS latest_observer_hop_count, po.path_bytes AS latest_observer_path_bytes, p.raw_payload, p.origin_pubkey FROM page sh CROSS JOIN saturation sat JOIN packets p ON p.packet_hash = sh.packet_hash LEFT JOIN LATERAL ( SELECT observer_id, iata, path_length_byte, hash_size, hop_count, path_bytes FROM packet_observations WHERE packet_hash = p.packet_hash ORDER BY heard_at DESC LIMIT 1 ) po ON true LEFT JOIN observers o ON o.id = po.observer_id LEFT JOIN transport_scopes ts ON ts.id = p.scope_id ORDER BY sh.site_heard_at DESC ` type ListPacketsByIATAsParams struct { Iatas []string `json:"iatas"` CursorTs pgtype.Timestamptz `json:"cursor_ts"` PayloadTypes []int16 `json:"payload_types"` RouteTypes []int16 `json:"route_types"` SinceTs pgtype.Timestamptz `json:"since_ts"` UntilTs pgtype.Timestamptz `json:"until_ts"` ScopeNames []string `json:"scope_names"` ScanDepth int32 `json:"scan_depth"` PageLimit int32 `json:"page_limit"` } type ListPacketsByIATAsRow struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` Summary string `json:"summary"` RouteType int16 `json:"route_type"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` ScopeID *int32 `json:"scope_id"` ScopeName *string `json:"scope_name"` SiteHeardAt pgtype.Timestamptz `json:"site_heard_at"` ScanSaturated bool `json:"scan_saturated"` ScanFloor pgtype.Timestamptz `json:"scan_floor"` ObservationCount int64 `json:"observation_count"` LatestObserverID uuid.UUID `json:"latest_observer_id"` LatestObserverName *string `json:"latest_observer_name"` LatestObserverIata string `json:"latest_observer_iata"` LatestObserverPathLengthByte int16 `json:"latest_observer_path_length_byte"` LatestObserverHashSize int16 `json:"latest_observer_hash_size"` LatestObserverHopCount int16 `json:"latest_observer_hop_count"` LatestObserverPathBytes []byte `json:"latest_observer_path_bytes"` RawPayload []byte `json:"raw_payload"` OriginPubkey []byte `json:"origin_pubkey"` } // IATA-filtered packet list, driven from idx_observations_iata_heard. // Walking packets newest-first and probing for the site probes ~589k packets // to fill a page for a quiet site; walking the site's own observation log is // proportional to the page size instead. Results are ordered by when the // requested sites heard the packet (site-local recency) and the cursor // follows that ordering. scan_depth caps how deep each site's observation // log is walked. A packet repeats once per observer that heard it, so a // page can collapse to fewer distinct packets than were asked for without // the site being exhausted. scan_saturated reports whether any site hit // that cap and scan_floor the oldest heard_at they all cover, so a short // page can keep paging instead of reading as the end of the data. // A site that filled scan_depth still has unread history below its floor. // The newest such floor is the point above which every site is covered. func (q *Queries) ListPacketsByIATAs(ctx context.Context, arg ListPacketsByIATAsParams) ([]ListPacketsByIATAsRow, error) { rows, err := q.db.Query(ctx, listPacketsByIATAs, arg.Iatas, arg.CursorTs, arg.PayloadTypes, arg.RouteTypes, arg.SinceTs, arg.UntilTs, arg.ScopeNames, arg.ScanDepth, arg.PageLimit, ) if err != nil { return nil, err } defer rows.Close() items := []ListPacketsByIATAsRow{} for rows.Next() { var i ListPacketsByIATAsRow if err := rows.Scan( &i.PacketHash, &i.PayloadType, &i.Summary, &i.RouteType, &i.FirstHeardAt, &i.LastHeardAt, &i.ScopeID, &i.ScopeName, &i.SiteHeardAt, &i.ScanSaturated, &i.ScanFloor, &i.ObservationCount, &i.LatestObserverID, &i.LatestObserverName, &i.LatestObserverIata, &i.LatestObserverPathLengthByte, &i.LatestObserverHashSize, &i.LatestObserverHopCount, &i.LatestObserverPathBytes, &i.RawPayload, &i.OriginPubkey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listRegionState = `-- name: ListRegionState :many SELECT r.slug, r.name, COALESCE(r.display_order, 0)::int AS display_order, r.imported, r.center_lat, r.center_lng, COALESCE(array_agg(ri.iata::text ORDER BY ri.iata) FILTER (WHERE ri.iata IS NOT NULL), '{}')::text[] AS iatas FROM regions r LEFT JOIN region_iatas ri ON ri.region_id = r.id GROUP BY r.id ORDER BY r.slug ` type ListRegionStateRow struct { Slug string `json:"slug"` Name string `json:"name"` DisplayOrder int32 `json:"display_order"` Imported bool `json:"imported"` CenterLat *float64 `json:"center_lat"` CenterLng *float64 `json:"center_lng"` Iatas []string `json:"iatas"` } // Every region with its members, for reconciling imported MeshMapper groups. func (q *Queries) ListRegionState(ctx context.Context) ([]ListRegionStateRow, error) { rows, err := q.db.Query(ctx, listRegionState) if err != nil { return nil, err } defer rows.Close() items := []ListRegionStateRow{} for rows.Next() { var i ListRegionStateRow if err := rows.Scan( &i.Slug, &i.Name, &i.DisplayOrder, &i.Imported, &i.CenterLat, &i.CenterLng, &i.Iatas, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listRegions = `-- name: ListRegions :many SELECT id, slug, name FROM regions ORDER BY display_order, name ` type ListRegionsRow struct { ID int32 `json:"id"` Slug string `json:"slug"` Name string `json:"name"` } // ============================================================ // REGIONS // ============================================================ func (q *Queries) ListRegions(ctx context.Context) ([]ListRegionsRow, error) { rows, err := q.db.Query(ctx, listRegions) if err != nil { return nil, err } defer rows.Close() items := []ListRegionsRow{} for rows.Next() { var i ListRegionsRow if err := rows.Scan(&i.ID, &i.Slug, &i.Name); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listScopeCatalogues = `-- name: ListScopeCatalogues :many SELECT iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_scope_catalogues ORDER BY iata, attempted_at ` func (q *Queries) ListScopeCatalogues(ctx context.Context) ([]MeshmapperScopeCatalogue, error) { rows, err := q.db.Query(ctx, listScopeCatalogues) if err != nil { return nil, err } defer rows.Close() items := []MeshmapperScopeCatalogue{} for rows.Next() { var i MeshmapperScopeCatalogue if err := rows.Scan( &i.Iata, &i.Url, &i.Payload, &i.Etag, &i.CheckedAt, &i.AttemptedAt, &i.NextAttempt, &i.LastError, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTraceTags = `-- name: ListTraceTags :many SELECT encode(t.trace_tag, 'hex') AS trace_tag, t.first_heard_at, t.last_heard_at, t.packet_count, (SELECT COUNT(*) FROM trace_iatas ti WHERE ti.trace_tag = t.trace_tag AND (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR ti.iata = ANY($1::bpchar[]))) AS iata_count, COALESCE(t.trace_type, '')::text AS trace_type, t.best_payload FROM trace_tags t WHERE (COALESCE(cardinality($1::bpchar[]), 0) = 0 OR EXISTS ( SELECT 1 FROM trace_iatas ti WHERE ti.trace_tag = t.trace_tag AND ti.iata = ANY($1::bpchar[]))) AND ($2::text = '' OR t.scope_id = (SELECT id FROM transport_scopes WHERE name = $2)) AND ($3::timestamptz IS NULL OR t.first_heard_at >= $3) AND ($4::timestamptz IS NULL OR t.first_heard_at <= $4) -- Keyset on the millisecond clients see, tie-broken by tag; matches idx_trace_tags_keyset. AND ($5::timestamptz IS NULL OR date_trunc('milliseconds', t.last_heard_at, 'UTC') < $5 OR ($8::bytea IS NOT NULL AND date_trunc('milliseconds', t.last_heard_at, 'UTC') = $5 AND t.trace_tag < $8)) AND ($7::text = '' OR t.trace_type = $7) ORDER BY date_trunc('milliseconds', t.last_heard_at, 'UTC') DESC, t.trace_tag DESC LIMIT $6 ` type ListTraceTagsParams struct { Column1 []string `json:"column_1"` Column2 string `json:"column_2"` Column3 pgtype.Timestamptz `json:"column_3"` Column4 pgtype.Timestamptz `json:"column_4"` Column5 pgtype.Timestamptz `json:"column_5"` Limit int32 `json:"limit"` Column7 string `json:"column_7"` Column8 []byte `json:"column_8"` } type ListTraceTagsRow struct { TraceTag string `json:"trace_tag"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` PacketCount int64 `json:"packet_count"` IataCount int64 `json:"iata_count"` TraceType string `json:"trace_type"` BestPayload []byte `json:"best_payload"` } // ============================================================ // TRACES // ============================================================ // Tags, most recent first. Filters match the tag summary (see RecordTrace). func (q *Queries) ListTraceTags(ctx context.Context, arg ListTraceTagsParams) ([]ListTraceTagsRow, error) { rows, err := q.db.Query(ctx, listTraceTags, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Column5, arg.Limit, arg.Column7, arg.Column8, ) if err != nil { return nil, err } defer rows.Close() items := []ListTraceTagsRow{} for rows.Next() { var i ListTraceTagsRow if err := rows.Scan( &i.TraceTag, &i.FirstHeardAt, &i.LastHeardAt, &i.PacketCount, &i.IataCount, &i.TraceType, &i.BestPayload, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listUndecryptedGroupTextPackets = `-- name: ListUndecryptedGroupTextPackets :many SELECT packet_hash, raw_payload FROM packets WHERE payload_type = 5 AND decrypted IS NOT TRUE ` type ListUndecryptedGroupTextPacketsRow struct { PacketHash []byte `json:"packet_hash"` RawPayload []byte `json:"raw_payload"` } // Returns GRP_TXT packets (payload_type=5) never successfully decrypted. Used at boot to // retry decryption against the current keystore for packets whose channel key was only added // to the config after they'd already been ingested -- see // internal/ingest.BackfillChannelMessages. func (q *Queries) ListUndecryptedGroupTextPackets(ctx context.Context) ([]ListUndecryptedGroupTextPacketsRow, error) { rows, err := q.db.Query(ctx, listUndecryptedGroupTextPackets) if err != nil { return nil, err } defer rows.Close() items := []ListUndecryptedGroupTextPacketsRow{} for rows.Next() { var i ListUndecryptedGroupTextPacketsRow if err := rows.Scan(&i.PacketHash, &i.RawPayload); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listUndecryptedGroupTextPacketsByHash = `-- name: ListUndecryptedGroupTextPacketsByHash :many SELECT packet_hash, raw_payload FROM packets WHERE payload_type = 5 AND decrypted IS NOT TRUE AND channel_hash = ANY($1::bytea[]) ` type ListUndecryptedGroupTextPacketsByHashRow struct { PacketHash []byte `json:"packet_hash"` RawPayload []byte `json:"raw_payload"` } // Like ListUndecryptedGroupTextPackets, limited to channels that just gained a key. func (q *Queries) ListUndecryptedGroupTextPacketsByHash(ctx context.Context, hashes [][]byte) ([]ListUndecryptedGroupTextPacketsByHashRow, error) { rows, err := q.db.Query(ctx, listUndecryptedGroupTextPacketsByHash, hashes) if err != nil { return nil, err } defer rows.Close() items := []ListUndecryptedGroupTextPacketsByHashRow{} for rows.Next() { var i ListUndecryptedGroupTextPacketsByHashRow if err := rows.Scan(&i.PacketHash, &i.RawPayload); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listZoneBoundaries = `-- name: ListZoneBoundaries :many SELECT iata, url, feature, etag, checked_at, attempted_at, next_attempt, last_error FROM meshmapper_zone_boundaries ORDER BY iata ` func (q *Queries) ListZoneBoundaries(ctx context.Context) ([]MeshmapperZoneBoundary, error) { rows, err := q.db.Query(ctx, listZoneBoundaries) if err != nil { return nil, err } defer rows.Close() items := []MeshmapperZoneBoundary{} for rows.Next() { var i MeshmapperZoneBoundary if err := rows.Scan( &i.Iata, &i.Url, &i.Feature, &i.Etag, &i.CheckedAt, &i.AttemptedAt, &i.NextAttempt, &i.LastError, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listZoneLists = `-- name: ListZoneLists :many SELECT country, payload, etag, fetched_at, attempted_at, next_attempt, last_error FROM meshmapper_zone_lists ORDER BY country ` func (q *Queries) ListZoneLists(ctx context.Context) ([]MeshmapperZoneList, error) { rows, err := q.db.Query(ctx, listZoneLists) if err != nil { return nil, err } defer rows.Close() items := []MeshmapperZoneList{} for rows.Next() { var i MeshmapperZoneList if err := rows.Scan( &i.Country, &i.Payload, &i.Etag, &i.FetchedAt, &i.AttemptedAt, &i.NextAttempt, &i.LastError, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const pruneImportedRegions = `-- name: PruneImportedRegions :many DELETE FROM regions WHERE imported AND NOT (slug = ANY($1::text[])) RETURNING slug ` func (q *Queries) PruneImportedRegions(ctx context.Context, keep []string) ([]string, error) { rows, err := q.db.Query(ctx, pruneImportedRegions, keep) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var slug string if err := rows.Scan(&slug); err != nil { return nil, err } items = append(items, slug) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const pruneZoneBoundaries = `-- name: PruneZoneBoundaries :many DELETE FROM meshmapper_zone_boundaries WHERE NOT (iata = ANY($1::text[])) RETURNING iata ` // Drops imports for IATAs no longer configured, so their manual border returns. func (q *Queries) PruneZoneBoundaries(ctx context.Context, keep []string) ([]string, error) { rows, err := q.db.Query(ctx, pruneZoneBoundaries, keep) if err != nil { return nil, err } defer rows.Close() items := []string{} for rows.Next() { var iata string if err := rows.Scan(&iata); err != nil { return nil, err } items = append(items, iata) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const reconfirmNeighbors = `-- name: ReconfirmNeighbors :exec DELETE FROM node_neighbors nn WHERE NOT EXISTS ( SELECT 1 FROM node_short_ids ns WHERE ns.node_id = nn.neighbor_id AND ns.iata = nn.iata ) OR ( SELECT COUNT(*) FROM node_short_ids ns WHERE ns.iata = nn.iata AND ns.prefix_4 = ( SELECT prefix_4 FROM node_short_ids WHERE node_id = nn.neighbor_id AND iata = nn.iata ) ) > 1 ` // Delete node_neighbors where the neighbor has departed from node_short_ids // for that IATA, or where its prefix_4 is now ambiguous. func (q *Queries) ReconfirmNeighbors(ctx context.Context) error { _, err := q.db.Exec(ctx, reconfirmNeighbors) return err } const reconfirmRoutes = `-- name: ReconfirmRoutes :one WITH batch AS MATERIALIZED ( SELECT r.iata, r.path_key, r.node_ids, r.hash_prefix FROM known_routes r WHERE r.last_reconfirmed_at < $1 ORDER BY r.last_reconfirmed_at LIMIT $2 FOR UPDATE OF r SKIP LOCKED ), amb AS ( SELECT a.iata::char(3) AS iata, a.len, a.p FROM ROWS FROM (unnest($3::text[]), unnest($4::int[]), unnest($5::bytea[])) AS a(iata, len, p) ), dead AS ( SELECT b.iata, b.path_key FROM batch b WHERE EXISTS ( SELECT 1 FROM unnest(b.node_ids) AS hop_node_id WHERE NOT EXISTS ( SELECT 1 FROM node_short_ids ns WHERE ns.node_id = hop_node_id AND ns.iata = b.iata ) ) UNION SELECT DISTINCT b.iata, b.path_key FROM batch b CROSS JOIN LATERAL unnest(b.hash_prefix) AS hp JOIN amb a ON a.iata = b.iata AND a.len = length(hp) AND a.p = hp ), deleted AS ( DELETE FROM known_routes kr USING dead d WHERE kr.iata = d.iata AND kr.path_key = d.path_key ), updated AS ( UPDATE known_routes kr SET last_reconfirmed_at = GREATEST(NOW(), $1::timestamptz) FROM batch b WHERE kr.iata = b.iata AND kr.path_key = b.path_key AND NOT EXISTS ( SELECT 1 FROM dead d WHERE d.iata = b.iata AND d.path_key = b.path_key ) ) SELECT count(*) FROM batch ` type ReconfirmRoutesParams struct { Before pgtype.Timestamptz `json:"before"` BatchSize int32 `json:"batch_size"` AmbIata []string `json:"amb_iata"` AmbLen []int32 `json:"amb_len"` AmbPrefix [][]byte `json:"amb_prefix"` } // Checks one batch of least-recently-reconfirmed routes: deletes those with a departed // hop node or a hop prefix now matching >1 node in that IATA (length-aware: // 1/2/3/4-byte hop prefixes check prefix_1/2/3/4; ambiguity set supplied by AmbiguousPrefixes), // and stamps the survivors. func (q *Queries) ReconfirmRoutes(ctx context.Context, arg ReconfirmRoutesParams) (int64, error) { row := q.db.QueryRow(ctx, reconfirmRoutes, arg.Before, arg.BatchSize, arg.AmbIata, arg.AmbLen, arg.AmbPrefix, ) var count int64 err := row.Scan(&count) return count, err } const recordTrace = `-- name: RecordTrace :exec WITH iata AS ( INSERT INTO trace_iatas (trace_tag, iata, last_heard) VALUES ($1, $3, $2) ON CONFLICT (trace_tag, iata) DO UPDATE SET last_heard = EXCLUDED.last_heard WHERE EXCLUDED.last_heard > trace_iatas.last_heard + INTERVAL '1 hour' ) INSERT INTO trace_tags (trace_tag, first_heard_at, last_heard_at, heard) VALUES ($1, $2, $2, true) ON CONFLICT (trace_tag) DO UPDATE SET first_heard_at = CASE WHEN trace_tags.heard THEN LEAST(trace_tags.first_heard_at, EXCLUDED.first_heard_at) ELSE EXCLUDED.first_heard_at END, last_heard_at = CASE WHEN trace_tags.heard THEN GREATEST(trace_tags.last_heard_at, EXCLUDED.last_heard_at) ELSE EXCLUDED.last_heard_at END, heard = true ` type RecordTraceParams struct { TraceTag []byte `json:"trace_tag"` HeardAt pgtype.Timestamptz `json:"heard_at"` Iata string `json:"iata"` } // One hearing of a TRACE packet: trace_iatas refreshes at most hourly and the tag's heard // window widens (the first hearing replaces the packet's provisional times). Packet counts // and payloads come from UpsertPacket. func (q *Queries) RecordTrace(ctx context.Context, arg RecordTraceParams) error { _, err := q.db.Exec(ctx, recordTrace, arg.TraceTag, arg.HeardAt, arg.Iata) return err } const refreshRadioPresets = `-- name: RefreshRadioPresets :exec REFRESH MATERIALIZED VIEW CONCURRENTLY mv_radio_presets ` func (q *Queries) RefreshRadioPresets(ctx context.Context) error { _, err := q.db.Exec(ctx, refreshRadioPresets) return err } const resolveEndpointHashPairs = `-- name: ResolveEndpointHashPairs :many SELECT ns.iata, ns.prefix_1 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns CROSS JOIN LATERAL ( SELECT id, name, latitude, longitude, public_key FROM nodes WHERE id = ns.node_id LIMIT 1 ) n WHERE ns.iata = ANY($1::bpchar[]) AND ns.prefix_1 = ANY($2::bytea[]) ` type ResolveEndpointHashPairsParams struct { Iatas []string `json:"iatas"` Hashes [][]byte `json:"hashes"` } type ResolveEndpointHashPairsRow struct { Iata string `json:"iata"` Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } // Batch form of ResolveEndpointHashes for a page of packets. Matches the cross // product of IATAs and hashes; callers pick out the pairs they asked for. func (q *Queries) ResolveEndpointHashPairs(ctx context.Context, arg ResolveEndpointHashPairsParams) ([]ResolveEndpointHashPairsRow, error) { rows, err := q.db.Query(ctx, resolveEndpointHashPairs, arg.Iatas, arg.Hashes) if err != nil { return nil, err } defer rows.Close() items := []ResolveEndpointHashPairsRow{} for rows.Next() { var i ResolveEndpointHashPairsRow if err := rows.Scan( &i.Iata, &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolveEndpointHashes = `-- name: ResolveEndpointHashes :many SELECT ns.prefix_1 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns CROSS JOIN LATERAL ( SELECT id, name, latitude, longitude, public_key FROM nodes WHERE id = ns.node_id LIMIT 1 ) n WHERE ns.iata = $1 AND ns.prefix_1 = ANY($2::bytea[]) ` type ResolveEndpointHashesParams struct { Iata string `json:"iata"` Column2 [][]byte `json:"column_2"` } type ResolveEndpointHashesRow struct { Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } // Logical endpoints can be any advertised role, unlike intermediate relay hops. // Endpoint hashes are always one byte; use the existing (iata, prefix_1) index. // LIMIT 1 keeps generic plans on a node PK lookup per candidate instead of // flattening the join into a scan of all nodes. The PK already guarantees one row. func (q *Queries) ResolveEndpointHashes(ctx context.Context, arg ResolveEndpointHashesParams) ([]ResolveEndpointHashesRow, error) { rows, err := q.db.Query(ctx, resolveEndpointHashes, arg.Iata, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []ResolveEndpointHashesRow{} for rows.Next() { var i ResolveEndpointHashesRow if err := rows.Scan( &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolvePathHashesP1 = `-- name: ResolvePathHashesP1 :many SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = $1 AND n.node_type IN (2, 3) AND ns.prefix_1 = ANY($2::bytea[]) ` type ResolvePathHashesP1Params struct { Iata string `json:"iata"` Column2 [][]byte `json:"column_2"` } type ResolvePathHashesP1Row struct { Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } // ============================================================ // HELPERS // ============================================================ // Path hash resolution is split per prefix width so each query gets a // cacheable generic plan on its (iata, prefix_N) index; a single CASE // predicate forced a fresh custom plan on every call. func (q *Queries) ResolvePathHashesP1(ctx context.Context, arg ResolvePathHashesP1Params) ([]ResolvePathHashesP1Row, error) { rows, err := q.db.Query(ctx, resolvePathHashesP1, arg.Iata, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []ResolvePathHashesP1Row{} for rows.Next() { var i ResolvePathHashesP1Row if err := rows.Scan( &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolvePathHashesP2 = `-- name: ResolvePathHashesP2 :many SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = $1 AND n.node_type IN (2, 3) AND ns.prefix_2 = ANY($2::bytea[]) ` type ResolvePathHashesP2Params struct { Iata string `json:"iata"` Column2 [][]byte `json:"column_2"` } type ResolvePathHashesP2Row struct { Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } func (q *Queries) ResolvePathHashesP2(ctx context.Context, arg ResolvePathHashesP2Params) ([]ResolvePathHashesP2Row, error) { rows, err := q.db.Query(ctx, resolvePathHashesP2, arg.Iata, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []ResolvePathHashesP2Row{} for rows.Next() { var i ResolvePathHashesP2Row if err := rows.Scan( &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolvePathHashesP3 = `-- name: ResolvePathHashesP3 :many SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = $1 AND n.node_type IN (2, 3) AND ns.prefix_3 = ANY($2::bytea[]) ` type ResolvePathHashesP3Params struct { Iata string `json:"iata"` Column2 [][]byte `json:"column_2"` } type ResolvePathHashesP3Row struct { Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } func (q *Queries) ResolvePathHashesP3(ctx context.Context, arg ResolvePathHashesP3Params) ([]ResolvePathHashesP3Row, error) { rows, err := q.db.Query(ctx, resolvePathHashesP3, arg.Iata, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []ResolvePathHashesP3Row{} for rows.Next() { var i ResolvePathHashesP3Row if err := rows.Scan( &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolvePathHashesP4 = `-- name: ResolvePathHashesP4 :many SELECT ns.prefix_4 AS hash, n.id AS node_id, n.name, n.latitude, n.longitude, n.public_key FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = $1 AND n.node_type IN (2, 3) AND ns.prefix_4 = ANY($2::bytea[]) ` type ResolvePathHashesP4Params struct { Iata string `json:"iata"` Column2 [][]byte `json:"column_2"` } type ResolvePathHashesP4Row struct { Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` PublicKey []byte `json:"public_key"` } func (q *Queries) ResolvePathHashesP4(ctx context.Context, arg ResolvePathHashesP4Params) ([]ResolvePathHashesP4Row, error) { rows, err := q.db.Query(ctx, resolvePathHashesP4, arg.Iata, arg.Column2) if err != nil { return nil, err } defer rows.Close() items := []ResolvePathHashesP4Row{} for rows.Next() { var i ResolvePathHashesP4Row if err := rows.Scan( &i.Hash, &i.NodeID, &i.Name, &i.Latitude, &i.Longitude, &i.PublicKey, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolveRelayHashPairsP1 = `-- name: ResolveRelayHashPairsP1 :many SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = ANY($1::bpchar[]) AND n.node_type IN (2, 3) AND ns.prefix_1 = ANY($2::bytea[]) ` type ResolveRelayHashPairsP1Params struct { Iatas []string `json:"iatas"` Hashes [][]byte `json:"hashes"` } type ResolveRelayHashPairsP1Row struct { Iata string `json:"iata"` Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` } // Batch form of ResolvePathHashesP1 across IATAs; callers check uniqueness per IATA. func (q *Queries) ResolveRelayHashPairsP1(ctx context.Context, arg ResolveRelayHashPairsP1Params) ([]ResolveRelayHashPairsP1Row, error) { rows, err := q.db.Query(ctx, resolveRelayHashPairsP1, arg.Iatas, arg.Hashes) if err != nil { return nil, err } defer rows.Close() items := []ResolveRelayHashPairsP1Row{} for rows.Next() { var i ResolveRelayHashPairsP1Row if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolveRelayHashPairsP2 = `-- name: ResolveRelayHashPairsP2 :many SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = ANY($1::bpchar[]) AND n.node_type IN (2, 3) AND ns.prefix_2 = ANY($2::bytea[]) ` type ResolveRelayHashPairsP2Params struct { Iatas []string `json:"iatas"` Hashes [][]byte `json:"hashes"` } type ResolveRelayHashPairsP2Row struct { Iata string `json:"iata"` Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` } // Batch form of ResolvePathHashesP2 across IATAs; callers check uniqueness per IATA. func (q *Queries) ResolveRelayHashPairsP2(ctx context.Context, arg ResolveRelayHashPairsP2Params) ([]ResolveRelayHashPairsP2Row, error) { rows, err := q.db.Query(ctx, resolveRelayHashPairsP2, arg.Iatas, arg.Hashes) if err != nil { return nil, err } defer rows.Close() items := []ResolveRelayHashPairsP2Row{} for rows.Next() { var i ResolveRelayHashPairsP2Row if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolveRelayHashPairsP3 = `-- name: ResolveRelayHashPairsP3 :many SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = ANY($1::bpchar[]) AND n.node_type IN (2, 3) AND ns.prefix_3 = ANY($2::bytea[]) ` type ResolveRelayHashPairsP3Params struct { Iatas []string `json:"iatas"` Hashes [][]byte `json:"hashes"` } type ResolveRelayHashPairsP3Row struct { Iata string `json:"iata"` Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` } // Batch form of ResolvePathHashesP3 across IATAs; callers check uniqueness per IATA. func (q *Queries) ResolveRelayHashPairsP3(ctx context.Context, arg ResolveRelayHashPairsP3Params) ([]ResolveRelayHashPairsP3Row, error) { rows, err := q.db.Query(ctx, resolveRelayHashPairsP3, arg.Iatas, arg.Hashes) if err != nil { return nil, err } defer rows.Close() items := []ResolveRelayHashPairsP3Row{} for rows.Next() { var i ResolveRelayHashPairsP3Row if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const resolveRelayHashPairsP4 = `-- name: ResolveRelayHashPairsP4 :many SELECT ns.iata, ns.prefix_4 AS hash, ns.node_id FROM node_short_ids ns JOIN nodes n ON n.id = ns.node_id WHERE ns.iata = ANY($1::bpchar[]) AND n.node_type IN (2, 3) AND ns.prefix_4 = ANY($2::bytea[]) ` type ResolveRelayHashPairsP4Params struct { Iatas []string `json:"iatas"` Hashes [][]byte `json:"hashes"` } type ResolveRelayHashPairsP4Row struct { Iata string `json:"iata"` Hash []byte `json:"hash"` NodeID uuid.UUID `json:"node_id"` } // Batch form of ResolvePathHashesP4 across IATAs; callers check uniqueness per IATA. func (q *Queries) ResolveRelayHashPairsP4(ctx context.Context, arg ResolveRelayHashPairsP4Params) ([]ResolveRelayHashPairsP4Row, error) { rows, err := q.db.Query(ctx, resolveRelayHashPairsP4, arg.Iatas, arg.Hashes) if err != nil { return nil, err } defer rows.Close() items := []ResolveRelayHashPairsP4Row{} for rows.Next() { var i ResolveRelayHashPairsP4Row if err := rows.Scan(&i.Iata, &i.Hash, &i.NodeID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const saveChannelCatalogue = `-- name: SaveChannelCatalogue :exec INSERT INTO meshmapper_channel_catalogues (iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error) VALUES ($1, $2, $3::jsonb, $4::text, $5::timestamptz, $6, $7, $8) ON CONFLICT (iata, url) DO UPDATE SET payload = COALESCE(EXCLUDED.payload, meshmapper_channel_catalogues.payload), etag = COALESCE(EXCLUDED.etag, meshmapper_channel_catalogues.etag), checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_channel_catalogues.checked_at), attempted_at = EXCLUDED.attempted_at, next_attempt = EXCLUDED.next_attempt, last_error = EXCLUDED.last_error ` type SaveChannelCatalogueParams struct { Iata string `json:"iata"` Url string `json:"url"` Payload []byte `json:"payload"` Etag *string `json:"etag"` CheckedAt pgtype.Timestamptz `json:"checked_at"` AttemptedAt pgtype.Timestamptz `json:"attempted_at"` NextAttempt pgtype.Timestamptz `json:"next_attempt"` LastError string `json:"last_error"` } // NULL payload/etag/checked_at retain the last good list after an error or 304. func (q *Queries) SaveChannelCatalogue(ctx context.Context, arg SaveChannelCatalogueParams) error { _, err := q.db.Exec(ctx, saveChannelCatalogue, arg.Iata, arg.Url, arg.Payload, arg.Etag, arg.CheckedAt, arg.AttemptedAt, arg.NextAttempt, arg.LastError, ) return err } const saveScopeCatalogue = `-- name: SaveScopeCatalogue :exec WITH inserted AS ( INSERT INTO transport_scopes (name, transport_key, key_fingerprint, imported_only) SELECT entry.name, entry.key, entry.fingerprint, TRUE FROM (SELECT unnest($9::text[]) AS name, unnest($10::bytea[]) AS key, unnest($11::bytea[]) AS fingerprint) AS entry ON CONFLICT (name) DO NOTHING ) INSERT INTO meshmapper_scope_catalogues (iata, url, payload, etag, checked_at, attempted_at, next_attempt, last_error) VALUES ($1, $2, $3::jsonb, $4::text, $5::timestamptz, $6, $7, $8) ON CONFLICT (iata, url) DO UPDATE SET payload = COALESCE(EXCLUDED.payload, meshmapper_scope_catalogues.payload), etag = COALESCE(EXCLUDED.etag, meshmapper_scope_catalogues.etag), checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_scope_catalogues.checked_at), attempted_at = EXCLUDED.attempted_at, next_attempt = EXCLUDED.next_attempt, last_error = EXCLUDED.last_error ` type SaveScopeCatalogueParams struct { Iata string `json:"iata"` Url string `json:"url"` Payload []byte `json:"payload"` Etag *string `json:"etag"` CheckedAt pgtype.Timestamptz `json:"checked_at"` AttemptedAt pgtype.Timestamptz `json:"attempted_at"` NextAttempt pgtype.Timestamptz `json:"next_attempt"` LastError string `json:"last_error"` Names []string `json:"names"` Keys [][]byte `json:"keys"` Fingerprints [][]byte `json:"fingerprints"` } // One statement commits the validated snapshot and its lookup identities together. // Empty arrays insert nothing. NULL payload/checked_at retain last-known-good data // after an error or 304. Imported names never replace existing manual metadata. func (q *Queries) SaveScopeCatalogue(ctx context.Context, arg SaveScopeCatalogueParams) error { _, err := q.db.Exec(ctx, saveScopeCatalogue, arg.Iata, arg.Url, arg.Payload, arg.Etag, arg.CheckedAt, arg.AttemptedAt, arg.NextAttempt, arg.LastError, arg.Names, arg.Keys, arg.Fingerprints, ) return err } const saveZoneBoundary = `-- name: SaveZoneBoundary :exec INSERT INTO meshmapper_zone_boundaries (iata, url, feature, etag, checked_at, attempted_at, next_attempt, last_error) VALUES ($1, $2, $3::jsonb, $4::text, $5::timestamptz, $6, $7, $8) ON CONFLICT (iata) DO UPDATE SET url = EXCLUDED.url, feature = COALESCE(EXCLUDED.feature, meshmapper_zone_boundaries.feature), etag = COALESCE(EXCLUDED.etag, meshmapper_zone_boundaries.etag), checked_at = COALESCE(EXCLUDED.checked_at, meshmapper_zone_boundaries.checked_at), attempted_at = EXCLUDED.attempted_at, next_attempt = EXCLUDED.next_attempt, last_error = EXCLUDED.last_error ` type SaveZoneBoundaryParams struct { Iata string `json:"iata"` Url string `json:"url"` Feature []byte `json:"feature"` Etag *string `json:"etag"` CheckedAt pgtype.Timestamptz `json:"checked_at"` AttemptedAt pgtype.Timestamptz `json:"attempted_at"` NextAttempt pgtype.Timestamptz `json:"next_attempt"` LastError string `json:"last_error"` } // NULL feature/etag/checked_at retain the last good boundary after an error or 304. func (q *Queries) SaveZoneBoundary(ctx context.Context, arg SaveZoneBoundaryParams) error { _, err := q.db.Exec(ctx, saveZoneBoundary, arg.Iata, arg.Url, arg.Feature, arg.Etag, arg.CheckedAt, arg.AttemptedAt, arg.NextAttempt, arg.LastError, ) return err } const saveZoneList = `-- name: SaveZoneList :exec INSERT INTO meshmapper_zone_lists (country, payload, etag, fetched_at, attempted_at, next_attempt, last_error) VALUES ($1, $2::jsonb, $3::text, $4::timestamptz, $5, $6, $7) ON CONFLICT (country) DO UPDATE SET payload = COALESCE(EXCLUDED.payload, meshmapper_zone_lists.payload), etag = COALESCE(EXCLUDED.etag, meshmapper_zone_lists.etag), fetched_at = COALESCE(EXCLUDED.fetched_at, meshmapper_zone_lists.fetched_at), attempted_at = EXCLUDED.attempted_at, next_attempt = EXCLUDED.next_attempt, last_error = EXCLUDED.last_error ` type SaveZoneListParams struct { Country string `json:"country"` Payload []byte `json:"payload"` Etag *string `json:"etag"` FetchedAt pgtype.Timestamptz `json:"fetched_at"` AttemptedAt pgtype.Timestamptz `json:"attempted_at"` NextAttempt pgtype.Timestamptz `json:"next_attempt"` LastError string `json:"last_error"` } // NULL payload/etag/fetched_at retain the last good list after an error or 304. func (q *Queries) SaveZoneList(ctx context.Context, arg SaveZoneListParams) error { _, err := q.db.Exec(ctx, saveZoneList, arg.Country, arg.Payload, arg.Etag, arg.FetchedAt, arg.AttemptedAt, arg.NextAttempt, arg.LastError, ) return err } const searchKnownRoutesByNodes = `-- name: SearchKnownRoutesByNodes :many SELECT id, node_ids, hash_prefix, iata, hop_count, first_seen, last_seen, observation_count FROM known_routes kr WHERE kr.node_ids && $1::uuid[] AND kr.node_ids && $2::uuid[] AND kr.iata = ANY($3::bpchar[]) ORDER BY hop_count ASC, last_seen DESC LIMIT $4 ` type SearchKnownRoutesByNodesParams struct { FromNodes []uuid.UUID `json:"from_nodes"` ToNodes []uuid.UUID `json:"to_nodes"` Iatas []string `json:"iatas"` RowLimit int32 `json:"row_limit"` } type SearchKnownRoutesByNodesRow struct { ID int64 `json:"id"` NodeIds []uuid.UUID `json:"node_ids"` HashPrefix [][]byte `json:"hash_prefix"` Iata string `json:"iata"` HopCount int32 `json:"hop_count"` FirstSeen pgtype.Timestamptz `json:"first_seen"` LastSeen pgtype.Timestamptz `json:"last_seen"` ObservationCount int64 `json:"observation_count"` } // Routes holding a source and a destination candidate; callers check order and IATA. // A route's hops resolve in its own IATA, so candidates from other IATAs can't match. // Both && predicates use the node_ids GIN index. func (q *Queries) SearchKnownRoutesByNodes(ctx context.Context, arg SearchKnownRoutesByNodesParams) ([]SearchKnownRoutesByNodesRow, error) { rows, err := q.db.Query(ctx, searchKnownRoutesByNodes, arg.FromNodes, arg.ToNodes, arg.Iatas, arg.RowLimit, ) if err != nil { return nil, err } defer rows.Close() items := []SearchKnownRoutesByNodesRow{} for rows.Next() { var i SearchKnownRoutesByNodesRow if err := rows.Scan( &i.ID, &i.NodeIds, &i.HashPrefix, &i.Iata, &i.HopCount, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const setNodeDefaultScope = `-- name: SetNodeDefaultScope :exec UPDATE nodes SET default_scope_id = $2 WHERE id = $1 ` type SetNodeDefaultScopeParams struct { ID uuid.UUID `json:"id"` DefaultScopeID *int32 `json:"default_scope_id"` } func (q *Queries) SetNodeDefaultScope(ctx context.Context, arg SetNodeDefaultScopeParams) error { _, err := q.db.Exec(ctx, setNodeDefaultScope, arg.ID, arg.DefaultScopeID) return err } const setNodeMultibytePaths = `-- name: SetNodeMultibytePaths :exec UPDATE nodes SET supports_multibyte_paths = TRUE WHERE id = $1 AND supports_multibyte_paths = FALSE ` func (q *Queries) SetNodeMultibytePaths(ctx context.Context, id uuid.UUID) error { _, err := q.db.Exec(ctx, setNodeMultibytePaths, id) return err } const setNodeMultibyteTraces = `-- name: SetNodeMultibyteTraces :exec UPDATE nodes SET supports_multibyte_traces = TRUE WHERE id = $1 AND supports_multibyte_traces = FALSE ` func (q *Queries) SetNodeMultibyteTraces(ctx context.Context, id uuid.UUID) error { _, err := q.db.Exec(ctx, setNodeMultibyteTraces, id) return err } const setPacketDecrypted = `-- name: SetPacketDecrypted :exec UPDATE packets SET decrypted = true WHERE packet_hash = $1 ` func (q *Queries) SetPacketDecrypted(ctx context.Context, packetHash []byte) error { _, err := q.db.Exec(ctx, setPacketDecrypted, packetHash) return err } const touchObserverBrokers = `-- name: TouchObserverBrokers :exec UPDATE observer_brokers ob SET last_seen = GREATEST(ob.last_seen, v.seen), last_packet_at = GREATEST(ob.last_packet_at, v.packet) FROM ( SELECT unnest($1::uuid[]) AS observer_id, unnest($2::text[]) AS broker_name, unnest($3::timestamptz[]) AS seen, unnest($4::timestamptz[]) AS packet ) v WHERE ob.observer_id = v.observer_id AND ob.broker_name = v.broker_name ` type TouchObserverBrokersParams struct { Column1 []uuid.UUID `json:"column_1"` Column2 []string `json:"column_2"` Column3 []pgtype.Timestamptz `json:"column_3"` Column4 []pgtype.Timestamptz `json:"column_4"` } func (q *Queries) TouchObserverBrokers(ctx context.Context, arg TouchObserverBrokersParams) error { _, err := q.db.Exec(ctx, touchObserverBrokers, arg.Column1, arg.Column2, arg.Column3, arg.Column4, ) return err } const touchObservers = `-- name: TouchObservers :exec UPDATE observers o SET last_seen = GREATEST(o.last_seen, v.seen), observation_count = COALESCE(o.observation_count, 0) + v.delta, last_iata = CASE WHEN v.iata <> '' AND (o.last_iata_at IS NULL OR v.iata_at > o.last_iata_at) THEN v.iata ELSE o.last_iata END, last_iata_at = GREATEST(o.last_iata_at, v.iata_at) FROM ( SELECT unnest($1::uuid[]) AS id, unnest($2::timestamptz[]) AS seen, unnest($3::int[]) AS delta, unnest($4::text[]) AS iata, unnest($5::timestamptz[]) AS iata_at ) v WHERE o.id = v.id ` type TouchObserversParams struct { Column1 []uuid.UUID `json:"column_1"` Column2 []pgtype.Timestamptz `json:"column_2"` Column3 []int32 `json:"column_3"` Column4 []string `json:"column_4"` Column5 []pgtype.Timestamptz `json:"column_5"` } // Batched flush of coalesced presence bumps. GREATEST keeps a late flush // from regressing a newer write-through (e.g. a status update). func (q *Queries) TouchObservers(ctx context.Context, arg TouchObserversParams) error { _, err := q.db.Exec(ctx, touchObservers, arg.Column1, arg.Column2, arg.Column3, arg.Column4, arg.Column5, ) return err } const touchPackets = `-- name: TouchPackets :exec UPDATE packets p SET last_heard_at = GREATEST(p.last_heard_at, v.heard) FROM ( SELECT unnest($1::bytea[]) AS packet_hash, unnest($2::timestamptz[]) AS heard ) v WHERE p.packet_hash = v.packet_hash ` type TouchPacketsParams struct { Column1 [][]byte `json:"column_1"` Column2 []pgtype.Timestamptz `json:"column_2"` } func (q *Queries) TouchPackets(ctx context.Context, arg TouchPacketsParams) error { _, err := q.db.Exec(ctx, touchPackets, arg.Column1, arg.Column2) return err } const updateObserverRegionScope = `-- name: UpdateObserverRegionScope :exec UPDATE observers SET region_scope = $2 WHERE id = $1 ` type UpdateObserverRegionScopeParams struct { ID uuid.UUID `json:"id"` RegionScope *string `json:"region_scope"` } // Records the observer's own OTA-reported region scope, from the "self" // field of a /neighbors report. Always known (not queried OTA), so this // unconditionally overwrites, unlike the neighbor-side region_scope. func (q *Queries) UpdateObserverRegionScope(ctx context.Context, arg UpdateObserverRegionScopeParams) error { _, err := q.db.Exec(ctx, updateObserverRegionScope, arg.ID, arg.RegionScope) return err } const updateObserverStatus = `-- name: UpdateObserverStatus :one UPDATE observers SET display_name = COALESCE(NULLIF($2, ''), display_name), observer_type = COALESCE(NULLIF($3, ''), observer_type), software_version = COALESCE($4, software_version), hardware_model = COALESCE($5, hardware_model), firmware_version = COALESCE($6, firmware_version), firmware_build = COALESCE($7, firmware_build), radio_freq_mhz = COALESCE($8, radio_freq_mhz), radio_sf = COALESCE($9, radio_sf), radio_bw_khz = COALESCE($10, radio_bw_khz), radio_cr = COALESCE($11, radio_cr), battery_level = COALESCE($12, battery_level), uptime_seconds = COALESCE($13, uptime_seconds), status_metadata = $14, last_status_at = NOW(), last_seen = NOW() WHERE public_key = $1 RETURNING id ` type UpdateObserverStatusParams struct { PublicKey []byte `json:"public_key"` Column2 interface{} `json:"column_2"` Column3 interface{} `json:"column_3"` SoftwareVersion *string `json:"software_version"` HardwareModel *string `json:"hardware_model"` FirmwareVersion *string `json:"firmware_version"` FirmwareBuild *string `json:"firmware_build"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioSf *int16 `json:"radio_sf"` RadioBwKhz *float32 `json:"radio_bw_khz"` RadioCr *int16 `json:"radio_cr"` BatteryLevel *float32 `json:"battery_level"` UptimeSeconds *int64 `json:"uptime_seconds"` StatusMetadata []byte `json:"status_metadata"` } func (q *Queries) UpdateObserverStatus(ctx context.Context, arg UpdateObserverStatusParams) (uuid.UUID, error) { row := q.db.QueryRow(ctx, updateObserverStatus, arg.PublicKey, arg.Column2, arg.Column3, arg.SoftwareVersion, arg.HardwareModel, arg.FirmwareVersion, arg.FirmwareBuild, arg.RadioFreqMhz, arg.RadioSf, arg.RadioBwKhz, arg.RadioCr, arg.BatteryLevel, arg.UptimeSeconds, arg.StatusMetadata, ) var id uuid.UUID err := row.Scan(&id) return id, err } const upsertChannel = `-- name: UpsertChannel :one INSERT INTO channels (channel_hash, key_fingerprint, name, hashtag, is_hashtag, key_known, last_seen) VALUES ($1, $2::bytea, $3, $4, $5, ($2 IS NOT NULL), NOW()) ON CONFLICT (channel_hash, key_fingerprint) DO UPDATE SET last_seen = NOW(), name = COALESCE(EXCLUDED.name, channels.name) RETURNING id, channel_hash, key_fingerprint, name, hashtag, is_hashtag, is_public, key_known, first_seen, last_seen, message_count ` type UpsertChannelParams struct { ChannelHash []byte `json:"channel_hash"` Column2 []byte `json:"column_2"` Name *string `json:"name"` Hashtag *string `json:"hashtag"` IsHashtag *bool `json:"is_hashtag"` } // ============================================================ // CHANNELS // ============================================================ // Upsert a channel by (hash, key_fingerprint). Pass NULL fingerprint for // hash-only records (key unknown). Returns the channel row. func (q *Queries) UpsertChannel(ctx context.Context, arg UpsertChannelParams) (Channel, error) { row := q.db.QueryRow(ctx, upsertChannel, arg.ChannelHash, arg.Column2, arg.Name, arg.Hashtag, arg.IsHashtag, ) var i Channel err := row.Scan( &i.ID, &i.ChannelHash, &i.KeyFingerprint, &i.Name, &i.Hashtag, &i.IsHashtag, &i.IsPublic, &i.KeyKnown, &i.FirstSeen, &i.LastSeen, &i.MessageCount, ) return i, err } const upsertChannelHashOnly = `-- name: UpsertChannelHashOnly :one INSERT INTO channels (channel_hash, last_seen) VALUES ($1, NOW()) ON CONFLICT (channel_hash) WHERE key_fingerprint IS NULL DO UPDATE SET last_seen = NOW() RETURNING id ` func (q *Queries) UpsertChannelHashOnly(ctx context.Context, channelHash []byte) (int32, error) { row := q.db.QueryRow(ctx, upsertChannelHashOnly, channelHash) var id int32 err := row.Scan(&id) return id, err } const upsertChannelIATA = `-- name: UpsertChannelIATA :exec INSERT INTO channel_iatas (channel_hash, iata, last_heard) VALUES ($1, $2, $3) ON CONFLICT (channel_hash, iata) DO UPDATE SET last_heard = EXCLUDED.last_heard WHERE EXCLUDED.last_heard > channel_iatas.last_heard + INTERVAL '1 hour' ` type UpsertChannelIATAParams struct { ChannelHash []byte `json:"channel_hash"` Iata string `json:"iata"` LastHeard pgtype.Timestamptz `json:"last_heard"` } // Refreshes at most hourly so repeat hears don't churn the row. func (q *Queries) UpsertChannelIATA(ctx context.Context, arg UpsertChannelIATAParams) error { _, err := q.db.Exec(ctx, upsertChannelIATA, arg.ChannelHash, arg.Iata, arg.LastHeard) return err } const upsertIATA = `-- name: UpsertIATA :exec INSERT INTO iata_codes (iata) VALUES ($1) ON CONFLICT (iata) DO NOTHING ` // Copyright 2026 Beacon Contributors // SPDX-License-Identifier: agpl // ============================================================ // IATA CODES // ============================================================ func (q *Queries) UpsertIATA(ctx context.Context, iata string) error { _, err := q.db.Exec(ctx, upsertIATA, iata) return err } const upsertIATABorder = `-- name: UpsertIATABorder :exec INSERT INTO iata_codes (iata, border) VALUES ($1, $2) ON CONFLICT (iata) DO UPDATE SET border = EXCLUDED.border ` type UpsertIATABorderParams struct { Iata string `json:"iata"` Border []byte `json:"border"` } // Written by the config-file-driven seeder (internal/config/seed.go), not a // runtime HTTP path. border is a full, pre-validated GeoJSON Feature with // bbox already computed -- see internal/config/border.go. func (q *Queries) UpsertIATABorder(ctx context.Context, arg UpsertIATABorderParams) error { _, err := q.db.Exec(ctx, upsertIATABorder, arg.Iata, arg.Border) return err } const upsertIATADetails = `-- name: UpsertIATADetails :exec INSERT INTO iata_codes (iata, display_name, approx_lat, approx_lng) VALUES ($1, $2, $3, $4) ON CONFLICT (iata) DO UPDATE SET display_name = EXCLUDED.display_name, approx_lat = EXCLUDED.approx_lat, approx_lng = EXCLUDED.approx_lng ` type UpsertIATADetailsParams struct { Iata string `json:"iata"` DisplayName *string `json:"display_name"` ApproxLat *float64 `json:"approx_lat"` ApproxLng *float64 `json:"approx_lng"` } func (q *Queries) UpsertIATADetails(ctx context.Context, arg UpsertIATADetailsParams) error { _, err := q.db.Exec(ctx, upsertIATADetails, arg.Iata, arg.DisplayName, arg.ApproxLat, arg.ApproxLng, ) return err } const upsertImportedRegion = `-- name: UpsertImportedRegion :one INSERT INTO regions (slug, name, display_order, center_lat, center_lng, zoom_level, imported, updated_at) VALUES ($1, $2, $3, $4, $5, NULL, TRUE, NOW()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, display_order = EXCLUDED.display_order, center_lat = EXCLUDED.center_lat, center_lng = EXCLUDED.center_lng, updated_at = NOW() WHERE regions.imported RETURNING id ` type UpsertImportedRegionParams struct { Slug string `json:"slug"` Name string `json:"name"` DisplayOrder *int32 `json:"display_order"` CenterLat *float64 `json:"center_lat"` CenterLng *float64 `json:"center_lng"` } // A hand-written region owns its slug: the WHERE turns a clash into no row. func (q *Queries) UpsertImportedRegion(ctx context.Context, arg UpsertImportedRegionParams) (int32, error) { row := q.db.QueryRow(ctx, upsertImportedRegion, arg.Slug, arg.Name, arg.DisplayOrder, arg.CenterLat, arg.CenterLng, ) var id int32 err := row.Scan(&id) return id, err } const upsertKnownRoute = `-- name: UpsertKnownRoute :exec INSERT INTO known_routes (path_key, node_ids, hash_prefix, iata, hop_count) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (iata, path_key) DO UPDATE SET hash_prefix = EXCLUDED.hash_prefix, last_seen = NOW(), observation_count = known_routes.observation_count + 1 ` type UpsertKnownRouteParams struct { PathKey []byte `json:"path_key"` NodeIds []uuid.UUID `json:"node_ids"` HashPrefix [][]byte `json:"hash_prefix"` Iata string `json:"iata"` HopCount int32 `json:"hop_count"` } // ============================================================ // ROUTES // ============================================================ // Route identity is path_key, an md5 of node_ids computed by the caller. // On conflict, observation_count and last_seen are bumped and hash_prefix follows the // latest hearing, so evidence matches the hash width the route uses now. func (q *Queries) UpsertKnownRoute(ctx context.Context, arg UpsertKnownRouteParams) error { _, err := q.db.Exec(ctx, upsertKnownRoute, arg.PathKey, arg.NodeIds, arg.HashPrefix, arg.Iata, arg.HopCount, ) return err } const upsertNode = `-- name: UpsertNode :one INSERT INTO nodes (public_key, node_type, name, latitude, longitude, location_source, last_advert_at, last_seen, radio_freq_mhz, radio_sf, radio_bw_khz, device_clock_drift_seconds) VALUES ($1, $2, $3, $4, $5, CASE WHEN $4::double precision IS NOT NULL THEN 'advert' END, NOW(), NOW(), $6, $7, $8, $9) ON CONFLICT (public_key) DO UPDATE SET node_type = EXCLUDED.node_type, name = COALESCE(EXCLUDED.name, nodes.name), latitude = CASE WHEN $10::bool THEN NULL ELSE COALESCE(EXCLUDED.latitude, nodes.latitude) END, longitude = CASE WHEN $10::bool THEN NULL ELSE COALESCE(EXCLUDED.longitude, nodes.longitude) END, location_source = CASE WHEN $10::bool THEN NULL WHEN EXCLUDED.latitude IS NOT NULL THEN 'advert' ELSE nodes.location_source END, last_advert_at = NOW(), last_seen = NOW(), radio_freq_mhz = EXCLUDED.radio_freq_mhz, radio_sf = EXCLUDED.radio_sf, radio_bw_khz = EXCLUDED.radio_bw_khz, device_clock_drift_seconds = EXCLUDED.device_clock_drift_seconds RETURNING id, public_key, node_type, name, latitude, longitude, location_source, last_advert_at, supports_multibyte_paths, supports_multibyte_traces, default_scope_id, min_firmware_version, first_seen, last_seen, radio_freq_mhz, radio_sf, radio_bw_khz, metadata, device_clock_drift_seconds ` type UpsertNodeParams struct { PublicKey []byte `json:"public_key"` NodeType int16 `json:"node_type"` Name *string `json:"name"` Latitude *float64 `json:"latitude"` Longitude *float64 `json:"longitude"` RadioFreqMhz *float32 `json:"radio_freq_mhz"` RadioSf *int16 `json:"radio_sf"` RadioBwKhz *float32 `json:"radio_bw_khz"` DeviceClockDriftSeconds *int32 `json:"device_clock_drift_seconds"` ClearLocation bool `json:"clear_location"` } // ============================================================ // NODES // ============================================================ func (q *Queries) UpsertNode(ctx context.Context, arg UpsertNodeParams) (Node, error) { row := q.db.QueryRow(ctx, upsertNode, arg.PublicKey, arg.NodeType, arg.Name, arg.Latitude, arg.Longitude, arg.RadioFreqMhz, arg.RadioSf, arg.RadioBwKhz, arg.DeviceClockDriftSeconds, arg.ClearLocation, ) var i Node err := row.Scan( &i.ID, &i.PublicKey, &i.NodeType, &i.Name, &i.Latitude, &i.Longitude, &i.LocationSource, &i.LastAdvertAt, &i.SupportsMultibytePaths, &i.SupportsMultibyteTraces, &i.DefaultScopeID, &i.MinFirmwareVersion, &i.FirstSeen, &i.LastSeen, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.Metadata, &i.DeviceClockDriftSeconds, ) return i, err } const upsertNodeIATA = `-- name: UpsertNodeIATA :exec INSERT INTO node_iatas (node_id, iata, last_heard, observation_count) VALUES ($1, $2, NOW(), 1) ON CONFLICT (node_id, iata) DO UPDATE SET last_heard = NOW(), observation_count = node_iatas.observation_count + 1 ` type UpsertNodeIATAParams struct { NodeID uuid.UUID `json:"node_id"` Iata string `json:"iata"` } // ============================================================ // NODE IATAS // ============================================================ func (q *Queries) UpsertNodeIATA(ctx context.Context, arg UpsertNodeIATAParams) error { _, err := q.db.Exec(ctx, upsertNodeIATA, arg.NodeID, arg.Iata) return err } const upsertNodeNeighbor = `-- name: UpsertNodeNeighbor :exec INSERT INTO node_neighbors (node_id, neighbor_id, iata, observation_count, snr, region_scope) VALUES ($1, $2, $3, 1, $4, $5) ON CONFLICT (node_id, neighbor_id, iata) DO UPDATE SET last_seen = NOW(), observation_count = node_neighbors.observation_count + 1, snr = COALESCE(EXCLUDED.snr, node_neighbors.snr), region_scope = COALESCE(EXCLUDED.region_scope, node_neighbors.region_scope) ` type UpsertNodeNeighborParams struct { NodeID uuid.UUID `json:"node_id"` NeighborID uuid.UUID `json:"neighbor_id"` Iata string `json:"iata"` Snr *float32 `json:"snr"` RegionScope *string `json:"region_scope"` } // ============================================================ // NEIGHBORS // ============================================================ // Records or updates a neighbor relationship between two nodes observed in the same IATA. // node_id is the advertising node, neighbor_id is the first-hop forwarder. // snr is optional; pass NULL when no signal reading is available (the // common case). regionScope is optional too; pass NULL whenever the OTA // scope query for this neighbor didn't succeed (status != "responded"), // so a failed/timed-out query doesn't erase a previously known scope. // On conflict, snr and region_scope are only overwritten when a new // non-null value is supplied. func (q *Queries) UpsertNodeNeighbor(ctx context.Context, arg UpsertNodeNeighborParams) error { _, err := q.db.Exec(ctx, upsertNodeNeighbor, arg.NodeID, arg.NeighborID, arg.Iata, arg.Snr, arg.RegionScope, ) return err } const upsertNodeShortID = `-- name: UpsertNodeShortID :exec INSERT INTO node_short_ids (node_id, iata, prefix_4) VALUES ($1, $2, $3) ON CONFLICT (node_id, iata) DO NOTHING ` type UpsertNodeShortIDParams struct { NodeID uuid.UUID `json:"node_id"` Iata string `json:"iata"` Prefix4 []byte `json:"prefix_4"` } func (q *Queries) UpsertNodeShortID(ctx context.Context, arg UpsertNodeShortIDParams) error { _, err := q.db.Exec(ctx, upsertNodeShortID, arg.NodeID, arg.Iata, arg.Prefix4) return err } const upsertObserver = `-- name: UpsertObserver :one INSERT INTO observers (public_key, observer_type, last_seen, last_iata, last_iata_at) VALUES ($1, 'unknown', NOW(), NULLIF($2::text, ''), CASE WHEN $2::text <> '' THEN $3::timestamptz END) ON CONFLICT (public_key) DO UPDATE SET last_seen = NOW(), observation_count = observers.observation_count + 1, last_iata = CASE WHEN EXCLUDED.last_iata IS NOT NULL AND (observers.last_iata_at IS NULL OR EXCLUDED.last_iata_at > observers.last_iata_at) THEN EXCLUDED.last_iata ELSE observers.last_iata END, last_iata_at = GREATEST(observers.last_iata_at, EXCLUDED.last_iata_at) RETURNING id, public_key, display_name, observer_type, software_version, hardware_model, firmware_version, firmware_build, radio_freq_mhz, radio_sf, radio_bw_khz, radio_cr, battery_level, uptime_seconds, status_metadata, last_status_at, first_seen, last_seen, observation_count, metadata, region_scope, last_iata, last_iata_at ` type UpsertObserverParams struct { PublicKey []byte `json:"public_key"` Iata string `json:"iata"` IataAt pgtype.Timestamptz `json:"iata_at"` } // ============================================================ // OBSERVERS // ============================================================ // Empty iata (status/neighbors) leaves last_iata alone; otherwise the newest iata_at wins. func (q *Queries) UpsertObserver(ctx context.Context, arg UpsertObserverParams) (Observer, error) { row := q.db.QueryRow(ctx, upsertObserver, arg.PublicKey, arg.Iata, arg.IataAt) var i Observer err := row.Scan( &i.ID, &i.PublicKey, &i.DisplayName, &i.ObserverType, &i.SoftwareVersion, &i.HardwareModel, &i.FirmwareVersion, &i.FirmwareBuild, &i.RadioFreqMhz, &i.RadioSf, &i.RadioBwKhz, &i.RadioCr, &i.BatteryLevel, &i.UptimeSeconds, &i.StatusMetadata, &i.LastStatusAt, &i.FirstSeen, &i.LastSeen, &i.ObservationCount, &i.Metadata, &i.RegionScope, &i.LastIata, &i.LastIataAt, ) return i, err } const upsertObserverBroker = `-- name: UpsertObserverBroker :exec INSERT INTO observer_brokers (observer_id, broker_name, last_seen, last_packet_at) VALUES ($1, $2, NOW(), CASE WHEN $3::boolean THEN NOW() END) ON CONFLICT (observer_id, broker_name) DO UPDATE SET last_seen = NOW(), last_packet_at = COALESCE(EXCLUDED.last_packet_at, observer_brokers.last_packet_at) ` type UpsertObserverBrokerParams struct { ObserverID uuid.UUID `json:"observer_id"` BrokerName string `json:"broker_name"` IsPacket bool `json:"is_packet"` } // ============================================================ // OBSERVER BROKERS // ============================================================ func (q *Queries) UpsertObserverBroker(ctx context.Context, arg UpsertObserverBrokerParams) error { _, err := q.db.Exec(ctx, upsertObserverBroker, arg.ObserverID, arg.BrokerName, arg.IsPacket) return err } const upsertObserverScope = `-- name: UpsertObserverScope :exec INSERT INTO observer_scopes (observer_id, scope_id, last_seen) VALUES ($1, $2, NOW()) ON CONFLICT (observer_id, scope_id) DO UPDATE SET last_seen = NOW() ` type UpsertObserverScopeParams struct { ObserverID uuid.UUID `json:"observer_id"` ScopeID int32 `json:"scope_id"` } func (q *Queries) UpsertObserverScope(ctx context.Context, arg UpsertObserverScopeParams) error { _, err := q.db.Exec(ctx, upsertObserverScope, arg.ObserverID, arg.ScopeID) return err } const upsertPacket = `-- name: UpsertPacket :one WITH up AS ( INSERT INTO packets ( packet_hash, payload_type, payload_version, route_type, transport_codes_present, region_code, sub_region_code, origin_pubkey, raw_payload, raw_header, parsed_payload, channel_hash, scope_id, trace_tag, first_heard_at, last_heard_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, NOW(), NOW() ) ON CONFLICT (packet_hash) DO UPDATE SET last_heard_at = NOW() RETURNING packet_hash, payload_type, payload_version, route_type, transport_codes_present, region_code, sub_region_code, origin_pubkey, raw_payload, raw_header, parsed_payload, decrypted, channel_hash, first_heard_at, last_heard_at, trace_tag, scope_id, (xmax = 0) AS inserted ), trace AS ( INSERT INTO trace_tags (trace_tag, first_heard_at, last_heard_at, packet_count, trace_type, scope_id, best_payload, best_path_len) SELECT u.trace_tag, u.first_heard_at, u.last_heard_at, 1, u.parsed_payload->>'type', u.scope_id, u.parsed_payload, CASE WHEN jsonb_typeof(u.parsed_payload->'pathHashes') = 'array' THEN jsonb_array_length(u.parsed_payload->'pathHashes') ELSE 0 END FROM up u WHERE u.inserted AND u.trace_tag IS NOT NULL ON CONFLICT (trace_tag) DO UPDATE SET packet_count = trace_tags.packet_count + 1, trace_type = GREATEST(trace_tags.trace_type, EXCLUDED.trace_type), scope_id = COALESCE(trace_tags.scope_id, EXCLUDED.scope_id), best_payload = CASE WHEN EXCLUDED.best_path_len > trace_tags.best_path_len THEN EXCLUDED.best_payload ELSE trace_tags.best_payload END, best_path_len = GREATEST(trace_tags.best_path_len, EXCLUDED.best_path_len) ) SELECT packet_hash, payload_type, payload_version, route_type, transport_codes_present, region_code, sub_region_code, origin_pubkey, raw_payload, raw_header, parsed_payload, decrypted, channel_hash, first_heard_at, last_heard_at, inserted FROM up ` type UpsertPacketParams struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` PayloadVersion int16 `json:"payload_version"` RouteType int16 `json:"route_type"` TransportCodesPresent *bool `json:"transport_codes_present"` RegionCode *int32 `json:"region_code"` SubRegionCode *int32 `json:"sub_region_code"` OriginPubkey []byte `json:"origin_pubkey"` RawPayload []byte `json:"raw_payload"` RawHeader []byte `json:"raw_header"` ParsedPayload []byte `json:"parsed_payload"` ChannelHash []byte `json:"channel_hash"` ScopeID *int32 `json:"scope_id"` TraceTag []byte `json:"trace_tag"` } type UpsertPacketRow struct { PacketHash []byte `json:"packet_hash"` PayloadType int16 `json:"payload_type"` PayloadVersion int16 `json:"payload_version"` RouteType int16 `json:"route_type"` TransportCodesPresent *bool `json:"transport_codes_present"` RegionCode *int32 `json:"region_code"` SubRegionCode *int32 `json:"sub_region_code"` OriginPubkey []byte `json:"origin_pubkey"` RawPayload []byte `json:"raw_payload"` RawHeader []byte `json:"raw_header"` ParsedPayload []byte `json:"parsed_payload"` Decrypted *bool `json:"decrypted"` ChannelHash []byte `json:"channel_hash"` FirstHeardAt pgtype.Timestamptz `json:"first_heard_at"` LastHeardAt pgtype.Timestamptz `json:"last_heard_at"` Inserted bool `json:"inserted"` } // ============================================================ // PACKETS // ============================================================ // A new TRACE packet adds itself to its tag summary in the same statement, so the summary // can't miss a stored packet. 'TRACE' > 'PING'; the longest path keeps the best payload. func (q *Queries) UpsertPacket(ctx context.Context, arg UpsertPacketParams) (UpsertPacketRow, error) { row := q.db.QueryRow(ctx, upsertPacket, arg.PacketHash, arg.PayloadType, arg.PayloadVersion, arg.RouteType, arg.TransportCodesPresent, arg.RegionCode, arg.SubRegionCode, arg.OriginPubkey, arg.RawPayload, arg.RawHeader, arg.ParsedPayload, arg.ChannelHash, arg.ScopeID, arg.TraceTag, ) var i UpsertPacketRow err := row.Scan( &i.PacketHash, &i.PayloadType, &i.PayloadVersion, &i.RouteType, &i.TransportCodesPresent, &i.RegionCode, &i.SubRegionCode, &i.OriginPubkey, &i.RawPayload, &i.RawHeader, &i.ParsedPayload, &i.Decrypted, &i.ChannelHash, &i.FirstHeardAt, &i.LastHeardAt, &i.Inserted, ) return i, err } const upsertRegion = `-- name: UpsertRegion :one INSERT INTO regions (slug, name, description, display_order, center_lat, center_lng, zoom_level, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name, description = EXCLUDED.description, display_order = EXCLUDED.display_order, center_lat = EXCLUDED.center_lat, center_lng = EXCLUDED.center_lng, zoom_level = EXCLUDED.zoom_level, imported = FALSE, -- config owns the slug from now on updated_at = NOW() RETURNING id ` type UpsertRegionParams struct { Slug string `json:"slug"` Name string `json:"name"` Description *string `json:"description"` DisplayOrder *int32 `json:"display_order"` CenterLat *float64 `json:"center_lat"` CenterLng *float64 `json:"center_lng"` ZoomLevel *int32 `json:"zoom_level"` } func (q *Queries) UpsertRegion(ctx context.Context, arg UpsertRegionParams) (int32, error) { row := q.db.QueryRow(ctx, upsertRegion, arg.Slug, arg.Name, arg.Description, arg.DisplayOrder, arg.CenterLat, arg.CenterLng, arg.ZoomLevel, ) var id int32 err := row.Scan(&id) return id, err } const upsertTransportScope = `-- name: UpsertTransportScope :exec INSERT INTO transport_scopes (name, display_name, transport_key, key_fingerprint) VALUES ($1, $2, $3, $4) ON CONFLICT (name) DO UPDATE SET display_name = EXCLUDED.display_name, transport_key = EXCLUDED.transport_key, key_fingerprint = EXCLUDED.key_fingerprint, imported_only = FALSE ` type UpsertTransportScopeParams struct { Name string `json:"name"` DisplayName *string `json:"display_name"` TransportKey []byte `json:"transport_key"` KeyFingerprint []byte `json:"key_fingerprint"` } // ============================================================ // TRANSPORT CODES // ============================================================ func (q *Queries) UpsertTransportScope(ctx context.Context, arg UpsertTransportScopeParams) error { _, err := q.db.Exec(ctx, upsertTransportScope, arg.Name, arg.DisplayName, arg.TransportKey, arg.KeyFingerprint, ) return err }