Files
hnh-map/internal/app/admin.go
Nikolay Tatarinov 605a31567e Add initial project structure with backend and frontend setup
- Created backend structure with Go, including main application logic and API endpoints.
- Added Docker support for both development and production environments.
- Introduced frontend using Nuxt 3 with Tailwind CSS for styling.
- Included configuration files for Docker and environment variables.
- Established basic documentation for contributing, development, and deployment processes.
- Set up .gitignore and .dockerignore files to manage ignored files in the repository.
2026-02-24 22:27:05 +03:00

1125 lines
26 KiB
Go

package app
import (
"archive/zip"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"go.etcd.io/bbolt"
"golang.org/x/crypto/bcrypt"
)
func (a *App) admin(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
users := []string{}
prefix := ""
maps := []MapInfo{}
defaultHide := false
a.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("users"))
if b == nil {
return nil
}
config := tx.Bucket([]byte("config"))
if config != nil {
prefix = string(config.Get([]byte("prefix")))
defaultHide = config.Get([]byte("defaultHide")) != nil
}
mapB := tx.Bucket([]byte("maps"))
if mapB != nil {
mapB.ForEach(func(k, v []byte) error {
mi := MapInfo{}
json.Unmarshal(v, &mi)
maps = append(maps, mi)
return nil
})
}
return b.ForEach(func(k, v []byte) error {
users = append(users, string(k))
return nil
})
})
a.ExecuteTemplate(rw, "admin/index.tmpl", struct {
Page Page
Session *Session
Users []string
Prefix string
DefaultHide bool
Maps []MapInfo
}{
Page: a.getPage(req),
Session: s,
Users: users,
Prefix: prefix,
DefaultHide: defaultHide,
Maps: maps,
})
}
func (a *App) adminUser(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
if req.Method == "POST" {
req.ParseForm()
username := req.FormValue("user")
password := req.FormValue("pass")
auths := req.Form["auths"]
tempAdmin := false
a.db.Update(func(tx *bbolt.Tx) error {
users, err := tx.CreateBucketIfNotExists([]byte("users"))
if err != nil {
return err
}
if s.Username == "admin" && users.Get([]byte("admin")) == nil {
tempAdmin = true
}
u := User{}
raw := users.Get([]byte(username))
if raw != nil {
json.Unmarshal(raw, &u)
}
if password != "" {
u.Pass, _ = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
}
u.Auths = auths
raw, _ = json.Marshal(u)
users.Put([]byte(username), raw)
return nil
})
if username == s.Username {
s.Auths = auths
}
if tempAdmin {
a.deleteSession(s)
}
http.Redirect(rw, req, "/admin", 302)
return
}
user := req.FormValue("user")
u := User{}
a.db.View(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("users"))
if b == nil {
return nil
}
userRaw := b.Get([]byte(user))
if userRaw == nil {
return nil
}
return json.Unmarshal(userRaw, &u)
})
a.ExecuteTemplate(rw, "admin/user.tmpl", struct {
Page Page
Session *Session
User User
Username string
}{
Page: a.getPage(req),
Session: s,
User: u,
Username: user,
})
}
func (a *App) wipe(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
err := a.db.Update(func(tx *bbolt.Tx) error {
if tx.Bucket([]byte("grids")) != nil {
err := tx.DeleteBucket([]byte("grids"))
if err != nil {
return err
}
}
if tx.Bucket([]byte("markers")) != nil {
err := tx.DeleteBucket([]byte("markers"))
if err != nil {
return err
}
}
if tx.Bucket([]byte("tiles")) != nil {
err := tx.DeleteBucket([]byte("tiles"))
if err != nil {
return err
}
}
if tx.Bucket([]byte("maps")) != nil {
err := tx.DeleteBucket([]byte("maps"))
if err != nil {
return err
}
}
return nil
})
if err != nil {
log.Println(err)
}
/*for z := 0; z <= 5; z++ {
os.RemoveAll(fmt.Sprintf("%s/%d", a.gridStorage, z))
}*/
http.Redirect(rw, req, "/admin/", 302)
}
func (a *App) setPrefix(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
a.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("config"))
if err != nil {
return err
}
return b.Put([]byte("prefix"), []byte(req.FormValue("prefix")))
})
http.Redirect(rw, req, "/admin/", 302)
}
func (a *App) setDefaultHide(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
a.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("config"))
if err != nil {
return err
}
if req.FormValue("defaultHide") != "" {
return b.Put([]byte("defaultHide"), []byte(req.FormValue("defaultHide")))
} else {
return b.Delete([]byte("defaultHide"))
}
})
http.Redirect(rw, req, "/admin/", 302)
}
func (a *App) setTitle(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
a.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("config"))
if err != nil {
return err
}
return b.Put([]byte("title"), []byte(req.FormValue("title")))
})
http.Redirect(rw, req, "/admin/", 302)
}
type zoomproc struct {
c Coord
m int
}
func (a *App) doRebuildZooms() {
needProcess := map[zoomproc]struct{}{}
saveGrid := map[zoomproc]string{}
a.db.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket([]byte("grids"))
if b == nil {
return nil
}
b.ForEach(func(k, v []byte) error {
grid := GridData{}
json.Unmarshal(v, &grid)
needProcess[zoomproc{grid.Coord.Parent(), grid.Map}] = struct{}{}
saveGrid[zoomproc{grid.Coord, grid.Map}] = grid.ID
return nil
})
tx.DeleteBucket([]byte("tiles"))
return nil
})
for g, id := range saveGrid {
f := fmt.Sprintf("%s/grids/%s.png", a.gridStorage, id)
if _, err := os.Stat(f); err != nil {
continue
}
a.SaveTile(g.m, g.c, 0, fmt.Sprintf("grids/%s.png", id), time.Now().UnixNano())
}
for z := 1; z <= 5; z++ {
process := needProcess
needProcess = map[zoomproc]struct{}{}
for p := range process {
a.updateZoomLevel(p.m, p.c, z)
needProcess[zoomproc{p.c.Parent(), p.m}] = struct{}{}
}
}
}
func (a *App) rebuildZooms(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
a.doRebuildZooms()
http.Redirect(rw, req, "/admin/", 302)
}
func (a *App) deleteUser(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
username := req.FormValue("user")
a.db.Update(func(tx *bbolt.Tx) error {
users, err := tx.CreateBucketIfNotExists([]byte("users"))
if err != nil {
return err
}
u := User{}
raw := users.Get([]byte(username))
if raw != nil {
json.Unmarshal(raw, &u)
}
tokens, err := tx.CreateBucketIfNotExists([]byte("tokens"))
if err != nil {
return err
}
for _, tok := range u.Tokens {
err = tokens.Delete([]byte(tok))
if err != nil {
return err
}
}
err = users.Delete([]byte(username))
if err != nil {
return err
}
return nil
})
if username == s.Username {
a.deleteSession(s)
}
http.Redirect(rw, req, "/admin", 302)
return
}
var errFound = errors.New("found tile")
func (a *App) wipeTile(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
mraw := req.FormValue("map")
mapid, err := strconv.Atoi(mraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
xraw := req.FormValue("x")
x, err := strconv.Atoi(xraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
yraw := req.FormValue("y")
y, err := strconv.Atoi(yraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
c := Coord{
X: x,
Y: y,
}
a.db.Update(func(tx *bbolt.Tx) error {
grids := tx.Bucket([]byte("grids"))
if grids == nil {
return nil
}
ids := [][]byte{}
err := grids.ForEach(func(k, v []byte) error {
g := GridData{}
err := json.Unmarshal(v, &g)
if err != nil {
return err
}
if g.Coord == c && g.Map == mapid {
ids = append(ids, k)
}
return nil
})
if err != nil {
return err
}
for _, id := range ids {
grids.Delete(id)
}
return nil
})
a.SaveTile(mapid, c, 0, "", -1)
for z := 1; z <= 5; z++ {
c = c.Parent()
a.updateZoomLevel(mapid, c, z)
}
rw.WriteHeader(200)
}
func (a *App) setCoords(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
mraw := req.FormValue("map")
mapid, err := strconv.Atoi(mraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
fxraw := req.FormValue("fx")
fx, err := strconv.Atoi(fxraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
fyraw := req.FormValue("fy")
fy, err := strconv.Atoi(fyraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
fc := Coord{
X: fx,
Y: fy,
}
txraw := req.FormValue("tx")
tx, err := strconv.Atoi(txraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
tyraw := req.FormValue("ty")
ty, err := strconv.Atoi(tyraw)
if err != nil {
http.Error(rw, "coord parse failed", http.StatusBadRequest)
return
}
tc := Coord{
X: tx,
Y: ty,
}
diff := Coord{
X: tc.X - fc.X,
Y: tc.Y - fc.Y,
}
tds := []*TileData{}
a.db.Update(func(tx *bbolt.Tx) error {
grids := tx.Bucket([]byte("grids"))
if grids == nil {
return nil
}
tiles := tx.Bucket([]byte("tiles"))
if tiles == nil {
return nil
}
mapZooms := tiles.Bucket([]byte(strconv.Itoa(mapid)))
if mapZooms == nil {
return nil
}
mapTiles := mapZooms.Bucket([]byte("0"))
err := grids.ForEach(func(k, v []byte) error {
g := GridData{}
err := json.Unmarshal(v, &g)
if err != nil {
return err
}
if g.Map == mapid {
g.Coord.X += diff.X
g.Coord.Y += diff.Y
raw, _ := json.Marshal(g)
grids.Put(k, raw)
}
return nil
})
if err != nil {
return err
}
err = mapTiles.ForEach(func(k, v []byte) error {
td := &TileData{}
err := json.Unmarshal(v, &td)
if err != nil {
return err
}
td.Coord.X += diff.X
td.Coord.Y += diff.Y
tds = append(tds, td)
return nil
})
if err != nil {
return err
}
err = tiles.DeleteBucket([]byte(strconv.Itoa(mapid)))
if err != nil {
return err
}
return nil
})
needProcess := map[zoomproc]struct{}{}
for _, td := range tds {
a.SaveTile(td.MapID, td.Coord, td.Zoom, td.File, time.Now().UnixNano())
needProcess[zoomproc{c: Coord{X: td.Coord.X, Y: td.Coord.Y}.Parent(), m: td.MapID}] = struct{}{}
}
for z := 1; z <= 5; z++ {
process := needProcess
needProcess = map[zoomproc]struct{}{}
for p := range process {
a.updateZoomLevel(p.m, p.c, z)
needProcess[zoomproc{p.c.Parent(), p.m}] = struct{}{}
}
}
rw.WriteHeader(200)
}
func (a *App) backup(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
return
}
rw.Header().Set("Content-Type", "application/zip")
rw.Header().Set("Content-Disposition", "attachment; filename=\"backup.zip\"")
zw := zip.NewWriter(rw)
defer zw.Close()
err := a.db.Update(func(tx *bbolt.Tx) error {
w, err := zw.Create("grids.db")
if err != nil {
return err
}
err = tx.Copy(w)
if err != nil {
return err
}
tiles := tx.Bucket([]byte("tiles"))
if tiles == nil {
return nil
}
zoom := tiles.Bucket([]byte("0"))
if zoom == nil {
return nil
}
return zoom.ForEach(func(k, v []byte) error {
td := TileData{}
json.Unmarshal(v, &td)
if td.File == "" {
return nil
}
f, err := os.Open(a.gridStorage + "/" + td.File)
if err != nil {
return nil
}
w, err := zw.Create(td.File)
f.Close()
if err != nil {
return err
}
_, err = io.Copy(w, f)
return err
})
})
if err != nil {
log.Println(err)
}
}
type mapData struct {
Grids map[string]string
Markers map[string][]Marker
}
func (a *App) export(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
return
}
rw.Header().Set("Content-Type", "application/zip")
rw.Header().Set("Content-Disposition", "attachment; filename=\"griddata.zip\"")
zw := zip.NewWriter(rw)
defer zw.Close()
err := a.db.Update(func(tx *bbolt.Tx) error {
maps := map[int]mapData{}
gridMap := map[string]int{}
grids := tx.Bucket([]byte("grids"))
if grids == nil {
return nil
}
tiles := tx.Bucket([]byte("tiles"))
if tiles == nil {
return nil
}
err := grids.ForEach(func(k, v []byte) error {
gd := GridData{}
err := json.Unmarshal(v, &gd)
if err != nil {
return err
}
md, ok := maps[gd.Map]
if !ok {
md = mapData{
Grids: map[string]string{},
Markers: map[string][]Marker{},
}
maps[gd.Map] = md
}
md.Grids[gd.Coord.Name()] = gd.ID
gridMap[gd.ID] = gd.Map
mapb := tiles.Bucket([]byte(strconv.Itoa(gd.Map)))
if mapb == nil {
return nil
}
zoom := mapb.Bucket([]byte("0"))
if zoom == nil {
return nil
}
tdraw := zoom.Get([]byte(gd.Coord.Name()))
if tdraw == nil {
return nil
}
td := TileData{}
err = json.Unmarshal(tdraw, &td)
if err != nil {
return err
}
w, err := zw.Create(fmt.Sprintf("%d/%s.png", gd.Map, gd.ID))
if err != nil {
return err
}
f, err := os.Open(filepath.Join(a.gridStorage, td.File))
if err != nil {
return err
}
_, err = io.Copy(w, f)
f.Close()
return err
})
if err != nil {
return err
}
err = func() error {
markersb := tx.Bucket([]byte("markers"))
if markersb == nil {
return nil
}
markersgrid := markersb.Bucket([]byte("grid"))
if markersgrid == nil {
return nil
}
return markersgrid.ForEach(func(k, v []byte) error {
marker := Marker{}
err := json.Unmarshal(v, &marker)
if err != nil {
return nil
}
if _, ok := maps[gridMap[marker.GridID]]; ok {
maps[gridMap[marker.GridID]].Markers[marker.GridID] = append(maps[gridMap[marker.GridID]].Markers[marker.GridID], marker)
}
return nil
})
}()
if err != nil {
return err
}
for mapid, mapdata := range maps {
w, err := zw.Create(fmt.Sprintf("%d/grids.json", mapid))
if err != nil {
return err
}
json.NewEncoder(w).Encode(mapdata)
}
return nil
})
if err != nil {
log.Println(err)
}
}
func (a *App) hideMarker(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
err := a.db.Update(func(tx *bbolt.Tx) error {
mb, err := tx.CreateBucketIfNotExists([]byte("markers"))
if err != nil {
return err
}
grid, err := mb.CreateBucketIfNotExists([]byte("grid"))
if err != nil {
return err
}
idB, err := mb.CreateBucketIfNotExists([]byte("id"))
if err != nil {
return err
}
key := idB.Get([]byte(req.FormValue("id")))
if key == nil {
return fmt.Errorf("Could not find key %s", req.FormValue("id"))
}
raw := grid.Get(key)
if raw == nil {
return fmt.Errorf("Could not find key %s", string(key))
}
m := Marker{}
json.Unmarshal(raw, &m)
m.Hidden = true
raw, _ = json.Marshal(m)
grid.Put(key, raw)
return nil
})
if err != nil {
log.Println(err)
}
return
}
func (a *App) merge(rw http.ResponseWriter, req *http.Request) {
if s := a.getSession(req); s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Error(rw, "Unauthorized", http.StatusUnauthorized)
return
}
err := req.ParseMultipartForm(1024 * 1024 * 500)
if err != nil {
log.Println(err)
http.Error(rw, "internal error", http.StatusInternalServerError)
return
}
mergef, hdr, err := req.FormFile("merge")
if err != nil {
log.Println(err)
http.Error(rw, "request error", http.StatusBadRequest)
return
}
zr, err := zip.NewReader(mergef, hdr.Size)
if err != nil {
log.Println(err)
http.Error(rw, "request error", http.StatusBadRequest)
return
}
ops := []struct {
mapid int
x, y int
f string
}{}
newTiles := map[string]struct{}{}
err = a.db.Update(func(tx *bbolt.Tx) error {
grids, err := tx.CreateBucketIfNotExists([]byte("grids"))
if err != nil {
return err
}
tiles, err := tx.CreateBucketIfNotExists([]byte("tiles"))
if err != nil {
return err
}
mb, err := tx.CreateBucketIfNotExists([]byte("markers"))
if err != nil {
return err
}
mgrid, err := mb.CreateBucketIfNotExists([]byte("grid"))
if err != nil {
return err
}
idB, err := mb.CreateBucketIfNotExists([]byte("id"))
if err != nil {
return err
}
configb, err := tx.CreateBucketIfNotExists([]byte("config"))
if err != nil {
return err
}
for _, fhdr := range zr.File {
if strings.HasSuffix(fhdr.Name, ".json") {
f, err := fhdr.Open()
if err != nil {
return err
}
md := mapData{}
err = json.NewDecoder(f).Decode(&md)
if err != nil {
return err
}
for _, ms := range md.Markers {
for _, mraw := range ms {
key := []byte(fmt.Sprintf("%s_%d_%d", mraw.GridID, mraw.Position.X, mraw.Position.Y))
if mgrid.Get(key) != nil {
continue
}
if mraw.Image == "" {
mraw.Image = "gfx/terobjs/mm/custom"
}
id, err := idB.NextSequence()
if err != nil {
return err
}
idKey := []byte(strconv.Itoa(int(id)))
m := Marker{
Name: mraw.Name,
ID: int(id),
GridID: mraw.GridID,
Position: Position{
X: mraw.Position.X,
Y: mraw.Position.Y,
},
Image: mraw.Image,
}
raw, _ := json.Marshal(m)
mgrid.Put(key, raw)
idB.Put(idKey, key)
}
}
mapB, err := tx.CreateBucketIfNotExists([]byte("maps"))
if err != nil {
return err
}
newGrids := map[Coord]string{}
maps := map[int]struct{ X, Y int }{}
for k, v := range md.Grids {
c := Coord{}
_, err := fmt.Sscanf(k, "%d_%d", &c.X, &c.Y)
if err != nil {
return err
}
newGrids[c] = v
gridRaw := grids.Get([]byte(v))
if gridRaw != nil {
gd := GridData{}
json.Unmarshal(gridRaw, &gd)
maps[gd.Map] = struct{ X, Y int }{gd.Coord.X - c.X, gd.Coord.Y - c.Y}
}
}
if len(maps) == 0 {
seq, err := mapB.NextSequence()
if err != nil {
return err
}
mi := MapInfo{
ID: int(seq),
Name: strconv.Itoa(int(seq)),
Hidden: configb.Get([]byte("defaultHide")) != nil,
}
raw, _ := json.Marshal(mi)
err = mapB.Put([]byte(strconv.Itoa(int(seq))), raw)
if err != nil {
return err
}
for c, grid := range newGrids {
cur := GridData{}
cur.ID = grid
cur.Map = int(seq)
cur.Coord = c
raw, err := json.Marshal(cur)
if err != nil {
return err
}
grids.Put([]byte(grid), raw)
}
continue
}
mapid := -1
offset := struct{ X, Y int }{}
for id, off := range maps {
mi := MapInfo{}
mraw := mapB.Get([]byte(strconv.Itoa(id)))
if mraw != nil {
json.Unmarshal(mraw, &mi)
}
if mi.Priority {
mapid = id
offset = off
break
}
if id < mapid || mapid == -1 {
mapid = id
offset = off
}
}
for c, grid := range newGrids {
cur := GridData{}
if curRaw := grids.Get([]byte(grid)); curRaw != nil {
continue
}
cur.ID = grid
cur.Map = mapid
cur.Coord.X = c.X + offset.X
cur.Coord.Y = c.Y + offset.Y
raw, err := json.Marshal(cur)
if err != nil {
return err
}
grids.Put([]byte(grid), raw)
}
if len(maps) > 1 {
grids.ForEach(func(k, v []byte) error {
gd := GridData{}
json.Unmarshal(v, &gd)
if gd.Map == mapid {
return nil
}
if merge, ok := maps[gd.Map]; ok {
var td *TileData
mapb, err := tiles.CreateBucketIfNotExists([]byte(strconv.Itoa(gd.Map)))
if err != nil {
return err
}
zoom, err := mapb.CreateBucketIfNotExists([]byte(strconv.Itoa(0)))
if err != nil {
return err
}
tileraw := zoom.Get([]byte(gd.Coord.Name()))
if tileraw != nil {
json.Unmarshal(tileraw, &td)
}
gd.Map = mapid
gd.Coord.X += offset.X - merge.X
gd.Coord.Y += offset.Y - merge.Y
raw, _ := json.Marshal(gd)
if td != nil {
ops = append(ops, struct {
mapid int
x int
y int
f string
}{
mapid: mapid,
x: gd.Coord.X,
y: gd.Coord.Y,
f: td.File,
})
}
grids.Put(k, raw)
}
return nil
})
}
for mergeid, merge := range maps {
if mapid == mergeid {
continue
}
mapB.Delete([]byte(strconv.Itoa(mergeid)))
log.Println("Reporting merge", mergeid, mapid)
a.reportMerge(mergeid, mapid, Coord{X: offset.X - merge.X, Y: offset.Y - merge.Y})
}
} else if strings.HasSuffix(fhdr.Name, ".png") {
os.MkdirAll(filepath.Join(a.gridStorage, "grids"), 0777)
f, err := os.Create(filepath.Join(a.gridStorage, "grids", filepath.Base(fhdr.Name)))
if err != nil {
return err
}
r, err := fhdr.Open()
if err != nil {
f.Close()
return err
}
io.Copy(f, r)
r.Close()
f.Close()
newTiles[strings.TrimSuffix(filepath.Base(fhdr.Name), ".png")] = struct{}{}
}
}
for gid := range newTiles {
gridRaw := grids.Get([]byte(gid))
if gridRaw != nil {
gd := GridData{}
json.Unmarshal(gridRaw, &gd)
ops = append(ops, struct {
mapid int
x int
y int
f string
}{
mapid: gd.Map,
x: gd.Coord.X,
y: gd.Coord.Y,
f: filepath.Join("grids", gid+".png"),
})
}
}
return nil
})
if err != nil {
log.Println(err)
http.Error(rw, "internal error", http.StatusInternalServerError)
return
}
for _, op := range ops {
a.SaveTile(op.mapid, Coord{X: op.x, Y: op.y}, 0, op.f, time.Now().UnixNano())
}
a.doRebuildZooms()
if !strings.HasPrefix(req.URL.Path, "/map/api") {
http.Redirect(rw, req, "/admin/", 302)
}
}
func (a *App) adminICMap(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
mraw := req.FormValue("map")
mapid, err := strconv.Atoi(mraw)
if err != nil {
http.Error(rw, "map parse failed", http.StatusBadRequest)
return
}
action := req.FormValue("action")
a.db.Update(func(tx *bbolt.Tx) error {
maps, err := tx.CreateBucketIfNotExists([]byte("maps"))
if err != nil {
return err
}
rawmap := maps.Get([]byte(strconv.Itoa(mapid)))
mapinfo := MapInfo{}
if rawmap != nil {
json.Unmarshal(rawmap, &mapinfo)
}
switch action {
case "toggle-hidden":
mapinfo.Hidden = !mapinfo.Hidden
a.ExecuteTemplate(rw, "admin/index.tmpl:toggle-hidden", mapinfo)
}
rawmap, err = json.Marshal(mapinfo)
if err != nil {
return err
}
return maps.Put([]byte(strconv.Itoa(mapid)), rawmap)
})
}
func (a *App) adminMap(rw http.ResponseWriter, req *http.Request) {
s := a.getSession(req)
if s == nil || !s.Auths.Has(AUTH_ADMIN) {
http.Redirect(rw, req, "/", 302)
return
}
mraw := req.FormValue("map")
mapid, err := strconv.Atoi(mraw)
if err != nil {
http.Error(rw, "map parse failed", http.StatusBadRequest)
return
}
if req.Method == "POST" {
req.ParseForm()
name := req.FormValue("name")
hidden := !(req.FormValue("hidden") == "")
priority := !(req.FormValue("priority") == "")
a.db.Update(func(tx *bbolt.Tx) error {
maps, err := tx.CreateBucketIfNotExists([]byte("maps"))
if err != nil {
return err
}
rawmap := maps.Get([]byte(strconv.Itoa(mapid)))
mapinfo := MapInfo{}
if rawmap != nil {
json.Unmarshal(rawmap, &mapinfo)
}
mapinfo.Name = name
mapinfo.Hidden = hidden
mapinfo.Priority = priority
rawmap, err = json.Marshal(mapinfo)
if err != nil {
return err
}
return maps.Put([]byte(strconv.Itoa(mapid)), rawmap)
})
http.Redirect(rw, req, "/admin", 302)
return
}
mi := MapInfo{}
a.db.View(func(tx *bbolt.Tx) error {
mapB := tx.Bucket([]byte("maps"))
if mapB == nil {
return nil
}
mraw := mapB.Get([]byte(strconv.Itoa(mapid)))
return json.Unmarshal(mraw, &mi)
})
a.ExecuteTemplate(rw, "admin/map.tmpl", struct {
Page Page
Session *Session
MapInfo MapInfo
}{
Page: a.getPage(req),
Session: s,
MapInfo: mi,
})
}