Refactor Dockerignore and enhance Leaflet styles for improved map functionality

- Updated .dockerignore to streamline build context by ensuring unnecessary files are excluded.
- Refined CSS styles in leaflet-overrides.css to enhance visual consistency and user experience for map tooltips and popups.
- Improved map initialization and update handling in useMapApi and useMapUpdates composables for better performance and reliability.
This commit is contained in:
2026-03-04 18:16:41 +03:00
parent 3968bdc76f
commit 179357bc93
14 changed files with 3738 additions and 3738 deletions

View File

@@ -1,450 +1,450 @@
package handlers
import (
"archive/zip"
"encoding/json"
"net/http"
"strconv"
"strings"
"github.com/andyleap/hnh-map/internal/app"
)
type mapInfoJSON struct {
ID int `json:"ID"`
Name string `json:"Name"`
Hidden bool `json:"Hidden"`
Priority bool `json:"Priority"`
}
// APIAdminUsers handles GET/POST /map/api/admin/users.
func (h *Handlers) APIAdminUsers(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
if req.Method == http.MethodGet {
if h.requireAdmin(rw, req) == nil {
return
}
list, err := h.Admin.ListUsers(ctx)
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, list)
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
s := h.requireAdmin(rw, req)
if s == nil {
return
}
var body struct {
User string `json:"user"`
Pass string `json:"pass"`
Auths []string `json:"auths"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil || body.User == "" {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
adminCreated, err := h.Admin.CreateOrUpdateUser(ctx, body.User, body.Pass, body.Auths)
if err != nil {
HandleServiceError(rw, err)
return
}
if body.User == s.Username {
s.Auths = body.Auths
}
if adminCreated && s.Username == "admin" {
h.Auth.DeleteSession(ctx, s)
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminUserByName handles GET /map/api/admin/users/:name.
func (h *Handlers) APIAdminUserByName(rw http.ResponseWriter, req *http.Request, name string) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
auths, found, err := h.Admin.GetUser(req.Context(), name)
if err != nil {
HandleServiceError(rw, err)
return
}
out := struct {
Username string `json:"username"`
Auths []string `json:"auths"`
}{Username: name}
if found {
out.Auths = auths
}
JSON(rw, http.StatusOK, out)
}
// APIAdminUserDelete handles DELETE /map/api/admin/users/:name.
func (h *Handlers) APIAdminUserDelete(rw http.ResponseWriter, req *http.Request, name string) {
if !h.requireMethod(rw, req, http.MethodDelete) {
return
}
s := h.requireAdmin(rw, req)
if s == nil {
return
}
ctx := req.Context()
if err := h.Admin.DeleteUser(ctx, name); err != nil {
HandleServiceError(rw, err)
return
}
if name == s.Username {
h.Auth.DeleteSession(ctx, s)
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminSettingsGet handles GET /map/api/admin/settings.
func (h *Handlers) APIAdminSettingsGet(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
prefix, defaultHide, title, err := h.Admin.GetSettings(req.Context())
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, struct {
Prefix string `json:"prefix"`
DefaultHide bool `json:"defaultHide"`
Title string `json:"title"`
}{Prefix: prefix, DefaultHide: defaultHide, Title: title})
}
// APIAdminSettingsPost handles POST /map/api/admin/settings.
func (h *Handlers) APIAdminSettingsPost(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
var body struct {
Prefix *string `json:"prefix"`
DefaultHide *bool `json:"defaultHide"`
Title *string `json:"title"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if err := h.Admin.UpdateSettings(req.Context(), body.Prefix, body.DefaultHide, body.Title); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminMaps handles GET /map/api/admin/maps.
func (h *Handlers) APIAdminMaps(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
maps, err := h.Admin.ListMaps(req.Context())
if err != nil {
HandleServiceError(rw, err)
return
}
out := make([]mapInfoJSON, len(maps))
for i, m := range maps {
out[i] = mapInfoJSON{ID: m.ID, Name: m.Name, Hidden: m.Hidden, Priority: m.Priority}
}
JSON(rw, http.StatusOK, out)
}
// APIAdminMapByID handles POST /map/api/admin/maps/:id.
func (h *Handlers) APIAdminMapByID(rw http.ResponseWriter, req *http.Request, idStr string) {
id, err := strconv.Atoi(idStr)
if err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
var body struct {
Name string `json:"name"`
Hidden bool `json:"hidden"`
Priority bool `json:"priority"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if err := h.Admin.UpdateMap(req.Context(), id, body.Name, body.Hidden, body.Priority); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminMapToggleHidden handles POST /map/api/admin/maps/:id/toggle-hidden.
func (h *Handlers) APIAdminMapToggleHidden(rw http.ResponseWriter, req *http.Request, idStr string) {
id, err := strconv.Atoi(idStr)
if err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
mi, err := h.Admin.ToggleMapHidden(req.Context(), id)
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, mapInfoJSON{
ID: mi.ID,
Name: mi.Name,
Hidden: mi.Hidden,
Priority: mi.Priority,
})
}
// APIAdminWipe handles POST /map/api/admin/wipe.
func (h *Handlers) APIAdminWipe(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
if err := h.Admin.Wipe(req.Context()); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminWipeTile handles POST /map/api/admin/wipeTile.
func (h *Handlers) APIAdminWipeTile(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
mapid, err := strconv.Atoi(req.FormValue("map"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
x, err := strconv.Atoi(req.FormValue("x"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
y, err := strconv.Atoi(req.FormValue("y"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
if err := h.Admin.WipeTile(req.Context(), mapid, x, y); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminSetCoords handles POST /map/api/admin/setCoords.
func (h *Handlers) APIAdminSetCoords(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
mapid, err := strconv.Atoi(req.FormValue("map"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
fx, err := strconv.Atoi(req.FormValue("fx"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
fy, err := strconv.Atoi(req.FormValue("fy"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
tx, err := strconv.Atoi(req.FormValue("tx"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
ty, err := strconv.Atoi(req.FormValue("ty"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
if err := h.Admin.SetCoords(req.Context(), mapid, fx, fy, tx, ty); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminHideMarker handles POST /map/api/admin/hideMarker.
func (h *Handlers) APIAdminHideMarker(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
markerID := req.FormValue("id")
if markerID == "" {
JSONError(rw, http.StatusBadRequest, "missing id", "BAD_REQUEST")
return
}
if err := h.Admin.HideMarker(req.Context(), markerID); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminRebuildZooms handles POST /map/api/admin/rebuildZooms.
// It starts the rebuild in the background and returns 202 Accepted immediately.
func (h *Handlers) APIAdminRebuildZooms(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
h.Admin.StartRebuildZooms()
rw.WriteHeader(http.StatusAccepted)
}
// APIAdminRebuildZoomsStatus handles GET /map/api/admin/rebuildZooms/status.
// Returns {"running": true|false} so the client can poll until the rebuild finishes.
func (h *Handlers) APIAdminRebuildZoomsStatus(rw http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
JSONError(rw, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
return
}
if h.requireAdmin(rw, req) == nil {
return
}
running := h.Admin.RebuildZoomsRunning()
JSON(rw, http.StatusOK, map[string]bool{"running": running})
}
// APIAdminExport handles GET /map/api/admin/export.
func (h *Handlers) APIAdminExport(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
rw.Header().Set("Content-Type", "application/zip")
rw.Header().Set("Content-Disposition", `attachment; filename="griddata.zip"`)
if err := h.Export.Export(req.Context(), rw); err != nil {
HandleServiceError(rw, err)
}
}
// APIAdminMerge handles POST /map/api/admin/merge.
func (h *Handlers) APIAdminMerge(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
if err := req.ParseMultipartForm(app.MergeMaxMemory); err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
mergef, hdr, err := req.FormFile("merge")
if err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
zr, err := zip.NewReader(mergef, hdr.Size)
if err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
if err := h.Export.Merge(req.Context(), zr); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminRoute routes /map/api/admin/* sub-paths.
func (h *Handlers) APIAdminRoute(rw http.ResponseWriter, req *http.Request, path string) {
switch {
case path == "wipeTile":
h.APIAdminWipeTile(rw, req)
case path == "setCoords":
h.APIAdminSetCoords(rw, req)
case path == "hideMarker":
h.APIAdminHideMarker(rw, req)
case path == "users":
h.APIAdminUsers(rw, req)
case strings.HasPrefix(path, "users/"):
name := strings.TrimPrefix(path, "users/")
if name == "" {
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
return
}
if req.Method == http.MethodDelete {
h.APIAdminUserDelete(rw, req, name)
} else {
h.APIAdminUserByName(rw, req, name)
}
case path == "settings":
if req.Method == http.MethodGet {
h.APIAdminSettingsGet(rw, req)
} else {
h.APIAdminSettingsPost(rw, req)
}
case path == "maps":
h.APIAdminMaps(rw, req)
case strings.HasPrefix(path, "maps/"):
rest := strings.TrimPrefix(path, "maps/")
parts := strings.SplitN(rest, "/", 2)
idStr := parts[0]
if len(parts) == 2 && parts[1] == "toggle-hidden" {
h.APIAdminMapToggleHidden(rw, req, idStr)
return
}
if len(parts) == 1 {
h.APIAdminMapByID(rw, req, idStr)
return
}
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
case path == "wipe":
h.APIAdminWipe(rw, req)
case path == "rebuildZooms":
h.APIAdminRebuildZooms(rw, req)
case path == "rebuildZooms/status":
h.APIAdminRebuildZoomsStatus(rw, req)
case path == "export":
h.APIAdminExport(rw, req)
case path == "merge":
h.APIAdminMerge(rw, req)
default:
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
}
}
package handlers
import (
"archive/zip"
"encoding/json"
"net/http"
"strconv"
"strings"
"github.com/andyleap/hnh-map/internal/app"
)
type mapInfoJSON struct {
ID int `json:"ID"`
Name string `json:"Name"`
Hidden bool `json:"Hidden"`
Priority bool `json:"Priority"`
}
// APIAdminUsers handles GET/POST /map/api/admin/users.
func (h *Handlers) APIAdminUsers(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
if req.Method == http.MethodGet {
if h.requireAdmin(rw, req) == nil {
return
}
list, err := h.Admin.ListUsers(ctx)
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, list)
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
s := h.requireAdmin(rw, req)
if s == nil {
return
}
var body struct {
User string `json:"user"`
Pass string `json:"pass"`
Auths []string `json:"auths"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil || body.User == "" {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
adminCreated, err := h.Admin.CreateOrUpdateUser(ctx, body.User, body.Pass, body.Auths)
if err != nil {
HandleServiceError(rw, err)
return
}
if body.User == s.Username {
s.Auths = body.Auths
}
if adminCreated && s.Username == "admin" {
h.Auth.DeleteSession(ctx, s)
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminUserByName handles GET /map/api/admin/users/:name.
func (h *Handlers) APIAdminUserByName(rw http.ResponseWriter, req *http.Request, name string) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
auths, found, err := h.Admin.GetUser(req.Context(), name)
if err != nil {
HandleServiceError(rw, err)
return
}
out := struct {
Username string `json:"username"`
Auths []string `json:"auths"`
}{Username: name}
if found {
out.Auths = auths
}
JSON(rw, http.StatusOK, out)
}
// APIAdminUserDelete handles DELETE /map/api/admin/users/:name.
func (h *Handlers) APIAdminUserDelete(rw http.ResponseWriter, req *http.Request, name string) {
if !h.requireMethod(rw, req, http.MethodDelete) {
return
}
s := h.requireAdmin(rw, req)
if s == nil {
return
}
ctx := req.Context()
if err := h.Admin.DeleteUser(ctx, name); err != nil {
HandleServiceError(rw, err)
return
}
if name == s.Username {
h.Auth.DeleteSession(ctx, s)
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminSettingsGet handles GET /map/api/admin/settings.
func (h *Handlers) APIAdminSettingsGet(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
prefix, defaultHide, title, err := h.Admin.GetSettings(req.Context())
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, struct {
Prefix string `json:"prefix"`
DefaultHide bool `json:"defaultHide"`
Title string `json:"title"`
}{Prefix: prefix, DefaultHide: defaultHide, Title: title})
}
// APIAdminSettingsPost handles POST /map/api/admin/settings.
func (h *Handlers) APIAdminSettingsPost(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
var body struct {
Prefix *string `json:"prefix"`
DefaultHide *bool `json:"defaultHide"`
Title *string `json:"title"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if err := h.Admin.UpdateSettings(req.Context(), body.Prefix, body.DefaultHide, body.Title); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminMaps handles GET /map/api/admin/maps.
func (h *Handlers) APIAdminMaps(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
maps, err := h.Admin.ListMaps(req.Context())
if err != nil {
HandleServiceError(rw, err)
return
}
out := make([]mapInfoJSON, len(maps))
for i, m := range maps {
out[i] = mapInfoJSON{ID: m.ID, Name: m.Name, Hidden: m.Hidden, Priority: m.Priority}
}
JSON(rw, http.StatusOK, out)
}
// APIAdminMapByID handles POST /map/api/admin/maps/:id.
func (h *Handlers) APIAdminMapByID(rw http.ResponseWriter, req *http.Request, idStr string) {
id, err := strconv.Atoi(idStr)
if err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
var body struct {
Name string `json:"name"`
Hidden bool `json:"hidden"`
Priority bool `json:"priority"`
}
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if err := h.Admin.UpdateMap(req.Context(), id, body.Name, body.Hidden, body.Priority); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminMapToggleHidden handles POST /map/api/admin/maps/:id/toggle-hidden.
func (h *Handlers) APIAdminMapToggleHidden(rw http.ResponseWriter, req *http.Request, idStr string) {
id, err := strconv.Atoi(idStr)
if err != nil {
JSONError(rw, http.StatusBadRequest, "bad request", "BAD_REQUEST")
return
}
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
mi, err := h.Admin.ToggleMapHidden(req.Context(), id)
if err != nil {
HandleServiceError(rw, err)
return
}
JSON(rw, http.StatusOK, mapInfoJSON{
ID: mi.ID,
Name: mi.Name,
Hidden: mi.Hidden,
Priority: mi.Priority,
})
}
// APIAdminWipe handles POST /map/api/admin/wipe.
func (h *Handlers) APIAdminWipe(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
if err := h.Admin.Wipe(req.Context()); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminWipeTile handles POST /map/api/admin/wipeTile.
func (h *Handlers) APIAdminWipeTile(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
mapid, err := strconv.Atoi(req.FormValue("map"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
x, err := strconv.Atoi(req.FormValue("x"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
y, err := strconv.Atoi(req.FormValue("y"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
if err := h.Admin.WipeTile(req.Context(), mapid, x, y); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminSetCoords handles POST /map/api/admin/setCoords.
func (h *Handlers) APIAdminSetCoords(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
mapid, err := strconv.Atoi(req.FormValue("map"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
fx, err := strconv.Atoi(req.FormValue("fx"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
fy, err := strconv.Atoi(req.FormValue("fy"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
tx, err := strconv.Atoi(req.FormValue("tx"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
ty, err := strconv.Atoi(req.FormValue("ty"))
if err != nil {
JSONError(rw, http.StatusBadRequest, "coord parse failed", "BAD_REQUEST")
return
}
if err := h.Admin.SetCoords(req.Context(), mapid, fx, fy, tx, ty); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminHideMarker handles POST /map/api/admin/hideMarker.
func (h *Handlers) APIAdminHideMarker(rw http.ResponseWriter, req *http.Request) {
if h.requireAdmin(rw, req) == nil {
return
}
markerID := req.FormValue("id")
if markerID == "" {
JSONError(rw, http.StatusBadRequest, "missing id", "BAD_REQUEST")
return
}
if err := h.Admin.HideMarker(req.Context(), markerID); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminRebuildZooms handles POST /map/api/admin/rebuildZooms.
// It starts the rebuild in the background and returns 202 Accepted immediately.
func (h *Handlers) APIAdminRebuildZooms(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
h.Admin.StartRebuildZooms()
rw.WriteHeader(http.StatusAccepted)
}
// APIAdminRebuildZoomsStatus handles GET /map/api/admin/rebuildZooms/status.
// Returns {"running": true|false} so the client can poll until the rebuild finishes.
func (h *Handlers) APIAdminRebuildZoomsStatus(rw http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
JSONError(rw, http.StatusMethodNotAllowed, "method not allowed", "METHOD_NOT_ALLOWED")
return
}
if h.requireAdmin(rw, req) == nil {
return
}
running := h.Admin.RebuildZoomsRunning()
JSON(rw, http.StatusOK, map[string]bool{"running": running})
}
// APIAdminExport handles GET /map/api/admin/export.
func (h *Handlers) APIAdminExport(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodGet) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
rw.Header().Set("Content-Type", "application/zip")
rw.Header().Set("Content-Disposition", `attachment; filename="griddata.zip"`)
if err := h.Export.Export(req.Context(), rw); err != nil {
HandleServiceError(rw, err)
}
}
// APIAdminMerge handles POST /map/api/admin/merge.
func (h *Handlers) APIAdminMerge(rw http.ResponseWriter, req *http.Request) {
if !h.requireMethod(rw, req, http.MethodPost) {
return
}
if h.requireAdmin(rw, req) == nil {
return
}
if err := req.ParseMultipartForm(app.MergeMaxMemory); err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
mergef, hdr, err := req.FormFile("merge")
if err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
zr, err := zip.NewReader(mergef, hdr.Size)
if err != nil {
JSONError(rw, http.StatusBadRequest, "request error", "BAD_REQUEST")
return
}
if err := h.Export.Merge(req.Context(), zr); err != nil {
HandleServiceError(rw, err)
return
}
rw.WriteHeader(http.StatusOK)
}
// APIAdminRoute routes /map/api/admin/* sub-paths.
func (h *Handlers) APIAdminRoute(rw http.ResponseWriter, req *http.Request, path string) {
switch {
case path == "wipeTile":
h.APIAdminWipeTile(rw, req)
case path == "setCoords":
h.APIAdminSetCoords(rw, req)
case path == "hideMarker":
h.APIAdminHideMarker(rw, req)
case path == "users":
h.APIAdminUsers(rw, req)
case strings.HasPrefix(path, "users/"):
name := strings.TrimPrefix(path, "users/")
if name == "" {
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
return
}
if req.Method == http.MethodDelete {
h.APIAdminUserDelete(rw, req, name)
} else {
h.APIAdminUserByName(rw, req, name)
}
case path == "settings":
if req.Method == http.MethodGet {
h.APIAdminSettingsGet(rw, req)
} else {
h.APIAdminSettingsPost(rw, req)
}
case path == "maps":
h.APIAdminMaps(rw, req)
case strings.HasPrefix(path, "maps/"):
rest := strings.TrimPrefix(path, "maps/")
parts := strings.SplitN(rest, "/", 2)
idStr := parts[0]
if len(parts) == 2 && parts[1] == "toggle-hidden" {
h.APIAdminMapToggleHidden(rw, req, idStr)
return
}
if len(parts) == 1 {
h.APIAdminMapByID(rw, req, idStr)
return
}
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
case path == "wipe":
h.APIAdminWipe(rw, req)
case path == "rebuildZooms":
h.APIAdminRebuildZooms(rw, req)
case path == "rebuildZooms/status":
h.APIAdminRebuildZoomsStatus(rw, req)
case path == "export":
h.APIAdminExport(rw, req)
case path == "merge":
h.APIAdminMerge(rw, req)
default:
JSONError(rw, http.StatusNotFound, "not found", "NOT_FOUND")
}
}

File diff suppressed because it is too large Load Diff