mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-23 11:02:25 +00:00
add region filtering to packets
This commit is contained in:
9 files changed
+202
-204
No files matched your search
+10
-1
@@ -224,7 +224,11 @@ LEFT JOIN observers o ON o.id = po.observer_id
|
||||
WHERE
|
||||
($1::smallint = -1 OR p.payload_type = $1::smallint)
|
||||
AND ($2::smallint = -1 OR p.route_type = $2::smallint)
|
||||
AND ($3 = '' OR po.iata ILIKE $3)
|
||||
AND ($3::text = '' OR EXISTS (
|
||||
SELECT 1 FROM packet_observations po3
|
||||
WHERE po3.packet_hash = p.packet_hash
|
||||
AND po3.iata = ANY(string_to_array($3::text, ','))
|
||||
))
|
||||
AND ($4::timestamptz IS NULL OR p.first_heard_at >= $4)
|
||||
AND ($5::timestamptz IS NULL OR p.first_heard_at <= $5)
|
||||
AND ($6::timestamptz IS NULL OR p.last_heard_at < $6)
|
||||
@@ -598,6 +602,11 @@ SELECT id, slug, name, description, center_lat, center_lng, zoom_level
|
||||
FROM regions
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: GetRegionBySlug :one
|
||||
SELECT id, slug, name, description, center_lat, center_lng, zoom_level
|
||||
FROM regions
|
||||
WHERE slug = $1;
|
||||
|
||||
-- name: GetRegionIATAs :many
|
||||
SELECT iata FROM region_iatas
|
||||
WHERE region_id = $1
|
||||
|
||||
+37
-2
@@ -661,6 +661,37 @@ func (q *Queries) GetRegion(ctx context.Context, id int32) (GetRegionRow, error)
|
||||
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
|
||||
@@ -1817,7 +1848,11 @@ LEFT JOIN observers o ON o.id = po.observer_id
|
||||
WHERE
|
||||
($1::smallint = -1 OR p.payload_type = $1::smallint)
|
||||
AND ($2::smallint = -1 OR p.route_type = $2::smallint)
|
||||
AND ($3 = '' OR po.iata ILIKE $3)
|
||||
AND ($3::text = '' OR EXISTS (
|
||||
SELECT 1 FROM packet_observations po3
|
||||
WHERE po3.packet_hash = p.packet_hash
|
||||
AND po3.iata = ANY(string_to_array($3::text, ','))
|
||||
))
|
||||
AND ($4::timestamptz IS NULL OR p.first_heard_at >= $4)
|
||||
AND ($5::timestamptz IS NULL OR p.first_heard_at <= $5)
|
||||
AND ($6::timestamptz IS NULL OR p.last_heard_at < $6)
|
||||
@@ -1828,7 +1863,7 @@ LIMIT $7
|
||||
type ListPacketsParams struct {
|
||||
Column1 int16 `json:"column_1"`
|
||||
Column2 int16 `json:"column_2"`
|
||||
Column3 interface{} `json:"column_3"`
|
||||
Column3 string `json:"column_3"`
|
||||
Column4 pgtype.Timestamptz `json:"column_4"`
|
||||
Column5 pgtype.Timestamptz `json:"column_5"`
|
||||
Column6 pgtype.Timestamptz `json:"column_6"`
|
||||
|
||||
+35
-2
@@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sqlc "github.com/MeshCore-Tower/tower-server/db/sqlc"
|
||||
@@ -364,6 +365,37 @@ func (s *Store) GetRegion(ctx context.Context, regionID int32) (*api.Region, err
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetRegionBySlug returns full detail for a single region including its associated IATA codes.
|
||||
// Returns nil, pgx.ErrNoRows if the region is not found.
|
||||
func (s *Store) GetRegionBySlug(ctx context.Context, slug string) (*api.Region, error) {
|
||||
region, err := s.q.GetRegionBySlug(ctx, slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := api.Region{
|
||||
RegionSummary: api.RegionSummary{
|
||||
ID: int(region.ID),
|
||||
Slug: region.Slug,
|
||||
Name: region.Name,
|
||||
},
|
||||
Description: region.Description,
|
||||
CenterLat: region.CenterLat,
|
||||
CenterLng: region.CenterLng,
|
||||
}
|
||||
var zoomLevel *int
|
||||
if region.ZoomLevel != nil {
|
||||
z := int(*region.ZoomLevel)
|
||||
zoomLevel = &z
|
||||
}
|
||||
result.ZoomLevel = zoomLevel
|
||||
iatas, err := s.q.GetRegionIATAs(ctx, region.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.IATAs = iatas
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// UpsertIATADetails updates an existing iata_codes row with display name and coordinates.
|
||||
// The row must already exist (auto-created on first packet arrival).
|
||||
// Safe to call on startup — does nothing if the IATA has not been seen yet.
|
||||
@@ -986,7 +1018,7 @@ func (s *Store) ListNodeObservations(ctx context.Context, nodeID uuid.UUID, curs
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListPackets(ctx context.Context, payloadType, routeType int16, iata string, since, until time.Time, cursor int64, limit int32) (api.Page[api.PacketSummary], error) {
|
||||
func (s *Store) ListPackets(ctx context.Context, payloadType, routeType int16, iatas []string, since, until time.Time, cursor int64, limit int32) (api.Page[api.PacketSummary], error) {
|
||||
var cursorTS pgtype.Timestamptz
|
||||
if cursor > 0 {
|
||||
cursorTS = pgtype.Timestamptz{Time: time.UnixMilli(cursor), Valid: true}
|
||||
@@ -999,10 +1031,11 @@ func (s *Store) ListPackets(ctx context.Context, payloadType, routeType int16, i
|
||||
if !until.IsZero() {
|
||||
untilTS = pgtype.Timestamptz{Time: until, Valid: true}
|
||||
}
|
||||
iataFilter := strings.Join(iatas, ",")
|
||||
rows, err := s.q.ListPackets(ctx, sqlc.ListPacketsParams{
|
||||
Column1: payloadType,
|
||||
Column2: routeType,
|
||||
Column3: iata,
|
||||
Column3: iataFilter,
|
||||
Column4: sinceTS,
|
||||
Column5: untilTS,
|
||||
Column6: cursorTS,
|
||||
|
||||
+20
-72
@@ -770,10 +770,28 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by IATA code",
|
||||
"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": "integer",
|
||||
"description": "Filter by first_heard_at \u003e= since (epoch ms)",
|
||||
@@ -803,7 +821,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary"
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@@ -1748,20 +1766,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"iata": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketObservationDetail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1877,45 +1881,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstHeardAt": {
|
||||
"description": "epoch ms",
|
||||
"type": "integer"
|
||||
},
|
||||
"lastHeardAt": {
|
||||
"description": "epoch ms",
|
||||
"type": "integer"
|
||||
},
|
||||
"latestObserver": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver"
|
||||
},
|
||||
"observationCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"packetHash": {
|
||||
"description": "hex-encoded",
|
||||
"type": "string"
|
||||
},
|
||||
"payloadType": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payloadTypeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"routeType": {
|
||||
"type": "integer"
|
||||
},
|
||||
"routeTypeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"summary": {
|
||||
"description": "human-readable payload summary",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketTransportCodes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2029,23 +1994,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hasMore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary"
|
||||
}
|
||||
},
|
||||
"nextCursor": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PayloadBreakdownItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+20
-72
@@ -768,10 +768,28 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Filter by IATA code",
|
||||
"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": "integer",
|
||||
"description": "Filter by first_heard_at \u003e= since (epoch ms)",
|
||||
@@ -801,7 +819,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary"
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
@@ -1746,20 +1764,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"iata": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketObservationDetail": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1875,45 +1879,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstHeardAt": {
|
||||
"description": "epoch ms",
|
||||
"type": "integer"
|
||||
},
|
||||
"lastHeardAt": {
|
||||
"description": "epoch ms",
|
||||
"type": "integer"
|
||||
},
|
||||
"latestObserver": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver"
|
||||
},
|
||||
"observationCount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"packetHash": {
|
||||
"description": "hex-encoded",
|
||||
"type": "string"
|
||||
},
|
||||
"payloadType": {
|
||||
"type": "integer"
|
||||
},
|
||||
"payloadTypeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"routeType": {
|
||||
"type": "integer"
|
||||
},
|
||||
"routeTypeName": {
|
||||
"type": "string"
|
||||
},
|
||||
"summary": {
|
||||
"description": "human-readable payload summary",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PacketTransportCodes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -2027,23 +1992,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"hasMore": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary"
|
||||
}
|
||||
},
|
||||
"nextCursor": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_com_MeshCore-Tower_tower-server_internal_api.PayloadBreakdownItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+14
-49
@@ -388,15 +388,6 @@ definitions:
|
||||
routeTypeName:
|
||||
type: string
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver:
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
iata:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.PacketObservationDetail:
|
||||
properties:
|
||||
heardAt:
|
||||
@@ -474,33 +465,6 @@ definitions:
|
||||
spreadFactor:
|
||||
type: integer
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary:
|
||||
properties:
|
||||
firstHeardAt:
|
||||
description: epoch ms
|
||||
type: integer
|
||||
lastHeardAt:
|
||||
description: epoch ms
|
||||
type: integer
|
||||
latestObserver:
|
||||
$ref: '#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketLatestObserver'
|
||||
observationCount:
|
||||
type: integer
|
||||
packetHash:
|
||||
description: hex-encoded
|
||||
type: string
|
||||
payloadType:
|
||||
type: integer
|
||||
payloadTypeName:
|
||||
type: string
|
||||
routeType:
|
||||
type: integer
|
||||
routeTypeName:
|
||||
type: string
|
||||
summary:
|
||||
description: human-readable payload summary
|
||||
type: string
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.PacketTransportCodes:
|
||||
properties:
|
||||
regionCode:
|
||||
@@ -574,17 +538,6 @@ definitions:
|
||||
nextCursor:
|
||||
type: integer
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary:
|
||||
properties:
|
||||
hasMore:
|
||||
type: boolean
|
||||
items:
|
||||
items:
|
||||
$ref: '#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.PacketSummary'
|
||||
type: array
|
||||
nextCursor:
|
||||
type: integer
|
||||
type: object
|
||||
github_com_MeshCore-Tower_tower-server_internal_api.PayloadBreakdownItem:
|
||||
properties:
|
||||
count:
|
||||
@@ -1225,10 +1178,22 @@ paths:
|
||||
in: query
|
||||
name: routeType
|
||||
type: integer
|
||||
- description: Filter by IATA code
|
||||
- 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 first_heard_at >= since (epoch ms)
|
||||
in: query
|
||||
name: since
|
||||
@@ -1251,7 +1216,7 @@ paths:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/github_com_MeshCore-Tower_tower-server_internal_api.Page-github_com_MeshCore-Tower_tower-server_internal_api_PacketSummary'
|
||||
type: object
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
|
||||
@@ -29,12 +29,15 @@ func PacketsRouter(reader api.Reader) http.Handler {
|
||||
// @Param payloadType query int false "Filter by payload type integer"
|
||||
// @Param payloadTypeName query string false "Filter by payload type name (advert, grp_txt, txt_msg, trace, anon_req)"
|
||||
// @Param routeType query int false "Filter by route type (0=transport_flood, 1=flood, 2=direct, 3=transport_direct)"
|
||||
// @Param iata query string false "Filter by IATA code"
|
||||
// @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 since query int false "Filter by first_heard_at >= since (epoch ms)"
|
||||
// @Param until query int false "Filter by first_heard_at <= until (epoch ms)"
|
||||
// @Param cursor query int false "last_heard_at epoch ms of last item for pagination"
|
||||
// @Param limit query int false "Max results (default 50)"
|
||||
// @Success 200 {object} api.Page[api.PacketSummary]
|
||||
// @Success 200 {object} object
|
||||
// @Failure 400 {object} handlers.APIError
|
||||
// @Failure 500 {object} handlers.APIError
|
||||
// @Router /packets [get]
|
||||
@@ -98,8 +101,16 @@ func listPackets(reader api.Reader) http.HandlerFunc {
|
||||
}
|
||||
limit = int32(l)
|
||||
}
|
||||
iata := r.URL.Query().Get("iata")
|
||||
packets, err := reader.ListPackets(r.Context(), payloadType, routeType, iata, since, until, 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...)
|
||||
}
|
||||
packets, err := reader.ListPackets(r.Context(), payloadType, routeType, iatas, since, until, cursor, limit)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "internal server error")
|
||||
return
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/MeshCore-Tower/tower-server/internal/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -67,3 +70,46 @@ func getRegion(reader api.Reader) http.HandlerFunc {
|
||||
respond(w, http.StatusOK, region)
|
||||
}
|
||||
}
|
||||
|
||||
// parseIATAs splits a comma-separated iatas query param and uppercases each value.
|
||||
func parseIATAs(r *http.Request) []string {
|
||||
raw := r.URL.Query().Get("iatas")
|
||||
if raw == "" {
|
||||
// fall back to single iata param for backwards compatibility
|
||||
if single := r.URL.Query().Get("iata"); single != "" {
|
||||
return []string{strings.ToUpper(strings.TrimSpace(single))}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
iatas := make([]string, 0, len(parts))
|
||||
for _, p := range parts {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
iatas = append(iatas, strings.ToUpper(p))
|
||||
}
|
||||
}
|
||||
return iatas
|
||||
}
|
||||
|
||||
// resolveRegionIATAs expands a regionId or region slug to a slice of IATA codes.
|
||||
func resolveRegionIATAs(ctx context.Context, regionID, regionSlug string, reader api.Reader) ([]string, error) {
|
||||
var region *api.Region
|
||||
var err error
|
||||
switch {
|
||||
case regionID != "":
|
||||
rid, e := strconv.Atoi(regionID)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("invalid regionId: %w", e)
|
||||
}
|
||||
region, err = reader.GetRegion(ctx, int32(rid))
|
||||
case regionSlug != "":
|
||||
region, err = reader.GetRegionBySlug(ctx, regionSlug)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("region not found: %w", err)
|
||||
}
|
||||
return region.IATAs, nil
|
||||
}
|
||||
@@ -371,6 +371,9 @@ type Reader interface {
|
||||
// GetRegion returns full detail for a single region including its associated IATA codes.
|
||||
// Returns nil, pgx.ErrNoRows if the region is not found.
|
||||
GetRegion(ctx context.Context, regionID int32) (*Region, error)
|
||||
// GetRegionBySlug returns full detail for a single region by its URL-safe slug.
|
||||
// Returns nil, pgx.ErrNoRows if the region is not found.
|
||||
GetRegionBySlug(ctx context.Context, slug string) (*Region, error)
|
||||
// ListChannels returns a paginated list of channels ordered by last seen.
|
||||
// Includes both hashtag-derived and explicit key channels.
|
||||
// Pass nil hash to skip hash filtering. Pass empty string iata to return all channels.
|
||||
@@ -421,9 +424,9 @@ type Reader interface {
|
||||
ListNodeObservations(ctx context.Context, nodeID uuid.UUID, cursor int64, limit int32) (Page[PacketObservationSummary], error)
|
||||
// ListPackets returns a paginated list of packets with the latest observation rolled in.
|
||||
// Pass 0 for payloadType/routeType to skip those filters.
|
||||
// Pass empty string for iata, zero times for since/until to skip those filters.
|
||||
// Pass nil for iatas, zero times for since/until to skip those filters.
|
||||
// cursor is last_heard_at epoch ms; pass 0 to start from the beginning.
|
||||
ListPackets(ctx context.Context, payloadType, routeType int16, iata string, since, until time.Time, cursor int64, limit int32) (Page[PacketSummary], error)
|
||||
ListPackets(ctx context.Context, payloadType, routeType int16, iatas []string, since, until time.Time, cursor int64, limit int32) (Page[PacketSummary], error)
|
||||
// GetPacket returns full packet detail including all observations with radio settings.
|
||||
// Returns nil, pgx.ErrNoRows if not found.
|
||||
GetPacket(ctx context.Context, packetHash []byte) (*Packet, error)
|
||||
|
||||
Reference in new issue
Block a user