Refactor frontend components and enhance API integration

- Updated frontend-nuxt.mdc to specify usage of composables for API calls.
- Added new AuthCard and ConfirmModal components for improved UI consistency.
- Introduced UserAvatar component for user profile display, replacing previous Gravatar implementation.
- Implemented useFormSubmit composable for handling form submissions with loading and error states.
- Enhanced vitest.config.ts to include coverage reporting for composables and components.
- Removed deprecated useAdminApi and useAuth composables to streamline API interactions.
- Updated login and setup pages to utilize new components and composables for better user experience.
This commit is contained in:
2026-03-04 00:14:05 +03:00
parent f6375e7d0f
commit 8f769543f4
34 changed files with 878 additions and 379 deletions

View File

@@ -20,6 +20,7 @@ type AdminService struct {
}
// NewAdminService creates an AdminService with the given store and map service.
// Uses direct args (two dependencies) rather than a deps struct.
func NewAdminService(st *store.Store, mapSvc *MapService) *AdminService {
return &AdminService{st: st, mapSvc: mapSvc}
}
@@ -37,19 +38,21 @@ func (s *AdminService) ListUsers(ctx context.Context) ([]string, error) {
}
// GetUser returns a user's permissions by username.
func (s *AdminService) GetUser(ctx context.Context, username string) (auths app.Auths, found bool) {
s.st.View(ctx, func(tx *bbolt.Tx) error {
func (s *AdminService) GetUser(ctx context.Context, username string) (auths app.Auths, found bool, err error) {
err = s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetUser(tx, username)
if raw == nil {
return nil
}
var u app.User
json.Unmarshal(raw, &u)
if err := json.Unmarshal(raw, &u); err != nil {
return err
}
auths = u.Auths
found = true
return nil
})
return auths, found
return auths, found, err
}
// CreateOrUpdateUser creates or updates a user.
@@ -60,7 +63,9 @@ func (s *AdminService) CreateOrUpdateUser(ctx context.Context, username string,
u := app.User{}
raw := s.st.GetUser(tx, username)
if raw != nil {
json.Unmarshal(raw, &u)
if err := json.Unmarshal(raw, &u); err != nil {
return err
}
}
if pass != "" {
hash, e := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
@@ -88,7 +93,9 @@ func (s *AdminService) DeleteUser(ctx context.Context, username string) error {
uRaw := s.st.GetUser(tx, username)
if uRaw != nil {
var u app.User
json.Unmarshal(uRaw, &u)
if err := json.Unmarshal(uRaw, &u); err != nil {
return err
}
for _, tok := range u.Tokens {
s.st.DeleteToken(tx, tok)
}
@@ -140,7 +147,9 @@ func (s *AdminService) ListMaps(ctx context.Context) ([]app.MapInfo, error) {
err := s.st.View(ctx, func(tx *bbolt.Tx) error {
return s.st.ForEachMap(tx, func(k, v []byte) error {
mi := app.MapInfo{}
json.Unmarshal(v, &mi)
if err := json.Unmarshal(v, &mi); err != nil {
return err
}
if id, err := strconv.Atoi(string(k)); err == nil {
mi.ID = id
}
@@ -152,18 +161,23 @@ func (s *AdminService) ListMaps(ctx context.Context) ([]app.MapInfo, error) {
}
// GetMap returns a map by ID.
func (s *AdminService) GetMap(ctx context.Context, id int) (*app.MapInfo, bool) {
func (s *AdminService) GetMap(ctx context.Context, id int) (*app.MapInfo, bool, error) {
var mi *app.MapInfo
s.st.View(ctx, func(tx *bbolt.Tx) error {
err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetMap(tx, id)
if raw != nil {
mi = &app.MapInfo{}
json.Unmarshal(raw, mi)
mi.ID = id
return json.Unmarshal(raw, mi)
}
return nil
})
return mi, mi != nil
if err != nil {
return nil, false, err
}
if mi != nil {
mi.ID = id
}
return mi, mi != nil, nil
}
// UpdateMap updates a map's name, hidden, and priority fields.
@@ -172,7 +186,9 @@ func (s *AdminService) UpdateMap(ctx context.Context, id int, name string, hidde
mi := app.MapInfo{}
raw := s.st.GetMap(tx, id)
if raw != nil {
json.Unmarshal(raw, &mi)
if err := json.Unmarshal(raw, &mi); err != nil {
return err
}
}
mi.ID = id
mi.Name = name
@@ -190,7 +206,9 @@ func (s *AdminService) ToggleMapHidden(ctx context.Context, id int) (*app.MapInf
raw := s.st.GetMap(tx, id)
mi = &app.MapInfo{}
if raw != nil {
json.Unmarshal(raw, mi)
if err := json.Unmarshal(raw, mi); err != nil {
return err
}
}
mi.ID = id
mi.Hidden = !mi.Hidden
@@ -340,7 +358,9 @@ func (s *AdminService) HideMarker(ctx context.Context, markerID string) error {
return nil
}
m := app.Marker{}
json.Unmarshal(raw, &m)
if err := json.Unmarshal(raw, &m); err != nil {
return err
}
m.Hidden = true
raw, _ = json.Marshal(m)
grid.Put(key, raw)

View File

@@ -52,9 +52,9 @@ func TestAdminGetUser_Found(t *testing.T) {
admin, st := newTestAdmin(t)
createUser(t, st, "alice", "pass", app.Auths{app.AUTH_MAP, app.AUTH_UPLOAD})
auths, found := admin.GetUser(context.Background(), "alice")
if !found {
t.Fatal("expected found")
auths, found, err := admin.GetUser(context.Background(), "alice")
if err != nil || !found {
t.Fatalf("expected found, err=%v", err)
}
if !auths.Has(app.AUTH_MAP) {
t.Fatal("expected map auth")
@@ -63,7 +63,10 @@ func TestAdminGetUser_Found(t *testing.T) {
func TestAdminGetUser_NotFound(t *testing.T) {
admin, _ := newTestAdmin(t)
_, found := admin.GetUser(context.Background(), "ghost")
_, found, err := admin.GetUser(context.Background(), "ghost")
if err != nil {
t.Fatal(err)
}
if found {
t.Fatal("expected not found")
}
@@ -78,9 +81,9 @@ func TestCreateOrUpdateUser_New(t *testing.T) {
t.Fatal(err)
}
auths, found := admin.GetUser(ctx, "bob")
if !found {
t.Fatal("expected user to exist")
auths, found, err := admin.GetUser(ctx, "bob")
if err != nil || !found {
t.Fatalf("expected user to exist, err=%v", err)
}
if !auths.Has(app.AUTH_MAP) {
t.Fatal("expected map auth")
@@ -97,9 +100,9 @@ func TestCreateOrUpdateUser_Update(t *testing.T) {
t.Fatal(err)
}
auths, found := admin.GetUser(ctx, "alice")
if !found {
t.Fatal("expected user")
auths, found, err := admin.GetUser(ctx, "alice")
if err != nil || !found {
t.Fatalf("expected user, err=%v", err)
}
if !auths.Has(app.AUTH_ADMIN) {
t.Fatal("expected admin auth after update")
@@ -139,9 +142,9 @@ func TestDeleteUser(t *testing.T) {
t.Fatal(err)
}
_, found := admin.GetUser(ctx, "alice")
if found {
t.Fatal("expected user to be deleted")
_, found, err := admin.GetUser(ctx, "alice")
if err != nil || found {
t.Fatalf("expected user to be deleted, err=%v", err)
}
}
@@ -210,9 +213,9 @@ func TestMapCRUD(t *testing.T) {
t.Fatal(err)
}
mi, found := admin.GetMap(ctx, 1)
if !found || mi == nil {
t.Fatal("expected map")
mi, found, err := admin.GetMap(ctx, 1)
if err != nil || !found || mi == nil {
t.Fatalf("expected map, err=%v", err)
}
if mi.Name != "world" {
t.Fatalf("expected world, got %s", mi.Name)
@@ -285,7 +288,10 @@ func TestWipe(t *testing.T) {
func TestGetMap_NotFound(t *testing.T) {
admin, _ := newTestAdmin(t)
_, found := admin.GetMap(context.Background(), 999)
_, found, err := admin.GetMap(context.Background(), 999)
if err != nil {
t.Fatal(err)
}
if found {
t.Fatal("expected not found")
}

View File

@@ -41,6 +41,7 @@ type AuthService struct {
}
// NewAuthService creates an AuthService with the given store.
// Uses direct args (single dependency) rather than a deps struct.
func NewAuthService(st *store.Store) *AuthService {
return &AuthService{st: st}
}
@@ -121,12 +122,14 @@ func (s *AuthService) CreateSession(ctx context.Context, username string, tempAd
// GetUser returns user if username/password match.
func (s *AuthService) GetUser(ctx context.Context, username, pass string) *app.User {
var u *app.User
s.st.View(ctx, func(tx *bbolt.Tx) error {
if err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetUser(tx, username)
if raw == nil {
return nil
}
json.Unmarshal(raw, &u)
if err := json.Unmarshal(raw, &u); err != nil {
return err
}
if u.Pass == nil {
u = nil
return nil
@@ -136,20 +139,26 @@ func (s *AuthService) GetUser(ctx context.Context, username, pass string) *app.U
return nil
}
return nil
})
}); err != nil {
return nil
}
return u
}
// GetUserByUsername returns user without password check (for OAuth-only check).
func (s *AuthService) GetUserByUsername(ctx context.Context, username string) *app.User {
var u *app.User
s.st.View(ctx, func(tx *bbolt.Tx) error {
if err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetUser(tx, username)
if raw != nil {
json.Unmarshal(raw, &u)
if err := json.Unmarshal(raw, &u); err != nil {
return err
}
}
return nil
})
}); err != nil {
return nil
}
return u
}
@@ -205,11 +214,13 @@ func GetBootstrapPassword() string {
// GetUserTokensAndPrefix returns tokens and config prefix for a user.
func (s *AuthService) GetUserTokensAndPrefix(ctx context.Context, username string) (tokens []string, prefix string) {
s.st.View(ctx, func(tx *bbolt.Tx) error {
_ = s.st.View(ctx, func(tx *bbolt.Tx) error {
uRaw := s.st.GetUser(tx, username)
if uRaw != nil {
var u app.User
json.Unmarshal(uRaw, &u)
if err := json.Unmarshal(uRaw, &u); err != nil {
return err
}
tokens = u.Tokens
}
if p := s.st.GetConfig(tx, "prefix"); p != nil {
@@ -232,7 +243,9 @@ func (s *AuthService) GenerateTokenForUser(ctx context.Context, username string)
uRaw := s.st.GetUser(tx, username)
u := app.User{}
if uRaw != nil {
json.Unmarshal(uRaw, &u)
if err := json.Unmarshal(uRaw, &u); err != nil {
return err
}
}
u.Tokens = append(u.Tokens, token)
tokens = u.Tokens
@@ -252,7 +265,9 @@ func (s *AuthService) SetUserPassword(ctx context.Context, username, pass string
uRaw := s.st.GetUser(tx, username)
u := app.User{}
if uRaw != nil {
json.Unmarshal(uRaw, &u)
if err := json.Unmarshal(uRaw, &u); err != nil {
return err
}
}
hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil {
@@ -297,7 +312,9 @@ func (s *AuthService) ValidateClientToken(ctx context.Context, token string) (st
return apperr.ErrUnauthorized
}
var u app.User
json.Unmarshal(uRaw, &u)
if err := json.Unmarshal(uRaw, &u); err != nil {
return err
}
if !u.Auths.Has(app.AUTH_UPLOAD) {
return apperr.ErrForbidden
}
@@ -401,7 +418,9 @@ func (s *AuthService) OAuthHandleCallback(ctx context.Context, provider, code, s
if raw == nil {
return apperr.ErrBadRequest
}
json.Unmarshal(raw, &st)
if err := json.Unmarshal(raw, &st); err != nil {
return err
}
return s.st.DeleteOAuthState(tx, state)
})
if err != nil || st.Provider == "" {
@@ -479,8 +498,8 @@ func (s *AuthService) findOrCreateOAuthUser(ctx context.Context, provider, sub,
err := s.st.Update(ctx, func(tx *bbolt.Tx) error {
_ = s.st.ForEachUser(tx, func(k, v []byte) error {
user := app.User{}
if json.Unmarshal(v, &user) != nil {
return nil
if err := json.Unmarshal(v, &user); err != nil {
return err
}
if user.OAuthLinks != nil && user.OAuthLinks[provider] == sub {
username = string(k)
@@ -491,7 +510,9 @@ func (s *AuthService) findOrCreateOAuthUser(ctx context.Context, provider, sub,
raw := s.st.GetUser(tx, username)
if raw != nil {
user := app.User{}
json.Unmarshal(raw, &user)
if err := json.Unmarshal(raw, &user); err != nil {
return err
}
if user.OAuthLinks == nil {
user.OAuthLinks = map[string]string{provider: sub}
} else {

View File

@@ -12,6 +12,7 @@ import (
"time"
"github.com/andyleap/hnh-map/internal/app"
"github.com/andyleap/hnh-map/internal/app/apperr"
"github.com/andyleap/hnh-map/internal/app/store"
"go.etcd.io/bbolt"
)
@@ -63,7 +64,7 @@ func (s *ClientService) Locate(ctx context.Context, gridID string) (string, erro
err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetGrid(tx, gridID)
if raw == nil {
return fmt.Errorf("grid not found")
return apperr.ErrNotFound
}
cur := app.GridData{}
if err := json.Unmarshal(raw, &cur); err != nil {
@@ -110,7 +111,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
gridRaw := grids.Get([]byte(grid))
if gridRaw != nil {
gd := app.GridData{}
json.Unmarshal(gridRaw, &gd)
if err := json.Unmarshal(gridRaw, &gd); err != nil {
return err
}
maps[gd.Map] = struct{ X, Y int }{gd.Coord.X - x, gd.Coord.Y - y}
}
}
@@ -152,7 +155,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
mi := app.MapInfo{}
mraw := mapB.Get([]byte(strconv.Itoa(id)))
if mraw != nil {
json.Unmarshal(mraw, &mi)
if err := json.Unmarshal(mraw, &mi); err != nil {
return err
}
}
if mi.Priority {
mapid = id
@@ -171,7 +176,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
for y, grid := range row {
cur := app.GridData{}
if curRaw := grids.Get([]byte(grid)); curRaw != nil {
json.Unmarshal(curRaw, &cur)
if err := json.Unmarshal(curRaw, &cur); err != nil {
return err
}
if time.Now().After(cur.NextUpdate) {
greq.GridRequests = append(greq.GridRequests, grid)
}
@@ -192,7 +199,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
if len(grup.Grids) >= 2 && len(grup.Grids[1]) >= 2 {
if curRaw := grids.Get([]byte(grup.Grids[1][1])); curRaw != nil {
cur := app.GridData{}
json.Unmarshal(curRaw, &cur)
if err := json.Unmarshal(curRaw, &cur); err != nil {
return err
}
greq.Map = cur.Map
greq.Coords = cur.Coord
}
@@ -200,7 +209,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
if len(maps) > 1 {
grids.ForEach(func(k, v []byte) error {
gd := app.GridData{}
json.Unmarshal(v, &gd)
if err := json.Unmarshal(v, &gd); err != nil {
return err
}
if gd.Map == mapid {
return nil
}
@@ -216,7 +227,9 @@ func (s *ClientService) ProcessGridUpdate(ctx context.Context, grup GridUpdate)
}
tileraw := zoom.Get([]byte(gd.Coord.Name()))
if tileraw != nil {
json.Unmarshal(tileraw, &td)
if err := json.Unmarshal(tileraw, &td); err != nil {
return err
}
}
gd.Map = mapid

View File

@@ -1,9 +1,14 @@
package services_test
import (
"context"
"encoding/json"
"testing"
"github.com/andyleap/hnh-map/internal/app"
"github.com/andyleap/hnh-map/internal/app/services"
"github.com/andyleap/hnh-map/internal/app/store"
"go.etcd.io/bbolt"
)
func TestFixMultipartContentType_NeedsQuoting(t *testing.T) {
@@ -30,3 +35,57 @@ func TestFixMultipartContentType_Normal(t *testing.T) {
t.Fatalf("expected unchanged, got %q", got)
}
}
func newTestClientService(t *testing.T) (*services.ClientService, *store.Store) {
t.Helper()
db := newTestDB(t)
st := store.New(db)
mapSvc := services.NewMapService(services.MapServiceDeps{
Store: st,
GridStorage: t.TempDir(),
GridUpdates: &app.Topic[app.TileData]{},
})
client := services.NewClientService(services.ClientServiceDeps{
Store: st,
MapSvc: mapSvc,
WithChars: func(fn func(chars map[string]app.Character)) { fn(map[string]app.Character{}) },
})
return client, st
}
func TestClientLocate_Found(t *testing.T) {
client, st := newTestClientService(t)
ctx := context.Background()
gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 2, Y: 3}}
raw, _ := json.Marshal(gd)
st.Update(ctx, func(tx *bbolt.Tx) error {
return st.PutGrid(tx, "g1", raw)
})
result, err := client.Locate(ctx, "g1")
if err != nil {
t.Fatal(err)
}
if result != "1;2;3" {
t.Fatalf("expected 1;2;3, got %q", result)
}
}
func TestClientLocate_NotFound(t *testing.T) {
client, _ := newTestClientService(t)
_, err := client.Locate(context.Background(), "ghost")
if err == nil {
t.Fatal("expected error for unknown grid")
}
}
func TestClientProcessGridUpdate_EmptyGrids(t *testing.T) {
client, _ := newTestClientService(t)
ctx := context.Background()
result, err := client.ProcessGridUpdate(ctx, services.GridUpdate{Grids: [][]string{}})
if err != nil {
t.Fatal(err)
}
if result == nil {
t.Fatal("expected non-nil result")
}
}

View File

@@ -30,6 +30,7 @@ type ExportService struct {
}
// NewExportService creates an ExportService with the given store and map service.
// Uses direct args (two dependencies) rather than a deps struct.
func NewExportService(st *store.Store, mapSvc *MapService) *ExportService {
return &ExportService{st: st, mapSvc: mapSvc}
}
@@ -190,7 +191,9 @@ func (s *ExportService) Merge(ctx context.Context, zr *zip.Reader) error {
gridRaw := grids.Get([]byte(gid))
if gridRaw != nil {
gd := app.GridData{}
json.Unmarshal(gridRaw, &gd)
if err := json.Unmarshal(gridRaw, &gd); err != nil {
return err
}
ops = append(ops, TileOp{
MapID: gd.Map,
X: gd.Coord.X,
@@ -265,7 +268,9 @@ func (s *ExportService) processMergeJSON(
gridRaw := grids.Get([]byte(v))
if gridRaw != nil {
gd := app.GridData{}
json.Unmarshal(gridRaw, &gd)
if err := json.Unmarshal(gridRaw, &gd); err != nil {
return err
}
existingMaps[gd.Map] = struct{ X, Y int }{gd.Coord.X - c.X, gd.Coord.Y - c.Y}
}
}
@@ -301,7 +306,9 @@ func (s *ExportService) processMergeJSON(
mi := app.MapInfo{}
mraw := mapB.Get([]byte(strconv.Itoa(id)))
if mraw != nil {
json.Unmarshal(mraw, &mi)
if err := json.Unmarshal(mraw, &mi); err != nil {
return err
}
}
if mi.Priority {
mapid = id
@@ -333,7 +340,9 @@ func (s *ExportService) processMergeJSON(
if len(existingMaps) > 1 {
grids.ForEach(func(k, v []byte) error {
gd := app.GridData{}
json.Unmarshal(v, &gd)
if err := json.Unmarshal(v, &gd); err != nil {
return err
}
if gd.Map == mapid {
return nil
}
@@ -349,7 +358,9 @@ func (s *ExportService) processMergeJSON(
}
tileraw := zoom.Get([]byte(gd.Coord.Name()))
if tileraw != nil {
json.Unmarshal(tileraw, &td)
if err := json.Unmarshal(tileraw, &td); err != nil {
return err
}
}
gd.Map = mapid

View File

@@ -0,0 +1,64 @@
package services_test
import (
"bytes"
"context"
"encoding/json"
"testing"
"github.com/andyleap/hnh-map/internal/app"
"github.com/andyleap/hnh-map/internal/app/services"
"github.com/andyleap/hnh-map/internal/app/store"
"go.etcd.io/bbolt"
)
func newTestExportService(t *testing.T) (*services.ExportService, *store.Store) {
t.Helper()
db := newTestDB(t)
st := store.New(db)
mapSvc := services.NewMapService(services.MapServiceDeps{
Store: st,
GridStorage: t.TempDir(),
GridUpdates: &app.Topic[app.TileData]{},
})
return services.NewExportService(st, mapSvc), st
}
func TestExport_EmptyDB(t *testing.T) {
export, _ := newTestExportService(t)
var buf bytes.Buffer
err := export.Export(context.Background(), &buf)
if err != nil {
t.Fatal(err)
}
if buf.Len() == 0 {
t.Fatal("expected non-empty zip output")
}
// ZIP magic number
if buf.Len() < 4 || buf.Bytes()[0] != 0x50 || buf.Bytes()[1] != 0x4b {
t.Fatal("expected valid zip header")
}
}
func TestExport_WithGrid(t *testing.T) {
export, st := newTestExportService(t)
ctx := context.Background()
gd := app.GridData{ID: "g1", Map: 1, Coord: app.Coord{X: 0, Y: 0}}
gdRaw, _ := json.Marshal(gd)
mi := app.MapInfo{ID: 1, Name: "test", Hidden: false}
miRaw, _ := json.Marshal(mi)
st.Update(ctx, func(tx *bbolt.Tx) error {
if err := st.PutGrid(tx, "g1", gdRaw); err != nil {
return err
}
return st.PutMap(tx, 1, miRaw)
})
var buf bytes.Buffer
err := export.Export(ctx, &buf)
if err != nil {
t.Fatal(err)
}
if buf.Len() < 4 {
t.Fatal("expected zip data")
}
}

View File

@@ -77,13 +77,17 @@ func (s *MapService) GetMarkers(ctx context.Context) ([]app.FrontendMarker, erro
}
return grid.ForEach(func(k, v []byte) error {
marker := app.Marker{}
json.Unmarshal(v, &marker)
if err := json.Unmarshal(v, &marker); err != nil {
return err
}
graw := grids.Get([]byte(marker.GridID))
if graw == nil {
return nil
}
g := app.GridData{}
json.Unmarshal(graw, &g)
if err := json.Unmarshal(graw, &g); err != nil {
return err
}
markers = append(markers, app.FrontendMarker{
Image: marker.Image,
Hidden: marker.Hidden,
@@ -111,7 +115,9 @@ func (s *MapService) GetMaps(ctx context.Context, showHidden bool) (map[int]*app
return nil
}
mi := &app.MapInfo{}
json.Unmarshal(v, mi)
if err := json.Unmarshal(v, mi); err != nil {
return err
}
if mi.Hidden && !showHidden {
return nil
}
@@ -165,14 +171,16 @@ func (s *MapService) GetGrid(ctx context.Context, id string) (*app.GridData, err
// GetTile returns a tile by map ID, coordinate, and zoom level.
func (s *MapService) GetTile(ctx context.Context, mapID int, c app.Coord, zoom int) *app.TileData {
var td *app.TileData
s.st.View(ctx, func(tx *bbolt.Tx) error {
if err := s.st.View(ctx, func(tx *bbolt.Tx) error {
raw := s.st.GetTile(tx, mapID, zoom, c.Name())
if raw != nil {
td = &app.TileData{}
json.Unmarshal(raw, td)
return json.Unmarshal(raw, td)
}
return nil
})
}); err != nil {
return nil
}
return td
}
@@ -259,7 +267,9 @@ func (s *MapService) RebuildZooms(ctx context.Context) error {
}
b.ForEach(func(k, v []byte) error {
grid := app.GridData{}
json.Unmarshal(v, &grid)
if err := json.Unmarshal(v, &grid); err != nil {
return err
}
needProcess[zoomproc{grid.Coord.Parent(), grid.Map}] = struct{}{}
saveGrid[zoomproc{grid.Coord, grid.Map}] = grid.ID
return nil
@@ -318,7 +328,9 @@ func (s *MapService) GetAllTileCache(ctx context.Context) []TileCache {
s.st.View(ctx, func(tx *bbolt.Tx) error {
return s.st.ForEachTile(tx, func(mapK, zoomK, coordK, v []byte) error {
td := app.TileData{}
json.Unmarshal(v, &td)
if err := json.Unmarshal(v, &td); err != nil {
return err
}
cache = append(cache, TileCache{
M: td.MapID,
X: td.Coord.X,