mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
10
NOTICE
10
NOTICE
@@ -9,7 +9,7 @@ The following 3rd-party software packages may be used by or distributed with
|
||||
PhotoPrism. Any information relevant to third-party vendors listed below are
|
||||
collected using common, reasonable means.
|
||||
|
||||
Date generated: 2025-07-02
|
||||
Date generated: 2025-07-03
|
||||
|
||||
================================================================================
|
||||
|
||||
@@ -2343,8 +2343,8 @@ SOFTWARE.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Package: github.com/golang/geo
|
||||
Version: v0.0.0-20250627182359-f4b81656db99
|
||||
License: Apache-2.0 (https://github.com/golang/geo/blob/f4b81656db99/LICENSE)
|
||||
Version: v0.0.0-20250702194635-55051eb0594d
|
||||
License: Apache-2.0 (https://github.com/golang/geo/blob/55051eb0594d/LICENSE)
|
||||
|
||||
|
||||
Apache License
|
||||
@@ -6414,8 +6414,8 @@ License: Apache-2.0 (https://github.com/zitadel/logging/blob/v0.6.2/LICENSE)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Package: github.com/zitadel/oidc/v3/pkg
|
||||
Version: v3.39.0
|
||||
License: Apache-2.0 (https://github.com/zitadel/oidc/blob/v3.39.0/LICENSE)
|
||||
Version: v3.39.1
|
||||
License: Apache-2.0 (https://github.com/zitadel/oidc/blob/v3.39.1/LICENSE)
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
|
||||
4
go.mod
4
go.mod
@@ -14,7 +14,7 @@ require (
|
||||
github.com/esimov/pigo v1.4.6
|
||||
github.com/gin-contrib/gzip v1.2.3
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/golang/geo v0.0.0-20250630213057-fac2d31592dd
|
||||
github.com/golang/geo v0.0.0-20250702194635-55051eb0594d
|
||||
github.com/google/open-location-code/go v0.0.0-20250620134813-83986da0156b
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/gosimple/slug v1.15.0
|
||||
@@ -86,7 +86,7 @@ require (
|
||||
github.com/ugjka/go-tz/v2 v2.2.6
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
github.com/wamuir/graft v0.10.0
|
||||
github.com/zitadel/oidc/v3 v3.39.0
|
||||
github.com/zitadel/oidc/v3 v3.39.1
|
||||
golang.org/x/mod v0.25.0
|
||||
golang.org/x/sys v0.33.0
|
||||
)
|
||||
|
||||
12
go.sum
12
go.sum
@@ -133,8 +133,8 @@ github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2 h1:r08P663ikXiulLT9XaabkLypL/W9MoCIbqgQoAutyX4=
|
||||
github.com/go-co-op/gocron/v2 v2.16.2/go.mod h1:4YTLGCCAH75A5RlQ6q+h+VacO7CgjkgP0EJ+BEOXRSI=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
@@ -184,8 +184,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
|
||||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20250630213057-fac2d31592dd h1:4BU/Fy2KwDucgY0al91L0wJI05rb1Y4hQKucQkybjT8=
|
||||
github.com/golang/geo v0.0.0-20250630213057-fac2d31592dd/go.mod h1:Vaw7L5b+xa3Rj4/pRtrQkymn3lSBRB/NAEdbF9YEVLA=
|
||||
github.com/golang/geo v0.0.0-20250702194635-55051eb0594d h1:AzT7tYHcPLcUlo6224yQJvhL1A9GjT5g1cYbyDSYt/w=
|
||||
github.com/golang/geo v0.0.0-20250702194635-55051eb0594d/go.mod h1:Vaw7L5b+xa3Rj4/pRtrQkymn3lSBRB/NAEdbF9YEVLA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -419,8 +419,8 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU=
|
||||
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4=
|
||||
github.com/zitadel/oidc/v3 v3.39.0 h1:WK3eNqmgshiYo1oEqONfXXbPbve+Qzgjl8KhKDFUvxc=
|
||||
github.com/zitadel/oidc/v3 v3.39.0/go.mod h1:JwdgdU/WxkmBtWuE8/pEjAbDTWXxJGqBix/gUoeEig4=
|
||||
github.com/zitadel/oidc/v3 v3.39.1 h1:6QwGwI3yxh4somT7fwRCeT1KOn/HOGv0PA0dFciwJjE=
|
||||
github.com/zitadel/oidc/v3 v3.39.1/go.mod h1:aH8brOrzoliAybVdfq2xIdGvbtl0j/VsKRNa7WE72gI=
|
||||
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU=
|
||||
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
||||
@@ -13,5 +13,5 @@ func ClientIP(c *gin.Context) (ip string) {
|
||||
|
||||
// UserAgent returns the user agent from the request context or an empty string if it is unknown.
|
||||
func UserAgent(c *gin.Context) string {
|
||||
return header.UserAgent(c)
|
||||
return header.ClientUserAgent(c)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
@@ -19,12 +20,13 @@ import (
|
||||
// @Id GetPlacesReverse
|
||||
// @Tags Places
|
||||
// @Produce json
|
||||
// @Param lat query string true "Latitude"
|
||||
// @Param lng query string true "Longitude"
|
||||
// @Success 200 {object} places.Location
|
||||
// @Failure 400 {object} gin.H "Missing latitude or longitude"
|
||||
// @Failure 401 {object} i18n.Response
|
||||
// @Failure 500 {object} gin.H "Geocoding service error"
|
||||
// @Param lat query string true "Latitude"
|
||||
// @Param lng query string true "Longitude"
|
||||
// @Param locale query string false "Locale"
|
||||
// @Success 200 {object} places.Location
|
||||
// @Failure 400 {object} gin.H "Missing latitude or longitude"
|
||||
// @Failure 401 {object} i18n.Response
|
||||
// @Failure 500 {object} gin.H "Geocoding service error"
|
||||
// @Router /api/v1/places/reverse [get]
|
||||
func GetPlacesReverse(router *gin.RouterGroup) {
|
||||
handler := func(c *gin.Context) {
|
||||
@@ -44,7 +46,7 @@ func GetPlacesReverse(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get latitude and longitude from query parameters.
|
||||
// Get latitude, longitude, and locale from query parameters.
|
||||
var lat, lng string
|
||||
|
||||
if lat = txt.Numeric(c.Query("lat")); lat == "" {
|
||||
@@ -61,14 +63,19 @@ func GetPlacesReverse(router *gin.RouterGroup) {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := places.LatLng(txt.Float64(lat), txt.Float64(lng))
|
||||
locale := clean.WebLocale(c.Query("locale"), conf.PlacesLocale())
|
||||
|
||||
// Perform service request.
|
||||
result, err := places.LatLng(txt.Float64(lat), txt.Float64(lng), locale)
|
||||
|
||||
// Return error if request was not successful.
|
||||
if err != nil {
|
||||
log.Errorf("places: failed to resolve location at lat %s, lng %s", lat, lng)
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err})
|
||||
return
|
||||
}
|
||||
|
||||
// Return location details.
|
||||
c.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func GetPlacesSearch(router *gin.RouterGroup) {
|
||||
|
||||
// Get the search string, locale, and result count limit from the query parameters.
|
||||
query := clean.SearchString(c.Query("q"))
|
||||
locale := clean.WebLocale(c.Query("locale"), conf.DefaultLocale())
|
||||
locale := clean.WebLocale(c.Query("locale"), conf.PlacesLocale())
|
||||
count := txt.IntVal(c.Query("count"), 1, 50, 10)
|
||||
|
||||
if query == "" {
|
||||
|
||||
@@ -3873,6 +3873,12 @@
|
||||
"name": "lng",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Locale",
|
||||
"name": "locale",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5665,6 +5671,9 @@
|
||||
"OriginalsLimit": {
|
||||
"type": "integer"
|
||||
},
|
||||
"PlacesLocale": {
|
||||
"type": "string"
|
||||
},
|
||||
"PngSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -7534,6 +7543,9 @@
|
||||
"lng": {
|
||||
"type": "number"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7580,7 +7592,7 @@
|
||||
"places.SearchResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"boundingbox": {
|
||||
"bbox": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
|
||||
@@ -306,6 +306,7 @@ func (c *Config) Propagate() {
|
||||
|
||||
// Set geocoding parameters.
|
||||
places.UserAgent = c.UserAgent()
|
||||
places.DefaultLocale = c.PlacesLocale()
|
||||
entity.GeoApi = c.GeoApi()
|
||||
|
||||
// Set session cache duration.
|
||||
@@ -698,15 +699,6 @@ func (c *Config) AutoImport() time.Duration {
|
||||
return time.Duration(c.options.AutoImport) * time.Second
|
||||
}
|
||||
|
||||
// GeoApi returns the preferred geocoding api (places, or none).
|
||||
func (c *Config) GeoApi() string {
|
||||
if c.options.DisablePlaces {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "places"
|
||||
}
|
||||
|
||||
// OriginalsLimit returns the maximum size of originals in MB.
|
||||
func (c *Config) OriginalsLimit() int {
|
||||
if c.options.OriginalsLimit <= 0 || c.options.OriginalsLimit > 100000 {
|
||||
|
||||
20
internal/config/config_places.go
Normal file
20
internal/config/config_places.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
// GeoApi returns the preferred geocoding api (places, or none).
|
||||
func (c *Config) GeoApi() string {
|
||||
if c.options.DisablePlaces {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "places"
|
||||
}
|
||||
|
||||
// PlacesLocale returns the locale name used for geocoding.
|
||||
func (c *Config) PlacesLocale() string {
|
||||
return clean.WebLocale(c.options.PlacesLocale, places.LocalLocale)
|
||||
}
|
||||
30
internal/config/config_places_test.go
Normal file
30
internal/config/config_places_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestConfig_GeoApi(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
assert.Equal(t, "places", c.GeoApi())
|
||||
c.options.DisablePlaces = true
|
||||
assert.Equal(t, "", c.GeoApi())
|
||||
}
|
||||
|
||||
func TestConfig_PlacesLocale(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
c.options.PlacesLocale = ""
|
||||
assert.Equal(t, "local", c.PlacesLocale())
|
||||
c.options.PlacesLocale = "local"
|
||||
assert.Equal(t, "local", c.PlacesLocale())
|
||||
c.options.PlacesLocale = "EN"
|
||||
assert.Equal(t, "en", c.PlacesLocale())
|
||||
c.options.PlacesLocale = "EN_US"
|
||||
assert.Equal(t, "en-US", c.PlacesLocale())
|
||||
c.options.PlacesLocale = ""
|
||||
assert.Equal(t, "local", c.PlacesLocale())
|
||||
}
|
||||
@@ -316,14 +316,6 @@ func TestConfig_AutoImport(t *testing.T) {
|
||||
assert.Equal(t, 2*time.Hour, c.AutoImport())
|
||||
}
|
||||
|
||||
func TestConfig_GeoApi(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
assert.Equal(t, "places", c.GeoApi())
|
||||
c.options.DisablePlaces = true
|
||||
assert.Equal(t, "", c.GeoApi())
|
||||
}
|
||||
|
||||
func TestConfig_OriginalsLimit(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/photoprism/photoprism/internal/config/ttl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/ffmpeg/encode"
|
||||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
@@ -510,6 +511,12 @@ var Flags = CliFlags{
|
||||
Usage: "default user interface theme `NAME`",
|
||||
EnvVars: EnvVars("DEFAULT_THEME"),
|
||||
}}, {
|
||||
Flag: &cli.StringFlag{
|
||||
Name: "places-locale",
|
||||
Usage: "location details language `CODE`, e.g. en, de, or local",
|
||||
Value: places.LocalLocale,
|
||||
EnvVars: EnvVars("PLACES_LOCALE"),
|
||||
}}, {
|
||||
Flag: &cli.StringFlag{
|
||||
Name: "app-name",
|
||||
Usage: "progressive web app `NAME` when installed on a device",
|
||||
|
||||
@@ -118,6 +118,7 @@ type Options struct {
|
||||
DefaultLocale string `yaml:"DefaultLocale" json:"DefaultLocale" flag:"default-locale"`
|
||||
DefaultTimezone string `yaml:"DefaultTimezone" json:"DefaultTimezone" flag:"default-timezone"`
|
||||
DefaultTheme string `yaml:"DefaultTheme" json:"DefaultTheme" flag:"default-theme"`
|
||||
PlacesLocale string `yaml:"PlacesLocale" json:"PlacesLocale" flag:"places-locale"`
|
||||
AppName string `yaml:"AppName" json:"AppName" flag:"app-name"`
|
||||
AppMode string `yaml:"AppMode" json:"AppMode" flag:"app-mode"`
|
||||
AppIcon string `yaml:"AppIcon" json:"AppIcon" flag:"app-icon"`
|
||||
|
||||
@@ -139,6 +139,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
||||
{"default-locale", c.DefaultLocale()},
|
||||
{"default-timezone", c.DefaultTimezone().String()},
|
||||
{"default-theme", c.DefaultTheme()},
|
||||
{"places-locale", c.PlacesLocale()},
|
||||
{"app-name", c.AppName()},
|
||||
{"app-mode", c.AppMode()},
|
||||
{"app-icon", c.AppIcon()},
|
||||
|
||||
@@ -662,7 +662,7 @@ func (m *Session) SetContext(c *gin.Context) *Session {
|
||||
}
|
||||
|
||||
// Set client user agent from request context.
|
||||
if ua := header.UserAgent(c); ua != "" {
|
||||
if ua := header.ClientUserAgent(c); ua != "" {
|
||||
m.SetUserAgent(ua)
|
||||
}
|
||||
|
||||
@@ -688,7 +688,7 @@ func (m *Session) UpdateContext(c *gin.Context) *Session {
|
||||
}
|
||||
|
||||
// Set client user agent from request context.
|
||||
if ua := header.UserAgent(c); ua != "" && ua != m.UserAgent {
|
||||
if ua := header.ClientUserAgent(c); ua != "" && ua != m.UserAgent {
|
||||
m.SetUserAgent(ua)
|
||||
changed = true
|
||||
}
|
||||
|
||||
@@ -245,9 +245,9 @@ func (c *Config) ReSync(token string) (err error) {
|
||||
|
||||
// Set user agent.
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
req.Header.Set(header.UserAgent, c.UserAgent)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", "PhotoPrism/Test")
|
||||
req.Header.Set(header.UserAgent, "PhotoPrism/Test")
|
||||
}
|
||||
|
||||
// Add Content-Type header.
|
||||
|
||||
@@ -82,12 +82,12 @@ func (c *Config) SendFeedback(frm form.Feedback) (err error) {
|
||||
|
||||
// Set user agent.
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set("User-Agent", c.UserAgent)
|
||||
req.Header.Set(header.UserAgent, c.UserAgent)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", "PhotoPrism/Test")
|
||||
req.Header.Set(header.UserAgent, "PhotoPrism/Test")
|
||||
}
|
||||
|
||||
req.Header.Add("Accept-Language", frm.UserLocales)
|
||||
req.Header.Add(header.AcceptLanguage, frm.UserLocales)
|
||||
req.Header.Add(header.ContentType, header.ContentTypeJson)
|
||||
|
||||
var r *http.Response
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// Cell returns location details based on the specified S2 cell ID.
|
||||
func Cell(id string) (result Location, err error) {
|
||||
func Cell(id string, locale string) (result Location, err error) {
|
||||
|
||||
// Normalize S2 Cell ID.
|
||||
id = s2.NormalizeToken(id)
|
||||
@@ -34,8 +34,11 @@ func Cell(id string) (result Location, err error) {
|
||||
return result, fmt.Errorf("skipping lat %f, lng %f", lat, lng)
|
||||
}
|
||||
|
||||
// Get request locale.
|
||||
locale = Locale(locale)
|
||||
|
||||
// Create cache key based on query parameters.
|
||||
cacheKey := fmt.Sprintf("id:%s", id)
|
||||
cacheKey := fmt.Sprintf("id:%s:%s", id, locale)
|
||||
|
||||
// Location details cached?
|
||||
if hit, ok := clientCache.Get(cacheKey); ok {
|
||||
@@ -50,7 +53,7 @@ func Cell(id string) (result Location, err error) {
|
||||
// Query the specified places service URLs.
|
||||
for _, serviceUrl := range LocationServiceUrls {
|
||||
reqUrl := fmt.Sprintf(serviceUrl, id)
|
||||
if r, err = GetRequest(reqUrl); err == nil {
|
||||
if r, err = GetRequest(reqUrl, locale); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestCell(t *testing.T) {
|
||||
lng := 13.40806264572578
|
||||
id := s2.Token(lat, lng)
|
||||
|
||||
l, err := Cell(id)
|
||||
l, err := Cell(id, DefaultLocale)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -25,17 +25,17 @@ func TestCell(t *testing.T) {
|
||||
assert.Equal(t, "de", l.CountryCode())
|
||||
})
|
||||
t.Run("MissingId", func(t *testing.T) {
|
||||
l, err := Cell("")
|
||||
l, err := Cell("", DefaultLocale)
|
||||
assert.Error(t, err, "places: invalid location id ")
|
||||
t.Log(l)
|
||||
})
|
||||
t.Run("WrongId", func(t *testing.T) {
|
||||
l, err := Cell("2")
|
||||
l, err := Cell("2", DefaultLocale)
|
||||
assert.Error(t, err, "places: skipping lat 0.000000, lng 0.000000")
|
||||
t.Log(l)
|
||||
})
|
||||
t.Run("ShortId", func(t *testing.T) {
|
||||
l, err := Cell("ab")
|
||||
l, err := Cell("ab", DefaultLocale)
|
||||
assert.Error(t, err, "places: skipping lat 0.000000, lng 0.000000")
|
||||
t.Log(l)
|
||||
})
|
||||
@@ -52,12 +52,12 @@ func TestCell(t *testing.T) {
|
||||
Cached: true,
|
||||
}
|
||||
|
||||
l, err := Cell(location.ID)
|
||||
l, err := Cell(location.ID, DefaultLocale)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, false, l.Cached)
|
||||
l2, err2 := Cell("1e95998417cc")
|
||||
l2, err2 := Cell("1e95998417cc", DefaultLocale)
|
||||
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// LatLng returns location details based on the specified latitude and longitude.
|
||||
func LatLng(lat, lng float64) (result Location, err error) {
|
||||
func LatLng(lat, lng float64, locale string) (result Location, err error) {
|
||||
if lat == 0.0 || lng == 0.0 {
|
||||
return result, ErrMissingCoordinates
|
||||
}
|
||||
@@ -24,9 +24,12 @@ func LatLng(lat, lng float64) (result Location, err error) {
|
||||
values := url.Values{"lat": {fmt.Sprintf("%f", lat)}, "lng": {fmt.Sprintf("%f", lng)}}
|
||||
params := values.Encode()
|
||||
|
||||
// Get request locale.
|
||||
locale = Locale(locale)
|
||||
|
||||
// Create cache key based on query parameters.
|
||||
id := s2.Token(lat, lng)
|
||||
cacheKey := fmt.Sprintf("id:%s", id)
|
||||
cacheKey := fmt.Sprintf("id:%s:%s", id, locale)
|
||||
|
||||
// Are location results cached?
|
||||
if hit, ok := clientCache.Get(cacheKey); ok {
|
||||
@@ -41,7 +44,7 @@ func LatLng(lat, lng float64) (result Location, err error) {
|
||||
// Query the specified places service URLs.
|
||||
for _, serviceUrl := range ReverseServiceUrls {
|
||||
reqUrl := fmt.Sprintf("%s?%s", serviceUrl, params)
|
||||
if r, err = GetRequest(reqUrl); err == nil {
|
||||
if r, err = GetRequest(reqUrl, locale); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,40 @@ import (
|
||||
)
|
||||
|
||||
func TestLatLng(t *testing.T) {
|
||||
lat := 52.51961810676184
|
||||
lng := 13.40806264572578
|
||||
lat := 52.5208
|
||||
lng := 13.40953
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
l, err := LatLng(lat, lng)
|
||||
t.Run("Local", func(t *testing.T) {
|
||||
l, err := LatLng(lat, lng, LocalLocale)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, LocalLocale, l.Locale)
|
||||
assert.Equal(t, "Berliner Fernsehturm", l.Name())
|
||||
assert.Equal(t, "Berlin", l.City())
|
||||
assert.Equal(t, "de", l.CountryCode())
|
||||
})
|
||||
t.Run("Englisb", func(t *testing.T) {
|
||||
l, err := LatLng(lat, lng, "en")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "en", l.Locale)
|
||||
assert.Equal(t, "Fernsehturm Berlin", l.Name())
|
||||
assert.Equal(t, "Berlin", l.City())
|
||||
assert.Equal(t, "de", l.CountryCode())
|
||||
})
|
||||
t.Run("MissingLng", func(t *testing.T) {
|
||||
l, err := LatLng(1, 0)
|
||||
l, err := LatLng(1, 0, LocalLocale)
|
||||
assert.Error(t, err, "places: skipping lat 0.000000, lng 0.000000")
|
||||
t.Log(l)
|
||||
})
|
||||
t.Run("MissingLat", func(t *testing.T) {
|
||||
l, err := LatLng(0, 1)
|
||||
l, err := LatLng(0, 1, LocalLocale)
|
||||
assert.Error(t, err, "places: skipping lat 0.000000, lng 0.000000")
|
||||
t.Log(l)
|
||||
})
|
||||
@@ -43,13 +57,13 @@ func TestLatLng(t *testing.T) {
|
||||
Cached: true,
|
||||
}
|
||||
|
||||
_, err := LatLng(location.LocLat, location.LocLng)
|
||||
_, err := LatLng(location.LocLat, location.LocLng, LocalLocale)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cachedLoc, cacheErr := LatLng(location.LocLat, location.LocLng)
|
||||
cachedLoc, cacheErr := LatLng(location.LocLat, location.LocLng, LocalLocale)
|
||||
|
||||
if cacheErr != nil {
|
||||
t.Fatal(cacheErr)
|
||||
|
||||
16
internal/service/hub/places/locale.go
Normal file
16
internal/service/hub/places/locale.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package places
|
||||
|
||||
import (
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
// LocalLocale specifies the locale name to return results in the local language.
|
||||
const LocalLocale = "local"
|
||||
|
||||
// DefaultLocale specifies the default places query locale.
|
||||
var DefaultLocale = LocalLocale
|
||||
|
||||
// Locale returns the places query locale string.
|
||||
func Locale(locale string) string {
|
||||
return clean.WebLocale(locale, DefaultLocale)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
// Location represents a specific geolocation identified by its S2 ID.
|
||||
type Location struct {
|
||||
ID string `json:"id"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
LocLat float64 `json:"lat"`
|
||||
LocLng float64 `json:"lng"`
|
||||
LocName string `json:"name"`
|
||||
|
||||
@@ -5,10 +5,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/media/http/header"
|
||||
)
|
||||
|
||||
// GetRequest fetches the cell ID data from the service URL.
|
||||
func GetRequest(reqUrl string) (r *http.Response, err error) {
|
||||
func GetRequest(reqUrl string, locale string) (r *http.Response, err error) {
|
||||
var req *http.Request
|
||||
|
||||
// Log request URL.
|
||||
@@ -25,9 +27,14 @@ func GetRequest(reqUrl string) (r *http.Response, err error) {
|
||||
|
||||
// Set user agent.
|
||||
if UserAgent != "" {
|
||||
req.Header.Set("User-Agent", UserAgent)
|
||||
req.Header.Set(header.UserAgent, UserAgent)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", "PhotoPrism/Test")
|
||||
req.Header.Set(header.UserAgent, "PhotoPrism/Test")
|
||||
}
|
||||
|
||||
// Set requested result locale.
|
||||
if locale != "" {
|
||||
req.Header.Set(header.AcceptLanguage, locale)
|
||||
}
|
||||
|
||||
// Add API key?
|
||||
|
||||
@@ -31,15 +31,13 @@ func Search(q, locale string, count int) (results SearchResults, err error) {
|
||||
|
||||
// Generate query parameter string.
|
||||
values := url.Values{"q": {q}, "count": {strconv.Itoa(count)}}
|
||||
|
||||
if locale != "" {
|
||||
values.Add("locale", locale)
|
||||
}
|
||||
|
||||
params := values.Encode()
|
||||
|
||||
// Get request locale.
|
||||
locale = Locale(locale)
|
||||
|
||||
// Create cache key based on query parameters.
|
||||
cacheKey := fmt.Sprintf("search:%s", params)
|
||||
cacheKey := fmt.Sprintf("search:%s:%s", params, locale)
|
||||
|
||||
// Are location results cached?
|
||||
if hit, ok := clientCache.Get(cacheKey); ok {
|
||||
@@ -53,7 +51,7 @@ func Search(q, locale string, count int) (results SearchResults, err error) {
|
||||
// Query the specified places service URLs.
|
||||
for _, serviceUrl := range SearchServiceUrls {
|
||||
reqUrl := fmt.Sprintf("%s?%s", serviceUrl, params)
|
||||
if r, err = GetRequest(reqUrl); err == nil {
|
||||
if r, err = GetRequest(reqUrl, locale); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ type SearchResult struct {
|
||||
Country string `json:"country"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
Boundingbox []float64 `json:"boundingbox,omitempty"`
|
||||
BoundingBox []float64 `json:"bbox,omitempty"`
|
||||
Importance float64 `json:"importance,omitempty"`
|
||||
Licence string `json:"licence,omitempty"`
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (l *Location) QueryApi(api string) error {
|
||||
}
|
||||
|
||||
func (l *Location) QueryPlaces() error {
|
||||
s, err := places.Cell(l.ID)
|
||||
s, err := places.Cell(l.ID, places.DefaultLocale)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -43,6 +43,13 @@ func TestPosixLocale(t *testing.T) {
|
||||
assert.Equal(t, "de", PosixLocale("und", "de"))
|
||||
assert.Equal(t, "cs", PosixLocale("cs", "und"))
|
||||
})
|
||||
t.Run("Local", func(t *testing.T) {
|
||||
assert.Equal(t, "local", PosixLocale("", "local"))
|
||||
assert.Equal(t, "Local", PosixLocale("", "Local"))
|
||||
assert.Equal(t, "", PosixLocale("local", ""))
|
||||
assert.Equal(t, "", PosixLocale("Local", ""))
|
||||
assert.Equal(t, "local", PosixLocale("local", "local"))
|
||||
})
|
||||
t.Run("Territory", func(t *testing.T) {
|
||||
assert.Equal(t, "cs_CZ", PosixLocale("cs_CZ", ""))
|
||||
assert.Equal(t, "cs_CZ", PosixLocale("cs-CZ", ""))
|
||||
@@ -68,6 +75,13 @@ func TestWebLocale(t *testing.T) {
|
||||
assert.Equal(t, "de", WebLocale("und", "de"))
|
||||
assert.Equal(t, "cs", WebLocale("cs", "und"))
|
||||
})
|
||||
t.Run("Local", func(t *testing.T) {
|
||||
assert.Equal(t, "local", WebLocale("", "local"))
|
||||
assert.Equal(t, "Local", WebLocale("", "Local"))
|
||||
assert.Equal(t, "", WebLocale("local", ""))
|
||||
assert.Equal(t, "", WebLocale("Local", ""))
|
||||
assert.Equal(t, "local", WebLocale("local", "local"))
|
||||
})
|
||||
t.Run("Territory", func(t *testing.T) {
|
||||
assert.Equal(t, "cs-CZ", WebLocale("cs-CZ", ""))
|
||||
assert.Equal(t, "cs-CZ", WebLocale("cs_CZ", ""))
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
Browser = "Sec-Ch-Ua"
|
||||
Platform = "Sec-Ch-Ua-Platform"
|
||||
FetchMode = "Sec-Fetch-Mode"
|
||||
UserAgent = "User-Agent"
|
||||
)
|
||||
|
||||
// Standard IP addresses and placeholders.
|
||||
@@ -36,8 +37,9 @@ func ClientIP(c *gin.Context) (ip string) {
|
||||
return UnknownIP
|
||||
}
|
||||
|
||||
// UserAgent returns the user agent from the request context or an empty string if it is unknown.
|
||||
func UserAgent(c *gin.Context) string {
|
||||
// ClientUserAgent returns the client user agent string
|
||||
// from the request context, or an empty string if unknown.
|
||||
func ClientUserAgent(c *gin.Context) string {
|
||||
if c == nil {
|
||||
// Should never happen.
|
||||
return ""
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestRequest(t *testing.T) {
|
||||
Cookie: []string{"CockpitLang=en-us; Foo=Bar"},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, "TEST", UserAgent(c))
|
||||
assert.Equal(t, "TEST", ClientUserAgent(c))
|
||||
assert.Equal(t, "\"Chromium\";v=\"130\", \"Google Chrome\";v=\"130\", \"Not?A_Brand\";v=\"99\"", c.GetHeader(Browser))
|
||||
assert.Equal(t, "\"Linux\"", c.GetHeader(Platform))
|
||||
assert.Equal(t, "navigate", c.GetHeader(FetchMode))
|
||||
|
||||
Reference in New Issue
Block a user