package app import ( "encoding/json" "fmt" "strconv" "strings" "time" "github.com/andyleap/hnh-map/internal/app/store" "go.etcd.io/bbolt" ) var migrations = []func(tx *bbolt.Tx) error{ func(tx *bbolt.Tx) error { if tx.Bucket(store.BucketMarkers) != nil { return tx.DeleteBucket(store.BucketMarkers) } return nil }, func(tx *bbolt.Tx) error { grids, err := tx.CreateBucketIfNotExists(store.BucketGrids) if err != nil { return err } tiles, err := tx.CreateBucketIfNotExists(store.BucketTiles) if err != nil { return err } zoom, err := tiles.CreateBucketIfNotExists([]byte(strconv.Itoa(0))) if err != nil { return err } return grids.ForEach(func(k, v []byte) error { g := GridData{} err := json.Unmarshal(v, &g) if err != nil { return err } td := &TileData{ Coord: g.Coord, Zoom: 0, File: fmt.Sprintf("0/%s", g.Coord.Name()), Cache: time.Now().UnixNano(), } raw, err := json.Marshal(td) if err != nil { return err } return zoom.Put([]byte(g.Coord.Name()), raw) }) }, func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists(store.BucketConfig) if err != nil { return err } return b.Put([]byte("title"), []byte("HnH Automapper Server")) }, func(tx *bbolt.Tx) error { // No-op: markers deletion already in migration 0 return nil }, func(tx *bbolt.Tx) error { if tx.Bucket(store.BucketTiles) != nil { allTiles := map[string]map[string]TileData{} tiles := tx.Bucket(store.BucketTiles) err := tiles.ForEach(func(k, v []byte) error { zoom := tiles.Bucket(k) zoomTiles := map[string]TileData{} allTiles[string(k)] = zoomTiles return zoom.ForEach(func(tk, tv []byte) error { td := TileData{} json.Unmarshal(tv, &td) zoomTiles[string(tk)] = td return nil }) }) if err != nil { return err } err = tx.DeleteBucket(store.BucketTiles) if err != nil { return err } tiles, err = tx.CreateBucket(store.BucketTiles) if err != nil { return err } maptiles, err := tiles.CreateBucket([]byte("0")) if err != nil { return err } for k, v := range allTiles { zoom, err := maptiles.CreateBucket([]byte(k)) if err != nil { return err } for tk, tv := range v { raw, _ := json.Marshal(tv) err = zoom.Put([]byte(strings.TrimSuffix(tk, ".png")), raw) if err != nil { return err } } } err = tiles.SetSequence(1) if err != nil { return err } } return nil }, func(tx *bbolt.Tx) error { // No-op: markers deletion already in migration 0 return nil }, func(tx *bbolt.Tx) error { highest := uint64(0) maps, err := tx.CreateBucketIfNotExists(store.BucketMaps) if err != nil { return err } grids, err := tx.CreateBucketIfNotExists(store.BucketGrids) if err != nil { return err } mapsFound := map[int]struct{}{} err = grids.ForEach(func(k, v []byte) error { gd := GridData{} err := json.Unmarshal(v, &gd) if err != nil { return err } if _, ok := mapsFound[gd.Map]; !ok { mapsFound[gd.Map] = struct{}{} if uint64(gd.Map) > highest { highest = uint64(gd.Map) } mi := MapInfo{ ID: gd.Map, Name: strconv.Itoa(gd.Map), Hidden: false, } raw, _ := json.Marshal(mi) return maps.Put([]byte(strconv.Itoa(gd.Map)), raw) } return nil }) if err != nil { return err } return maps.SetSequence(highest + 1) }, func(tx *bbolt.Tx) error { users := tx.Bucket(store.BucketUsers) if users == nil { return nil } return users.ForEach(func(k, v []byte) error { u := User{} json.Unmarshal(v, &u) if u.Auths.Has(AUTH_MAP) && !u.Auths.Has(AUTH_MARKERS) { u.Auths = append(u.Auths, AUTH_MARKERS) raw, err := json.Marshal(u) if err != nil { return err } users.Put(k, raw) } return nil }) }, func(tx *bbolt.Tx) error { _, err := tx.CreateBucketIfNotExists(store.BucketOAuthStates) return err }, } // RunMigrations runs all pending migrations on the database. func RunMigrations(db *bbolt.DB) error { return db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists(store.BucketConfig) if err != nil { return err } vraw := b.Get([]byte("version")) v, _ := strconv.Atoi(string(vraw)) if v < len(migrations) { for _, f := range migrations[v:] { if err := f(tx); err != nil { return err } } } return b.Put([]byte("version"), []byte(strconv.Itoa(len(migrations)))) }) }