mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-16 08:52:38 +00:00
134 lines
4.5 KiB
Go
134 lines
4.5 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// RoutesRouter mounts all /routes routes onto a subrouter.
|
|
//
|
|
// GET /routes → listKnownRoutes
|
|
// GET /routes/search → searchKnownRoutes
|
|
func RoutesRouter(reader api.Reader) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Get("/", listKnownRoutes(reader))
|
|
r.Get("/cross", searchCrossIATARoutes(reader))
|
|
r.Get("/search", searchKnownRoutes(reader))
|
|
return r
|
|
}
|
|
|
|
// listKnownRoutes godoc
|
|
//
|
|
// @Summary List known routes
|
|
// @Tags Routes
|
|
// @Produce json
|
|
// @Param iata query string false "Filter by IATA code"
|
|
// @Param hopCount query int false "Filter by exact hop count"
|
|
// @Param cursor query int false "Epoch ms timestamp of last item for pagination"
|
|
// @Param limit query int false "Max results (default 50); must be positive, values above 200 are clamped" minimum(1) maximum(200)
|
|
// @Failure 400 {object} handlers.APIError
|
|
// @Success 200 {object} []api.KnownRoute
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /routes [get]
|
|
func listKnownRoutes(reader api.Reader) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
iata := r.URL.Query().Get("iata")
|
|
var hopCount int32
|
|
if v := r.URL.Query().Get("hopCount"); v != "" {
|
|
if h, err := strconv.ParseInt(v, 10, 32); err == nil {
|
|
hopCount = int32(h)
|
|
}
|
|
}
|
|
var cursor time.Time
|
|
if v := r.URL.Query().Get("cursor"); v != "" {
|
|
if ms, err := strconv.ParseInt(v, 10, 64); err == nil {
|
|
cursor = time.UnixMilli(ms)
|
|
}
|
|
}
|
|
limit, err := parseLimit(r, 50)
|
|
if err != nil {
|
|
respondError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
routes, err := reader.ListKnownRoutes(r.Context(), iata, hopCount, cursor, limit)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "internal server error")
|
|
return
|
|
}
|
|
respond(w, http.StatusOK, routes)
|
|
}
|
|
}
|
|
|
|
// searchKnownRoutes godoc
|
|
//
|
|
// @Summary Search known routes by source and destination hash
|
|
// @Tags Routes
|
|
// @Produce json
|
|
// @Param iata query string true "IATA code to search within"
|
|
// @Param from query string true "Source node hash prefix (hex)"
|
|
// @Param to query string true "Destination node hash prefix (hex)"
|
|
// @Success 200 {object} []api.KnownRoute
|
|
// @Failure 400 {object} handlers.APIError
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /routes/search [get]
|
|
func searchKnownRoutes(reader api.Reader) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
iata := strings.ToUpper(r.URL.Query().Get("iata"))
|
|
from := strings.ToLower(r.URL.Query().Get("from"))
|
|
to := strings.ToLower(r.URL.Query().Get("to"))
|
|
if iata == "" || from == "" || to == "" {
|
|
respondError(w, http.StatusBadRequest, "iata, from and to are required")
|
|
return
|
|
}
|
|
routes, err := reader.SearchKnownRoutes(r.Context(), iata, from, to)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "internal server error")
|
|
return
|
|
}
|
|
respond(w, http.StatusOK, routes)
|
|
}
|
|
}
|
|
|
|
// searchCrossIATARoutes godoc
|
|
//
|
|
// @Summary Search for routes that cross IATA boundaries
|
|
// @Tags Routes
|
|
// @Produce json
|
|
// @Param fromHash query string true "Source node hash prefix (hex)"
|
|
// @Param fromIata query string true "Source IATA code"
|
|
// @Param toHash query string true "Destination node hash prefix (hex)"
|
|
// @Param toIata query string true "Destination IATA code"
|
|
// @Success 200 {object} []api.CrossIATARoute
|
|
// @Failure 400 {object} handlers.APIError
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /routes/cross [get]
|
|
func searchCrossIATARoutes(reader api.Reader) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
fromHash := strings.ToLower(r.URL.Query().Get("fromHash"))
|
|
fromIATA := strings.ToUpper(r.URL.Query().Get("fromIata"))
|
|
toHash := strings.ToLower(r.URL.Query().Get("toHash"))
|
|
toIATA := strings.ToUpper(r.URL.Query().Get("toIata"))
|
|
if fromHash == "" || fromIATA == "" || toHash == "" || toIATA == "" {
|
|
respondError(w, http.StatusBadRequest, "fromHash, fromIata, toHash and toIata are required")
|
|
return
|
|
}
|
|
routes, err := reader.SearchCrossIATARoutes(r.Context(), fromHash, fromIATA, toHash, toIATA)
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "internal server error")
|
|
return
|
|
}
|
|
if routes == nil {
|
|
routes = []api.CrossIATARoute{}
|
|
}
|
|
respond(w, http.StatusOK, routes)
|
|
}
|
|
}
|