diff --git a/app.go b/app.go index 7269b8a..a916fca 100644 --- a/app.go +++ b/app.go @@ -41,6 +41,8 @@ type goBlog struct { db *database // Errors errorCheckMediaTypes []ct.MediaType + // Geo + photonMutex sync.Mutex // Hooks pPostHooks []postHookFunc pUpdateHooks []postHookFunc diff --git a/geo.go b/geo.go index 9a007b0..10473d7 100644 --- a/geo.go +++ b/geo.go @@ -1,9 +1,9 @@ package main import ( - "bytes" "context" "embed" + "encoding/json" "fmt" "strings" @@ -11,47 +11,57 @@ import ( "github.com/carlmjohnson/requests" geojson "github.com/paulmach/go.geojson" "github.com/thoas/go-funk" + "go.goblog.app/app/pkgs/bufferpool" ) func (a *goBlog) geoTitle(g *gogeouri.Geo, lang string) string { if name, ok := g.Parameters["name"]; ok && len(name) > 0 && name[0] != "" { return name[0] } - ba, err := a.photonReverse(g.Latitude, g.Longitude, lang) - if err != nil { - return "" - } - fc, err := geojson.UnmarshalFeatureCollection(ba) + fc, err := a.photonReverse(g.Latitude, g.Longitude, lang) if err != nil || len(fc.Features) < 1 { return "" } f := fc.Features[0] - name := f.PropertyMustString("name", "") - city := f.PropertyMustString("city", "") - state := f.PropertyMustString("state", "") - country := f.PropertyMustString("country", "") - return strings.Join(funk.FilterString([]string{name, city, state, country}, func(s string) bool { return s != "" }), ", ") + return strings.Join(funk.FilterString([]string{ + f.PropertyMustString("name", ""), f.PropertyMustString("city", ""), f.PropertyMustString("state", ""), f.PropertyMustString("country", ""), + }, func(s string) bool { return s != "" }), ", ") } -func (a *goBlog) photonReverse(lat, lon float64, lang string) ([]byte, error) { +func (a *goBlog) photonReverse(lat, lon float64, lang string) (*geojson.FeatureCollection, error) { + // Only allow one concurrent request + a.photonMutex.Lock() + defer a.photonMutex.Unlock() + // Create feature collection + fc := geojson.NewFeatureCollection() + // Check cache cacheKey := fmt.Sprintf("photon-%v-%v-%v", lat, lon, lang) - cache, _ := a.db.retrievePersistentCache(cacheKey) - if cache != nil { - return cache, nil + if cache, _ := a.db.retrievePersistentCache(cacheKey); cache != nil { + // Cache hit, unmarshal and return + if err := json.Unmarshal(cache, fc); err != nil { + return nil, err + } + return fc, nil } - var buf bytes.Buffer - rb := requests.URL("https://photon.komoot.io/reverse").Client(a.httpClient).UserAgent(appUserAgent).ToBytesBuffer(&buf) + // No cache, fetch from Photon + buf := bufferpool.Get() + defer bufferpool.Put(buf) + // Create request + rb := requests.URL("https://photon.komoot.io/reverse").Client(a.httpClient).UserAgent(appUserAgent).ToBytesBuffer(buf) + // Set parameters rb.Param("lat", fmt.Sprintf("%v", lat)).Param("lon", fmt.Sprintf("%v", lon)) - if lang == "de" || lang == "fr" || lang == "it" { - rb.Param("lang", lang) - } else { - rb.Param("lang", "en") - } + rb.Param("lang", funk.ShortIf(lang == "de" || lang == "fr" || lang == "it", lang, "en").(string)) // Photon only supports en, de, fr, it + // Do request if err := rb.Fetch(context.Background()); err != nil { return nil, err } + // Cache response _ = a.db.cachePersistently(cacheKey, buf.Bytes()) - return buf.Bytes(), nil + // Unmarshal response + if err := json.NewDecoder(buf).Decode(fc); err != nil { + return nil, err + } + return fc, nil } func geoOSMLink(g *gogeouri.Geo) string { diff --git a/geo_test.go b/geo_test.go new file mode 100644 index 0000000..6c4385a --- /dev/null +++ b/geo_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_geo(t *testing.T) { + + fc := newFakeHttpClient() + + app := &goBlog{ + httpClient: fc.Client, + cfg: createDefaultTestConfig(t), + } + _ = app.initConfig() + _ = app.initDatabase(false) + defer app.db.close() + app.initComponents(false) + + p := &post{ + Blog: "en", + Parameters: map[string][]string{ + "location": {"geo:52.51627,13.37737"}, + }, + } + + gu := app.geoURI(p) + require.NotNil(t, gu) + assert.Equal(t, 52.51627, gu.Latitude) + assert.Equal(t, 13.37737, gu.Longitude) + + osmLink := geoOSMLink(gu) + assert.Equal(t, "https://www.openstreetmap.org/?mlat=52.51627&mlon=13.37737", osmLink) + + // Test original Photon request + + fc.setFakeResponse(http.StatusOK, `{"features":[{"geometry":{"coordinates":[13.3774202,52.5162623],"type":"Point"},"type":"Feature","properties":{"osm_id":38345682,"osm_type":"W","extent":[13.3772052,52.5162623,13.3774202,52.5162476],"country":"Deutschland","osm_key":"highway","city":"Berlin","countrycode":"DE","district":"Mitte","osm_value":"service","postcode":"10117","name":"Platz des 18. März","type":"street"}}],"type":"FeatureCollection"}`) + + gt := app.geoTitle(gu, "de") + + require.NotNil(t, fc.req) + assert.Equal(t, "https://photon.komoot.io/reverse?lang=de&lat=52.51627&lon=13.37737", fc.req.URL.String()) + + assert.Equal(t, "Platz des 18. März, Berlin, Deutschland", gt) + + // Test cache + + fc.setFakeResponse(http.StatusOK, "") + + gt = app.geoTitle(gu, "de") + + assert.Nil(t, fc.req) + + assert.Equal(t, "Platz des 18. März, Berlin, Deutschland", gt) + +}