Files
galaxy-game/backend/internal/server/handlers_admin_geo.go
2026-05-06 10:14:55 +03:00

99 lines
2.8 KiB
Go

package server
import (
"context"
"net/http"
"galaxy/backend/internal/geo"
"galaxy/backend/internal/server/handlers"
"galaxy/backend/internal/server/httperr"
"galaxy/backend/internal/telemetry"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
)
// AdminGeoLister is the narrow contract the admin geo handler needs
// from the geo domain. `*geo.Service` satisfies it directly; tests
// pass a recording fake.
type AdminGeoLister interface {
ListUserCounters(ctx context.Context, userID uuid.UUID) ([]geo.CountryCounter, error)
}
// AdminGeoHandlers groups the admin-side geo-counter handlers under
// `/api/v1/admin/geo/*`.
type AdminGeoHandlers struct {
svc AdminGeoLister
logger *zap.Logger
}
// NewAdminGeoHandlers constructs the handler set. svc may be nil — in
// that case every handler returns 501 not_implemented, matching the
// pre-Stage-5.8 placeholder behaviour. logger may also be nil; zap.NewNop
// is used in that case.
func NewAdminGeoHandlers(svc AdminGeoLister, logger *zap.Logger) *AdminGeoHandlers {
if logger == nil {
logger = zap.NewNop()
}
return &AdminGeoHandlers{svc: svc, logger: logger.Named("http.admin.geo")}
}
// adminGeoCountryWire mirrors `GeoCountryCounter` from `openapi.yaml`.
type adminGeoCountryWire struct {
Country string `json:"country"`
Count int64 `json:"count"`
LastSeenAt *string `json:"last_seen_at,omitempty"`
}
// adminGeoListWire mirrors `GeoCountryCounterList` from `openapi.yaml`.
type adminGeoListWire struct {
UserID string `json:"user_id"`
Items []adminGeoCountryWire `json:"items"`
}
// ListUserCountries handles GET /api/v1/admin/geo/users/{user_id}/countries.
func (h *AdminGeoHandlers) ListUserCountries() gin.HandlerFunc {
if h.svc == nil {
return handlers.NotImplemented("adminGeoListUserCountries")
}
return func(c *gin.Context) {
userID, ok := parseUserIDParam(c)
if !ok {
return
}
ctx := c.Request.Context()
entries, err := h.svc.ListUserCounters(ctx, userID)
if err != nil {
h.logger.Error("admin geo list user countries failed",
append(telemetry.TraceFieldsFromContext(ctx),
zap.String("user_id", userID.String()),
zap.Error(err),
)...,
)
httperr.Abort(c, http.StatusInternalServerError, httperr.CodeInternalError, "service error")
return
}
c.JSON(http.StatusOK, geoCountersToWire(userID, entries))
}
}
func geoCountersToWire(userID uuid.UUID, entries []geo.CountryCounter) adminGeoListWire {
out := adminGeoListWire{
UserID: userID.String(),
Items: make([]adminGeoCountryWire, 0, len(entries)),
}
for _, e := range entries {
item := adminGeoCountryWire{
Country: e.Country,
Count: e.Count,
}
if e.LastSeenAt != nil {
formatted := e.LastSeenAt.UTC().Format("2006-01-02T15:04:05.000Z07:00")
item.LastSeenAt = &formatted
}
out.Items = append(out.Items, item)
}
return out
}