DB migration, proposals.

* DB schema is migrated.
* Added mechanism for proposing app improvements.
This commit is contained in:
zegkljan 2022-08-13 02:20:34 +02:00
parent 2379a33594
commit 19fc8e0322
6 changed files with 122 additions and 20 deletions

3
.gitignore vendored
View File

@ -37,4 +37,5 @@ go.work
# Project-specific
oko-server
*.sqlite*
.vscode
.vscode
__debug_bin

View File

@ -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"`
}

View File

@ -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

View File

@ -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
}

View File

@ -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,12 +13,13 @@ 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,
content_type text NOT NULL,
thumbnail_contents blob NOT NULL,
contents blob NOT NULL
);
);
PRAGMA user_version = 1;

View 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;