add region filter to observers

This commit is contained in:
Enot (ded) Skelly
2026-06-03 15:07:36 -07:00
parent 67e60de76f
commit cba1690470
8 changed files with 81 additions and 21 deletions
+5 -5
View File
@@ -89,11 +89,11 @@ COALESCE((
FROM observers o
LEFT JOIN observer_brokers ob ON ob.observer_id = o.id
WHERE
($1 = '' OR (
SELECT po.iata FROM packet_observations po
WHERE po.observer_id = o.id
ORDER BY po.heard_at DESC LIMIT 1
) ILIKE $1)
($1::text = '' OR (
SELECT po.iata FROM packet_observations po
WHERE po.observer_id = o.id
ORDER BY po.heard_at DESC LIMIT 1
) = ANY(string_to_array($1::text, ',')))
AND ($2 = '' OR o.observer_type = $2)
AND ($3 = '' OR ob.broker_name = $3)
AND ($4 = '' OR CASE
+6 -6
View File
@@ -1745,11 +1745,11 @@ COALESCE((
FROM observers o
LEFT JOIN observer_brokers ob ON ob.observer_id = o.id
WHERE
($1 = '' OR (
SELECT po.iata FROM packet_observations po
WHERE po.observer_id = o.id
ORDER BY po.heard_at DESC LIMIT 1
) ILIKE $1)
($1::text = '' OR (
SELECT po.iata FROM packet_observations po
WHERE po.observer_id = o.id
ORDER BY po.heard_at DESC LIMIT 1
) = ANY(string_to_array($1::text, ',')))
AND ($2 = '' OR o.observer_type = $2)
AND ($3 = '' OR ob.broker_name = $3)
AND ($4 = '' OR CASE
@@ -1764,7 +1764,7 @@ LIMIT $7
`
type ListObserversParams struct {
Column1 interface{} `json:"column_1"`
Column1 string `json:"column_1"`
Column2 interface{} `json:"column_2"`
Column3 interface{} `json:"column_3"`
Column4 interface{} `json:"column_4"`
+3 -2
View File
@@ -612,13 +612,14 @@ func (s *Store) ListChannelMessagesByHash(ctx context.Context, hash []byte, sinc
// status is "online" or "offline" derived from last_status_at recency.
// ListObservers returns a paginated list of observers with optional filters.
// cursor is last_seen epoch ms of the last observer; pass 0 to start from the beginning.
func (s *Store) ListObservers(ctx context.Context, iata, observerType, broker, status, name string, cursor int64, limit int32) (api.Page[api.ObserverSummary], error) {
func (s *Store) ListObservers(ctx context.Context, iatas []string, observerType, broker, status, name string, cursor int64, limit int32) (api.Page[api.ObserverSummary], error) {
var cursorTS pgtype.Timestamptz
if cursor > 0 {
cursorTS = pgtype.Timestamptz{Time: time.UnixMilli(cursor), Valid: true}
}
iataFilter := strings.Join(iatas, ",")
params := sqlc.ListObserversParams{
Column1: iata,
Column1: iataFilter,
Column2: observerType,
Column3: broker,
Column4: status,
+19 -1
View File
@@ -545,10 +545,28 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
"description": "Filter by IATA code (case-insensitive)",
"description": "Filter by single IATA code (case-insensitive)",
"name": "iata",
"in": "query"
},
{
"type": "string",
"description": "Filter by multiple IATA codes, comma-separated e.g. YVR,YYJ",
"name": "iatas",
"in": "query"
},
{
"type": "integer",
"description": "Filter by region ID, expands to member IATAs",
"name": "regionId",
"in": "query"
},
{
"type": "string",
"description": "Filter by region slug, expands to member IATAs",
"name": "region",
"in": "query"
},
{
"type": "string",
"description": "Filter by observer type (e.g. meshcoretomqtt, meshcore-ha)",
+19 -1
View File
@@ -543,10 +543,28 @@
"parameters": [
{
"type": "string",
"description": "Filter by IATA code (case-insensitive)",
"description": "Filter by single IATA code (case-insensitive)",
"name": "iata",
"in": "query"
},
{
"type": "string",
"description": "Filter by multiple IATA codes, comma-separated e.g. YVR,YYJ",
"name": "iatas",
"in": "query"
},
{
"type": "integer",
"description": "Filter by region ID, expands to member IATAs",
"name": "regionId",
"in": "query"
},
{
"type": "string",
"description": "Filter by region slug, expands to member IATAs",
"name": "region",
"in": "query"
},
{
"type": "string",
"description": "Filter by observer type (e.g. meshcoretomqtt, meshcore-ha)",
+13 -1
View File
@@ -1029,10 +1029,22 @@ paths:
/observers:
get:
parameters:
- description: Filter by IATA code (case-insensitive)
- description: Filter by single IATA code (case-insensitive)
in: query
name: iata
type: string
- description: Filter by multiple IATA codes, comma-separated e.g. YVR,YYJ
in: query
name: iatas
type: string
- description: Filter by region ID, expands to member IATAs
in: query
name: regionId
type: integer
- description: Filter by region slug, expands to member IATAs
in: query
name: region
type: string
- description: Filter by observer type (e.g. meshcoretomqtt, meshcore-ha)
in: query
name: type
+14 -3
View File
@@ -32,7 +32,10 @@ func ObserversRouter(reader api.Reader) http.Handler {
// @Summary List observers
// @Tags Observers
// @Produce json
// @Param iata query string false "Filter by IATA code (case-insensitive)"
// @Param iata query string false "Filter by single IATA code (case-insensitive)"
// @Param iatas query string false "Filter by multiple IATA codes, comma-separated e.g. YVR,YYJ"
// @Param regionId query int false "Filter by region ID, expands to member IATAs"
// @Param region query string false "Filter by region slug, expands to member IATAs"
// @Param type query string false "Filter by observer type (e.g. meshcoretomqtt, meshcore-ha)"
// @Param broker query string false "Filter by broker name"
// @Param status query string false "Filter by status (online or offline)"
@@ -45,7 +48,6 @@ func ObserversRouter(reader api.Reader) http.Handler {
// @Router /observers [get]
func listObservers(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
iata := r.URL.Query().Get("iata")
observerType := r.URL.Query().Get("type")
broker := r.URL.Query().Get("broker")
name := r.URL.Query().Get("name")
@@ -68,7 +70,16 @@ func listObservers(reader api.Reader) http.HandlerFunc {
}
limit = int32(l)
}
observers, err := reader.ListObservers(r.Context(), iata, observerType, broker, status, name, cursor, limit)
iatas := parseIATAs(r)
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
iatas = append(iatas, regionIATAs...)
}
observers, err := reader.ListObservers(r.Context(), iatas, observerType, broker, status, name, cursor, limit)
if err != nil {
respondError(w, http.StatusInternalServerError, "failed to get list of observers")
return
+2 -2
View File
@@ -396,10 +396,10 @@ type Reader interface {
// Pass cursor=0 to start from the beginning.
ListChannelMessagesByHash(ctx context.Context, hash []byte, since time.Time, limit int32, iata string, cursor int64) (Page[ChannelMessage], error)
// ListObservers returns a paginated list of observers with optional filters.
// All filter params are optional — pass empty string to skip a filter.
// All filter params are optional — pass empty string or nil to skip a filter.
// status is "online" or "offline" derived from last_status_at recency.
// cursor is last_seen epoch ms of the last observer; pass 0 to start from the beginning.
ListObservers(ctx context.Context, iata, observerType, broker, status, name string, cursor int64, limit int32) (Page[ObserverSummary], error)
ListObservers(ctx context.Context, iatas []string, observerType, broker, status, name string, cursor int64, limit int32) (Page[ObserverSummary], error)
// GetObserver returns full detail for a single observer by UUID.
// Returns nil, pgx.ErrNoRows if the observer is not found.
GetObserver(ctx context.Context, observerID uuid.UUID) (*Observer, error)