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
|
||||
*.sqlite*
|
||||
.vscode
|
||||
__debug_bin
|
@ -60,6 +60,7 @@ type Update struct {
|
||||
Update []Feature `json:"update"`
|
||||
Delete []FeatureID `json:"delete"`
|
||||
DeletePhotos []FeaturePhotoID `json:"delete_photos"`
|
||||
Proposals []Proposal `json:"proposals"`
|
||||
}
|
||||
|
||||
type HandshakeChallenge struct {
|
||||
@ -92,3 +93,9 @@ type PhotoMetadata struct {
|
||||
ID FeaturePhotoID `json:"id"`
|
||||
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) {
|
||||
err := s.reinitDB()
|
||||
err := s.initDB(true)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
|
110
server/server.go
110
server/server.go
@ -22,8 +22,11 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
//go:embed initdb.sql
|
||||
var initDB string
|
||||
//go:embed sql_schema/V1_init.sql
|
||||
var sql_v1 string
|
||||
|
||||
//go:embed sql_schema/V2_proposals.sql
|
||||
var sql_v2 string
|
||||
|
||||
type Server struct {
|
||||
config ServerConfig
|
||||
@ -141,6 +144,7 @@ func (s *Server) checkpointDb(conn *sqlite.Conn, truncate bool) {
|
||||
|
||||
func (s *Server) setupDB() {
|
||||
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)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Fatal("Failed to open/create DB.")
|
||||
@ -148,11 +152,9 @@ func (s *Server) setupDB() {
|
||||
s.dbpool = dbpool
|
||||
s.checkpointNotice = make(chan struct{})
|
||||
|
||||
if s.config.ReinitDB {
|
||||
err = s.reinitDB()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Fatal("init DB transaction failed")
|
||||
}
|
||||
err = s.initDB(s.config.ReinitDB)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Fatal("init DB transaction failed")
|
||||
}
|
||||
|
||||
// aggressively checkpoint the database on idle times
|
||||
@ -217,13 +219,62 @@ func (s *Server) setupTiles() {
|
||||
s.mapPackSize = info.Size()
|
||||
}
|
||||
|
||||
func (s *Server) reinitDB() error {
|
||||
s.log.Info("Reinitializing DB.")
|
||||
func (s *Server) initDB(reinit bool) error {
|
||||
s.log.Info("Initializing DB.")
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
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) {
|
||||
@ -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 {
|
||||
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()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
@ -459,6 +510,13 @@ func (s *Server) update(data models.Update, photos map[string]models.Photo) erro
|
||||
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
|
||||
}()
|
||||
@ -860,3 +918,33 @@ func (s *Server) getPhoto(featureID models.FeatureID, photoID models.FeaturePhot
|
||||
|
||||
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 IF NOT EXISTS users (
|
||||
CREATE TABLE users (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name text NOT NULL UNIQUE
|
||||
);
|
||||
INSERT INTO users(id, name) VALUES(0, 'system');
|
||||
|
||||
DROP TABLE IF EXISTS features;
|
||||
CREATE TABLE IF NOT EXISTS features (
|
||||
CREATE TABLE features (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
owner_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
@ -15,8 +13,7 @@ CREATE TABLE IF NOT EXISTS features (
|
||||
geom text NOT NULL
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS feature_photos;
|
||||
CREATE TABLE IF NOT EXISTS feature_photos (
|
||||
CREATE TABLE feature_photos (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
feature_id integer NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||||
thumbnail_content_type text NOT NULL,
|
||||
@ -24,3 +21,5 @@ CREATE TABLE IF NOT EXISTS feature_photos (
|
||||
thumbnail_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