Files
hnh-map/internal/app/store/db.go
Nikolay Tatarinov 5ffa10f8b7 Update project structure and enhance frontend functionality
- Added a new AGENTS.md file to document the project structure and conventions.
- Updated .gitignore to include node_modules and refined cursor rules.
- Introduced new backend and frontend components for improved map interactions, including context menus and controls.
- Enhanced API composables for better admin and authentication functionalities.
- Refactored existing components for cleaner code and improved user experience.
- Updated README.md to clarify production asset serving and user setup instructions.
2026-02-25 16:32:55 +03:00

445 lines
11 KiB
Go

package store
import (
"strconv"
"go.etcd.io/bbolt"
)
// Store provides access to bbolt database (bucket helpers, CRUD).
type Store struct {
db *bbolt.DB
}
// New creates a Store for the given database.
func New(db *bbolt.DB) *Store {
return &Store{db: db}
}
// View runs fn in a read-only transaction.
func (s *Store) View(fn func(tx *bbolt.Tx) error) error {
return s.db.View(fn)
}
// Update runs fn in a read-write transaction.
func (s *Store) Update(fn func(tx *bbolt.Tx) error) error {
return s.db.Update(fn)
}
// --- Users ---
// GetUser returns raw user bytes by username, or nil if not found.
func (s *Store) GetUser(tx *bbolt.Tx, username string) []byte {
b := tx.Bucket(BucketUsers)
if b == nil {
return nil
}
return b.Get([]byte(username))
}
// PutUser stores user bytes by username.
func (s *Store) PutUser(tx *bbolt.Tx, username string, raw []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketUsers)
if err != nil {
return err
}
return b.Put([]byte(username), raw)
}
// DeleteUser removes a user.
func (s *Store) DeleteUser(tx *bbolt.Tx, username string) error {
b := tx.Bucket(BucketUsers)
if b == nil {
return nil
}
return b.Delete([]byte(username))
}
// ForEachUser calls fn for each user key.
func (s *Store) ForEachUser(tx *bbolt.Tx, fn func(k, v []byte) error) error {
b := tx.Bucket(BucketUsers)
if b == nil {
return nil
}
return b.ForEach(fn)
}
// UserCount returns the number of users.
func (s *Store) UserCount(tx *bbolt.Tx) int {
b := tx.Bucket(BucketUsers)
if b == nil {
return 0
}
return b.Stats().KeyN
}
// --- Sessions ---
// GetSession returns raw session bytes by ID, or nil if not found.
func (s *Store) GetSession(tx *bbolt.Tx, id string) []byte {
b := tx.Bucket(BucketSessions)
if b == nil {
return nil
}
return b.Get([]byte(id))
}
// PutSession stores session bytes.
func (s *Store) PutSession(tx *bbolt.Tx, id string, raw []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketSessions)
if err != nil {
return err
}
return b.Put([]byte(id), raw)
}
// DeleteSession removes a session.
func (s *Store) DeleteSession(tx *bbolt.Tx, id string) error {
b := tx.Bucket(BucketSessions)
if b == nil {
return nil
}
return b.Delete([]byte(id))
}
// --- Tokens ---
// GetTokenUser returns username for token, or nil if not found.
func (s *Store) GetTokenUser(tx *bbolt.Tx, token string) []byte {
b := tx.Bucket(BucketTokens)
if b == nil {
return nil
}
return b.Get([]byte(token))
}
// PutToken stores token -> username mapping.
func (s *Store) PutToken(tx *bbolt.Tx, token, username string) error {
b, err := tx.CreateBucketIfNotExists(BucketTokens)
if err != nil {
return err
}
return b.Put([]byte(token), []byte(username))
}
// DeleteToken removes a token.
func (s *Store) DeleteToken(tx *bbolt.Tx, token string) error {
b := tx.Bucket(BucketTokens)
if b == nil {
return nil
}
return b.Delete([]byte(token))
}
// --- Config ---
// GetConfig returns config value by key.
func (s *Store) GetConfig(tx *bbolt.Tx, key string) []byte {
b := tx.Bucket(BucketConfig)
if b == nil {
return nil
}
return b.Get([]byte(key))
}
// PutConfig stores config value.
func (s *Store) PutConfig(tx *bbolt.Tx, key string, value []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketConfig)
if err != nil {
return err
}
return b.Put([]byte(key), value)
}
// DeleteConfig removes a config key.
func (s *Store) DeleteConfig(tx *bbolt.Tx, key string) error {
b := tx.Bucket(BucketConfig)
if b == nil {
return nil
}
return b.Delete([]byte(key))
}
// --- Maps ---
// GetMap returns raw MapInfo bytes by ID.
func (s *Store) GetMap(tx *bbolt.Tx, id int) []byte {
b := tx.Bucket(BucketMaps)
if b == nil {
return nil
}
return b.Get([]byte(strconv.Itoa(id)))
}
// PutMap stores MapInfo.
func (s *Store) PutMap(tx *bbolt.Tx, id int, raw []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketMaps)
if err != nil {
return err
}
return b.Put([]byte(strconv.Itoa(id)), raw)
}
// DeleteMap removes a map.
func (s *Store) DeleteMap(tx *bbolt.Tx, id int) error {
b := tx.Bucket(BucketMaps)
if b == nil {
return nil
}
return b.Delete([]byte(strconv.Itoa(id)))
}
// MapsNextSequence returns next map ID sequence.
func (s *Store) MapsNextSequence(tx *bbolt.Tx) (uint64, error) {
b, err := tx.CreateBucketIfNotExists(BucketMaps)
if err != nil {
return 0, err
}
return b.NextSequence()
}
// MapsSetSequence sets map bucket sequence.
func (s *Store) MapsSetSequence(tx *bbolt.Tx, v uint64) error {
b := tx.Bucket(BucketMaps)
if b == nil {
return nil
}
return b.SetSequence(v)
}
// ForEachMap calls fn for each map.
func (s *Store) ForEachMap(tx *bbolt.Tx, fn func(k, v []byte) error) error {
b := tx.Bucket(BucketMaps)
if b == nil {
return nil
}
return b.ForEach(fn)
}
// --- Grids ---
// GetGrid returns raw GridData bytes by ID.
func (s *Store) GetGrid(tx *bbolt.Tx, id string) []byte {
b := tx.Bucket(BucketGrids)
if b == nil {
return nil
}
return b.Get([]byte(id))
}
// PutGrid stores GridData.
func (s *Store) PutGrid(tx *bbolt.Tx, id string, raw []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketGrids)
if err != nil {
return err
}
return b.Put([]byte(id), raw)
}
// DeleteGrid removes a grid.
func (s *Store) DeleteGrid(tx *bbolt.Tx, id string) error {
b := tx.Bucket(BucketGrids)
if b == nil {
return nil
}
return b.Delete([]byte(id))
}
// ForEachGrid calls fn for each grid.
func (s *Store) ForEachGrid(tx *bbolt.Tx, fn func(k, v []byte) error) error {
b := tx.Bucket(BucketGrids)
if b == nil {
return nil
}
return b.ForEach(fn)
}
// --- Tiles (nested: mapid -> zoom -> coord) ---
// GetTile returns raw TileData bytes.
func (s *Store) GetTile(tx *bbolt.Tx, mapID, zoom int, coordKey string) []byte {
tiles := tx.Bucket(BucketTiles)
if tiles == nil {
return nil
}
mapB := tiles.Bucket([]byte(strconv.Itoa(mapID)))
if mapB == nil {
return nil
}
zoomB := mapB.Bucket([]byte(strconv.Itoa(zoom)))
if zoomB == nil {
return nil
}
return zoomB.Get([]byte(coordKey))
}
// PutTile stores TileData.
func (s *Store) PutTile(tx *bbolt.Tx, mapID, zoom int, coordKey string, raw []byte) error {
tiles, err := tx.CreateBucketIfNotExists(BucketTiles)
if err != nil {
return err
}
mapB, err := tiles.CreateBucketIfNotExists([]byte(strconv.Itoa(mapID)))
if err != nil {
return err
}
zoomB, err := mapB.CreateBucketIfNotExists([]byte(strconv.Itoa(zoom)))
if err != nil {
return err
}
return zoomB.Put([]byte(coordKey), raw)
}
// DeleteTilesBucket removes the tiles bucket (for wipe).
func (s *Store) DeleteTilesBucket(tx *bbolt.Tx) error {
if tx.Bucket(BucketTiles) == nil {
return nil
}
return tx.DeleteBucket(BucketTiles)
}
// ForEachTile calls fn for each tile in the nested structure.
func (s *Store) ForEachTile(tx *bbolt.Tx, fn func(mapK, zoomK, coordK, v []byte) error) error {
tiles := tx.Bucket(BucketTiles)
if tiles == nil {
return nil
}
return tiles.ForEach(func(mapK, _ []byte) error {
mapB := tiles.Bucket(mapK)
if mapB == nil {
return nil
}
return mapB.ForEach(func(zoomK, _ []byte) error {
zoomB := mapB.Bucket(zoomK)
if zoomB == nil {
return nil
}
return zoomB.ForEach(func(coordK, v []byte) error {
return fn(mapK, zoomK, coordK, v)
})
})
})
}
// GetTilesMapBucket returns the bucket for a map's tiles, or nil.
func (s *Store) GetTilesMapBucket(tx *bbolt.Tx, mapID int) *bbolt.Bucket {
tiles := tx.Bucket(BucketTiles)
if tiles == nil {
return nil
}
return tiles.Bucket([]byte(strconv.Itoa(mapID)))
}
// CreateTilesMapBucket creates and returns the bucket for a map's tiles.
func (s *Store) CreateTilesMapBucket(tx *bbolt.Tx, mapID int) (*bbolt.Bucket, error) {
tiles, err := tx.CreateBucketIfNotExists(BucketTiles)
if err != nil {
return nil, err
}
return tiles.CreateBucketIfNotExists([]byte(strconv.Itoa(mapID)))
}
// DeleteTilesMapBucket removes a map's tile bucket.
func (s *Store) DeleteTilesMapBucket(tx *bbolt.Tx, mapID int) error {
tiles := tx.Bucket(BucketTiles)
if tiles == nil {
return nil
}
return tiles.DeleteBucket([]byte(strconv.Itoa(mapID)))
}
// --- Markers (nested: grid bucket, id bucket) ---
// GetMarkersGridBucket returns the markers-by-grid bucket.
func (s *Store) GetMarkersGridBucket(tx *bbolt.Tx) *bbolt.Bucket {
mb := tx.Bucket(BucketMarkers)
if mb == nil {
return nil
}
return mb.Bucket(BucketMarkersGrid)
}
// GetMarkersIDBucket returns the markers-by-id bucket.
func (s *Store) GetMarkersIDBucket(tx *bbolt.Tx) *bbolt.Bucket {
mb := tx.Bucket(BucketMarkers)
if mb == nil {
return nil
}
return mb.Bucket(BucketMarkersID)
}
// CreateMarkersBuckets creates markers, grid, and id buckets.
func (s *Store) CreateMarkersBuckets(tx *bbolt.Tx) (*bbolt.Bucket, *bbolt.Bucket, error) {
mb, err := tx.CreateBucketIfNotExists(BucketMarkers)
if err != nil {
return nil, nil, err
}
grid, err := mb.CreateBucketIfNotExists(BucketMarkersGrid)
if err != nil {
return nil, nil, err
}
idB, err := mb.CreateBucketIfNotExists(BucketMarkersID)
if err != nil {
return nil, nil, err
}
return grid, idB, nil
}
// MarkersNextSequence returns next marker ID.
func (s *Store) MarkersNextSequence(tx *bbolt.Tx) (uint64, error) {
mb := tx.Bucket(BucketMarkers)
if mb == nil {
return 0, nil
}
idB := mb.Bucket(BucketMarkersID)
if idB == nil {
return 0, nil
}
return idB.NextSequence()
}
// --- OAuth states ---
// GetOAuthState returns raw state bytes.
func (s *Store) GetOAuthState(tx *bbolt.Tx, state string) []byte {
b := tx.Bucket(BucketOAuthStates)
if b == nil {
return nil
}
return b.Get([]byte(state))
}
// PutOAuthState stores state.
func (s *Store) PutOAuthState(tx *bbolt.Tx, state string, raw []byte) error {
b, err := tx.CreateBucketIfNotExists(BucketOAuthStates)
if err != nil {
return err
}
return b.Put([]byte(state), raw)
}
// DeleteOAuthState removes state.
func (s *Store) DeleteOAuthState(tx *bbolt.Tx, state string) error {
b := tx.Bucket(BucketOAuthStates)
if b == nil {
return nil
}
return b.Delete([]byte(state))
}
// --- Bucket existence (for wipe) ---
// BucketExists returns true if the bucket exists.
func (s *Store) BucketExists(tx *bbolt.Tx, name []byte) bool {
return tx.Bucket(name) != nil
}
// DeleteBucket removes a bucket.
func (s *Store) DeleteBucket(tx *bbolt.Tx, name []byte) error {
if tx.Bucket(name) == nil {
return nil
}
return tx.DeleteBucket(name)
}