diff --git a/cmd/server/config.go b/cmd/server/config.go index 46cd5e9a..9abd6580 100644 --- a/cmd/server/config.go +++ b/cmd/server/config.go @@ -244,6 +244,15 @@ type Config struct { Areas map[string]AreaEntry `json:"areas,omitempty"` + // HomeArea names an entry in Areas whose geometry defines "home" for + // the foreign/domestic classification (geo_filter above), instead of + // maintaining a second, separately-drawn boundary that can drift out + // of sync with the area map (see AreaForPoint/AreaKeysForPoint). + // When set and the named area exists, it takes priority over any + // standalone GeoFilter value — see (*Server).getGeoFilter. Leave + // unset to keep using GeoFilter exactly as before. + HomeArea string `json:"homeArea,omitempty"` + Timestamps *TimestampConfig `json:"timestamps,omitempty"` // CORSAllowedOrigins is the list of origins permitted to make cross-origin diff --git a/cmd/server/config_client_geofilter_test.go b/cmd/server/config_client_geofilter_test.go index 7cb7fc8d..188c5794 100644 --- a/cmd/server/config_client_geofilter_test.go +++ b/cmd/server/config_client_geofilter_test.go @@ -76,3 +76,49 @@ func TestConfigClientOmitsGeoFilterWhenUnconfigured(t *testing.T) { t.Error("expected geoFilter to be omitted from /api/config/client when unconfigured") } } + +// TestGetGeoFilter_HomeAreaTakesPriority covers the new HomeArea linkage: +// when cfg.HomeArea names an existing area, its geometry is the effective +// geo_filter -- a single boundary shared with the areas system instead of +// a second copy that can drift out of sync (exactly what happened with +// Germany's box vs. Denmark's area earlier this session). +func TestGetGeoFilter_HomeAreaTakesPriority(t *testing.T) { + srv, _ := setupTestServer(t) + standaloneLat := 10.0 + srv.cfg.GeoFilter = &GeoFilterConfig{LatMin: &standaloneLat} // deliberately different from the area + f := func(v float64) *float64 { return &v } + srv.cfg.Areas = map[string]AreaEntry{ + "DK": {Label: "Danmark", LatMin: f(54.5), LatMax: f(57.8), LonMin: f(8.0), LonMax: f(15.25)}, + } + srv.cfg.HomeArea = "DK" + + gf := srv.getGeoFilter() + if gf == nil || gf.LatMin == nil || *gf.LatMin != 54.5 { + t.Fatalf("getGeoFilter() = %+v, want DK area's LatMin=54.5 (HomeArea should win over the standalone GeoFilter)", gf) + } +} + +// TestGetGeoFilter_FallsBackWhenHomeAreaUnresolved confirms existing +// deployments (no HomeArea configured, or one naming a since-removed area) +// see no behavior change -- the standalone GeoFilter still applies. +func TestGetGeoFilter_FallsBackWhenHomeAreaUnresolved(t *testing.T) { + srv, _ := setupTestServer(t) + standaloneLat := 10.0 + srv.cfg.GeoFilter = &GeoFilterConfig{LatMin: &standaloneLat} + + t.Run("HomeArea unset", func(t *testing.T) { + srv.cfg.HomeArea = "" + gf := srv.getGeoFilter() + if gf == nil || gf.LatMin == nil || *gf.LatMin != 10.0 { + t.Errorf("getGeoFilter() = %+v, want the standalone GeoFilter (LatMin=10.0)", gf) + } + }) + + t.Run("HomeArea names a nonexistent area", func(t *testing.T) { + srv.cfg.HomeArea = "NOPE" + gf := srv.getGeoFilter() + if gf == nil || gf.LatMin == nil || *gf.LatMin != 10.0 { + t.Errorf("getGeoFilter() = %+v, want the standalone GeoFilter (LatMin=10.0)", gf) + } + }) +} diff --git a/cmd/server/routes.go b/cmd/server/routes.go index 8cfcef18..b7a4cc68 100644 --- a/cmd/server/routes.go +++ b/cmd/server/routes.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gorilla/mux" + "github.com/meshcore-analyzer/geofilter" "github.com/meshcore-analyzer/packetpath" "github.com/meshcore-analyzer/prunequeue" regionutil "github.com/meshcore-analyzer/regions" @@ -180,9 +181,26 @@ func (s *Server) isPubkeyHidden(pubkey string) bool { return s.cfg.IsNameHidden(name) } +// getGeoFilter returns the effective home-boundary geometry. If +// cfg.HomeArea names an existing entry in cfg.Areas, that area's geometry +// wins — a single boundary shared with the areas system, instead of a +// second copy that can silently drift (see cfg.HomeArea doc comment). +// Falls back to the standalone GeoFilter field when HomeArea is unset or +// doesn't resolve, so existing deployments see no behavior change. func (s *Server) getGeoFilter() *GeoFilterConfig { s.cfgMu.RLock() defer s.cfgMu.RUnlock() + if s.cfg != nil && s.cfg.HomeArea != "" { + if area, ok := s.cfg.Areas[s.cfg.HomeArea]; ok { + return &geofilter.Config{ + Polygon: area.Polygon, + LatMin: area.LatMin, + LatMax: area.LatMax, + LonMin: area.LonMin, + LonMax: area.LonMax, + } + } + } return s.cfg.GeoFilter }