mirror of
https://github.com/Cernobor/oko-server.git
synced 2025-02-24 08:27:17 +00:00
DB migration, proposals.
* DB schema is migrated. * Added mechanism for proposing app improvements.
This commit is contained in:
parent
2379a33594
commit
19fc8e0322
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,3 +38,4 @@ go.work
|
|||||||
oko-server
|
oko-server
|
||||||
*.sqlite*
|
*.sqlite*
|
||||||
.vscode
|
.vscode
|
||||||
|
__debug_bin
|
@ -60,6 +60,7 @@ type Update struct {
|
|||||||
Update []Feature `json:"update"`
|
Update []Feature `json:"update"`
|
||||||
Delete []FeatureID `json:"delete"`
|
Delete []FeatureID `json:"delete"`
|
||||||
DeletePhotos []FeaturePhotoID `json:"delete_photos"`
|
DeletePhotos []FeaturePhotoID `json:"delete_photos"`
|
||||||
|
Proposals []Proposal `json:"proposals"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HandshakeChallenge struct {
|
type HandshakeChallenge struct {
|
||||||
@ -92,3 +93,9 @@ type PhotoMetadata struct {
|
|||||||
ID FeaturePhotoID `json:"id"`
|
ID FeaturePhotoID `json:"id"`
|
||||||
ThumbnailFilename string `json:"thumbnail_filename"`
|
ThumbnailFilename string `json:"thumbnail_filename"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Proposal struct {
|
||||||
|
OwnerID int `json:"owner_id"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
How string `json:"how"`
|
||||||
|
}
|
||||||
|
@ -113,7 +113,7 @@ func (s *Server) setupRouter() *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePOSTReset(gc *gin.Context) {
|
func (s *Server) handlePOSTReset(gc *gin.Context) {
|
||||||
err := s.reinitDB()
|
err := s.initDB(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalError(gc, err)
|
internalError(gc, err)
|
||||||
return
|
return
|
||||||
|
110
server/server.go
110
server/server.go
@ -22,8 +22,11 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed initdb.sql
|
//go:embed sql_schema/V1_init.sql
|
||||||
var initDB string
|
var sql_v1 string
|
||||||
|
|
||||||
|
//go:embed sql_schema/V2_proposals.sql
|
||||||
|
var sql_v2 string
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config ServerConfig
|
config ServerConfig
|
||||||
@ -141,6 +144,7 @@ func (s *Server) checkpointDb(conn *sqlite.Conn, truncate bool) {
|
|||||||
|
|
||||||
func (s *Server) setupDB() {
|
func (s *Server) setupDB() {
|
||||||
sqlitex.PoolCloseTimeout = time.Second * 10
|
sqlitex.PoolCloseTimeout = time.Second * 10
|
||||||
|
s.log.Debugf("Using db %s", s.config.DbPath)
|
||||||
dbpool, err := sqlitex.Open(fmt.Sprintf("file:%s", s.config.DbPath), 0, 10)
|
dbpool, err := sqlitex.Open(fmt.Sprintf("file:%s", s.config.DbPath), 0, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.WithError(err).Fatal("Failed to open/create DB.")
|
s.log.WithError(err).Fatal("Failed to open/create DB.")
|
||||||
@ -148,11 +152,9 @@ func (s *Server) setupDB() {
|
|||||||
s.dbpool = dbpool
|
s.dbpool = dbpool
|
||||||
s.checkpointNotice = make(chan struct{})
|
s.checkpointNotice = make(chan struct{})
|
||||||
|
|
||||||
if s.config.ReinitDB {
|
err = s.initDB(s.config.ReinitDB)
|
||||||
err = s.reinitDB()
|
if err != nil {
|
||||||
if err != nil {
|
s.log.WithError(err).Fatal("init DB transaction failed")
|
||||||
s.log.WithError(err).Fatal("init DB transaction failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// aggressively checkpoint the database on idle times
|
// aggressively checkpoint the database on idle times
|
||||||
@ -217,13 +219,62 @@ func (s *Server) setupTiles() {
|
|||||||
s.mapPackSize = info.Size()
|
s.mapPackSize = info.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) reinitDB() error {
|
func (s *Server) initDB(reinit bool) error {
|
||||||
s.log.Info("Reinitializing DB.")
|
s.log.Info("Initializing DB.")
|
||||||
conn := s.getDbConn()
|
conn := s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
defer s.requestCheckpoint()
|
defer s.requestCheckpoint()
|
||||||
return sqlitex.ExecScript(conn, initDB)
|
|
||||||
|
if reinit {
|
||||||
|
s.log.Warn("REinitializing DB.")
|
||||||
|
tables := []string{}
|
||||||
|
err := sqlitex.Exec(conn, "select name from sqlite_master where type = 'table'", func(stmt *sqlite.Stmt) error {
|
||||||
|
tables = append(tables, stmt.ColumnText(0))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get table names: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, table := range tables {
|
||||||
|
err = sqlitex.Exec(conn, "drop table "+table, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to drop tables: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sqlitex.Exec(conn, "PRAGMA user_version = 0", nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reset user version: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var version int
|
||||||
|
err := sqlitex.Exec(conn, "PRAGMA user_version", func(stmt *sqlite.Stmt) error {
|
||||||
|
version = stmt.ColumnInt(0)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get user version: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Debugf("Current db version: %d", version)
|
||||||
|
if version <= 0 {
|
||||||
|
s.log.Debugf("Running db migration V1")
|
||||||
|
err = sqlitex.ExecScript(conn, sql_v1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run V1 init script")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if version <= 1 {
|
||||||
|
s.log.Debugf("Running db migration V2")
|
||||||
|
err = sqlitex.ExecScript(conn, sql_v2)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to run V2 init script")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error) {
|
func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error) {
|
||||||
@ -412,7 +463,7 @@ func (s *Server) getDataWithPhotos() (file *os.File, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) update(data models.Update, photos map[string]models.Photo) error {
|
func (s *Server) update(data models.Update, photos map[string]models.Photo) error {
|
||||||
s.log.Debugf("Updating data: %d created, %d created photos, %d updated, %d deleted, %d deleted photos, %d photo files", len(data.Create), len(data.CreatedPhotos), len(data.Update), len(data.Delete), len(data.DeletePhotos), len(photos))
|
s.log.Debugf("Updating data: %d created, %d created photos, %d updated, %d deleted, %d deleted photos, %d photo files, %d proposals", len(data.Create), len(data.CreatedPhotos), len(data.Update), len(data.Delete), len(data.DeletePhotos), len(photos), len(data.Proposals))
|
||||||
conn := s.getDbConn()
|
conn := s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
@ -459,6 +510,13 @@ func (s *Server) update(data models.Update, photos map[string]models.Photo) erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if data.Proposals != nil {
|
||||||
|
err = s.addProposals(conn, data.Proposals)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to add proposals: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}()
|
}()
|
||||||
@ -860,3 +918,33 @@ func (s *Server) getPhoto(featureID models.FeatureID, photoID models.FeaturePhot
|
|||||||
|
|
||||||
return data, *contentType, nil
|
return data, *contentType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) addProposals(conn *sqlite.Conn, proposals []models.Proposal) error {
|
||||||
|
stmt, err := conn.Prepare("insert into proposals(owner_id, description, how) values(?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||||
|
}
|
||||||
|
defer stmt.Finalize()
|
||||||
|
|
||||||
|
for _, proposal := range proposals {
|
||||||
|
err := stmt.Reset()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to reset prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
err = stmt.ClearBindings()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to clear bindings of prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt.BindInt64(1, int64(proposal.OwnerID))
|
||||||
|
stmt.BindText(2, proposal.Description)
|
||||||
|
stmt.BindText(3, proposal.How)
|
||||||
|
|
||||||
|
_, err = stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.requestCheckpoint()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
DROP TABLE IF EXISTS users;
|
CREATE TABLE users (
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
id integer PRIMARY KEY AUTOINCREMENT,
|
||||||
name text NOT NULL UNIQUE
|
name text NOT NULL UNIQUE
|
||||||
);
|
);
|
||||||
INSERT INTO users(id, name) VALUES(0, 'system');
|
INSERT INTO users(id, name) VALUES(0, 'system');
|
||||||
|
|
||||||
DROP TABLE IF EXISTS features;
|
CREATE TABLE features (
|
||||||
CREATE TABLE IF NOT EXISTS features (
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
id integer PRIMARY KEY AUTOINCREMENT,
|
||||||
owner_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
owner_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
@ -15,8 +13,7 @@ CREATE TABLE IF NOT EXISTS features (
|
|||||||
geom text NOT NULL
|
geom text NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
DROP TABLE IF EXISTS feature_photos;
|
CREATE TABLE feature_photos (
|
||||||
CREATE TABLE IF NOT EXISTS feature_photos (
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
id integer PRIMARY KEY AUTOINCREMENT,
|
||||||
feature_id integer NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
feature_id integer NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||||||
thumbnail_content_type text NOT NULL,
|
thumbnail_content_type text NOT NULL,
|
||||||
@ -24,3 +21,5 @@ CREATE TABLE IF NOT EXISTS feature_photos (
|
|||||||
thumbnail_contents blob NOT NULL,
|
thumbnail_contents blob NOT NULL,
|
||||||
contents blob NOT NULL
|
contents blob NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
PRAGMA user_version = 1;
|
7
server/sql_schema/V2_proposals.sql
Normal file
7
server/sql_schema/V2_proposals.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE proposals (
|
||||||
|
owner_id integer NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
description text NOT NULL,
|
||||||
|
how text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
PRAGMA user_version = 2;
|
Loading…
Reference in New Issue
Block a user