package main import ( "net/http" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // PpCountry is the country aggregation row returned to the client: a // 2-letter ISO 3166-1 code, the user-scoped photo count, and a representative // thumb hash for the sidebar row. type PpCountry struct { Code string `json:"Code"` PhotoCount int `json:"PhotoCount"` Thumb string `json:"Thumb"` } // handleCountries aggregates photos.photo_country directly against // PhotoPrism's DB (no upstream proxy needed — this is a simple GROUP BY) // and scopes the result to the caller's BasePath, mirroring handleLabels. // // Route: GET /api/sidecar/countries (behind requireSession, ppDb != nil) func handleCountries(ppDb *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { basePath := ctxBasePath(c) type countryStat struct { Code string `gorm:"column:code"` Cnt int64 `gorm:"column:cnt"` ThumbHash string `gorm:"column:thumb_hash"` } var stats []countryStat query := ppDb.Table("photos p"). Select(`p.photo_country AS code, COUNT(DISTINCT p.id) AS cnt, COALESCE(MIN(f.file_hash), '') AS thumb_hash`). Joins(`LEFT JOIN files f ON f.photo_uid = p.photo_uid AND f.file_primary = 1 AND f.file_missing = 0`). Where("p.deleted_at IS NULL"). Where("p.photo_country != '' AND p.photo_country != 'zz'") if basePath != "" { prefix := basePath + "/%" query = query.Where("(p.photo_path = ? OR p.photo_path LIKE ?)", basePath, prefix) } if err := query. Group("p.photo_country"). Having("cnt > 0"). Order("cnt DESC"). Scan(&stats).Error; err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": "country stats query failed"}) return } out := make([]PpCountry, 0, len(stats)) for _, s := range stats { out = append(out, PpCountry{ Code: s.Code, PhotoCount: int(s.Cnt), Thumb: s.ThumbHash, }) } c.JSON(http.StatusOK, out) } }