mirror of
https://github.com/Cernobor/oko-server.git
synced 2025-02-24 08:27:17 +00:00
data update
* foreign keys always enabled on each connection * implemented data update (create, update, delete)
This commit is contained in:
parent
01fb4c75f8
commit
395b17d1be
@ -5,6 +5,7 @@ import geojson "github.com/paulmach/go.geojson"
|
|||||||
// core objects
|
// core objects
|
||||||
|
|
||||||
type UserID int
|
type UserID int
|
||||||
|
type FeatureID int
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID UserID `json:"id"`
|
ID UserID `json:"id"`
|
||||||
@ -12,7 +13,7 @@ type User struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Feature struct {
|
type Feature struct {
|
||||||
ID int `json:"id"`
|
ID FeatureID `json:"id"`
|
||||||
OwnerID *UserID `json:"owner_id"`
|
OwnerID *UserID `json:"owner_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
@ -23,9 +24,9 @@ type Feature struct {
|
|||||||
// transport objects
|
// transport objects
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
Create []Feature `json:"create"`
|
Create []Feature `json:"create"`
|
||||||
Update []Feature `json:"update"`
|
Update []Feature `json:"update"`
|
||||||
Delete []UserID `json:"delete"`
|
Delete []FeatureID `json:"delete"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HandshakeChallenge struct {
|
type HandshakeChallenge struct {
|
||||||
|
@ -105,7 +105,7 @@ func (s *Server) handlePOSTHandshake(gc *gin.Context) {
|
|||||||
var hs models.HandshakeChallenge
|
var hs models.HandshakeChallenge
|
||||||
err := gc.ShouldBindJSON(&hs)
|
err := gc.ShouldBindJSON(&hs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gc.String(http.StatusBadRequest, "malformed handshake challenge")
|
gc.String(http.StatusBadRequest, fmt.Sprintf("malformed handshake challenge: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +140,19 @@ func (s *Server) handleGETData(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handlePOSTData(gc *gin.Context) {
|
func (s *Server) handlePOSTData(gc *gin.Context) {
|
||||||
panic("not implemented")
|
var data models.Update
|
||||||
|
err := gc.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
gc.String(http.StatusBadRequest, fmt.Sprintf("malformed data: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.update(data)
|
||||||
|
if err != nil {
|
||||||
|
internalError(gc, fmt.Errorf("failed to update data: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gc.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleGETDataPeople(gc *gin.Context) {
|
func (s *Server) handleGETDataPeople(gc *gin.Context) {
|
||||||
|
@ -8,9 +8,10 @@ INSERT INTO users(id, name) VALUES(0, 'system');
|
|||||||
DROP TABLE IF EXISTS features;
|
DROP TABLE IF EXISTS features;
|
||||||
CREATE TABLE IF NOT EXISTS features (
|
CREATE TABLE IF NOT EXISTS features (
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
id integer PRIMARY KEY AUTOINCREMENT,
|
||||||
owner_id integer REFERENCES users(id) ON DELETE CASCADE,
|
owner_id integer,
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
description text,
|
description text,
|
||||||
category text,
|
category text,
|
||||||
geom text NOT NULL
|
geom text NOT NULL,
|
||||||
|
FOREIGN KEY(owner_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
178
server/server.go
178
server/server.go
@ -78,6 +78,15 @@ func (s *Server) Run(ctx context.Context) {
|
|||||||
s.log.Info("Server exitting.")
|
s.log.Info("Server exitting.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) getDbConn() *sqlite.Conn {
|
||||||
|
conn := s.dbpool.Get(s.ctx)
|
||||||
|
_, err := conn.Prep("PRAGMA foreign_keys = ON").Step()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) setupDB(reinit bool) {
|
func (s *Server) setupDB(reinit bool) {
|
||||||
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 {
|
||||||
@ -86,7 +95,7 @@ func (s *Server) setupDB(reinit bool) {
|
|||||||
s.dbpool = dbpool
|
s.dbpool = dbpool
|
||||||
|
|
||||||
if reinit {
|
if reinit {
|
||||||
conn := s.dbpool.Get(s.ctx)
|
conn := s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
err = sqlitex.ExecScript(conn, initDB)
|
err = sqlitex.ExecScript(conn, initDB)
|
||||||
@ -97,7 +106,7 @@ func (s *Server) setupDB(reinit bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupTiles() {
|
func (s *Server) setupTiles() {
|
||||||
tsRootURL, err := url.Parse("/tileserver")
|
tsRootURL, err := url.Parse(URITileserverRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.WithError(err).Fatal("Failed to parse tileserver root URL.")
|
s.log.WithError(err).Fatal("Failed to parse tileserver root URL.")
|
||||||
}
|
}
|
||||||
@ -119,7 +128,7 @@ func (s *Server) setupTiles() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error) {
|
func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error) {
|
||||||
conn := s.dbpool.Get(s.ctx)
|
conn := s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
userID, err := func() (uid int, err error) {
|
userID, err := func() (uid int, err error) {
|
||||||
@ -162,7 +171,7 @@ func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getData() (models.Data, error) {
|
func (s *Server) getData() (models.Data, error) {
|
||||||
conn := s.dbpool.Get(s.ctx)
|
conn := s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
return func() (data models.Data, err error) {
|
return func() (data models.Data, err error) {
|
||||||
@ -184,9 +193,42 @@ func (s *Server) getData() (models.Data, error) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) update(data models.Update) error {
|
||||||
|
conn := s.getDbConn()
|
||||||
|
defer s.dbpool.Put(conn)
|
||||||
|
|
||||||
|
return func() (err error) {
|
||||||
|
defer sqlitex.Save(conn)(&err)
|
||||||
|
|
||||||
|
if data.Create != nil {
|
||||||
|
err = s.addFeatures(conn, data.Create)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to add features: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.Update != nil {
|
||||||
|
err = s.updateFeatures(conn, data.Update)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to update features: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.Delete != nil {
|
||||||
|
err = s.deleteFeatures(conn, data.Delete)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to delete features: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) {
|
func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) {
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
conn = s.dbpool.Get(s.ctx)
|
conn = s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +248,7 @@ func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) {
|
|||||||
|
|
||||||
func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
|
func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
conn = s.dbpool.Get(s.ctx)
|
conn = s.getDbConn()
|
||||||
defer s.dbpool.Put(conn)
|
defer s.dbpool.Put(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +277,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
feature := models.Feature{
|
feature := models.Feature{
|
||||||
ID: id,
|
ID: models.FeatureID(id),
|
||||||
OwnerID: (*models.UserID)(ownerID),
|
OwnerID: (*models.UserID)(ownerID),
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: description,
|
Description: description,
|
||||||
@ -251,3 +293,125 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
|
|||||||
}
|
}
|
||||||
return features, nil
|
return features, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) addFeatures(conn *sqlite.Conn, features []models.Feature) error {
|
||||||
|
stmt, err := conn.Prepare("insert into features(owner_id, name, description, category, geom) values(?, ?, ?, ?, ?)")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||||
|
}
|
||||||
|
defer stmt.Finalize()
|
||||||
|
|
||||||
|
for _, feature := range features {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if feature.OwnerID == nil {
|
||||||
|
stmt.BindNull(1)
|
||||||
|
} else {
|
||||||
|
stmt.BindInt64(1, int64(*feature.OwnerID))
|
||||||
|
}
|
||||||
|
stmt.BindText(2, feature.Name)
|
||||||
|
if feature.Description == nil {
|
||||||
|
stmt.BindNull(3)
|
||||||
|
} else {
|
||||||
|
stmt.BindText(3, *feature.Description)
|
||||||
|
}
|
||||||
|
if feature.Category == nil {
|
||||||
|
stmt.BindNull(4)
|
||||||
|
} else {
|
||||||
|
stmt.BindText(4, *feature.Category)
|
||||||
|
}
|
||||||
|
geomBytes, err := json.Marshal(feature.Geometry)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal geometry of feature ID %d: %w", feature.ID, err)
|
||||||
|
}
|
||||||
|
stmt.BindText(5, string(geomBytes))
|
||||||
|
|
||||||
|
_, err = stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) updateFeatures(conn *sqlite.Conn, features []models.Feature) error {
|
||||||
|
stmt, err := conn.Prepare("update features set owner_id = ?, name = ?, description = ?, category = ?, geom = ? where id = ?")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||||
|
}
|
||||||
|
defer stmt.Finalize()
|
||||||
|
|
||||||
|
for _, feature := range features {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if feature.OwnerID == nil {
|
||||||
|
stmt.BindNull(1)
|
||||||
|
} else {
|
||||||
|
stmt.BindInt64(1, int64(*feature.OwnerID))
|
||||||
|
}
|
||||||
|
stmt.BindText(2, feature.Name)
|
||||||
|
if feature.Description == nil {
|
||||||
|
stmt.BindNull(3)
|
||||||
|
} else {
|
||||||
|
stmt.BindText(3, *feature.Description)
|
||||||
|
}
|
||||||
|
if feature.Category == nil {
|
||||||
|
stmt.BindNull(4)
|
||||||
|
} else {
|
||||||
|
stmt.BindText(4, *feature.Category)
|
||||||
|
}
|
||||||
|
geomBytes, err := json.Marshal(feature.Geometry)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to marshal geometry of feature ID %d: %w", feature.ID, err)
|
||||||
|
}
|
||||||
|
stmt.BindText(5, string(geomBytes))
|
||||||
|
stmt.BindInt64(6, int64(feature.ID))
|
||||||
|
|
||||||
|
_, err = stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) deleteFeatures(conn *sqlite.Conn, featureIDs []models.FeatureID) error {
|
||||||
|
stmt, err := conn.Prepare("delete from features where id = ?")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||||
|
}
|
||||||
|
defer stmt.Finalize()
|
||||||
|
|
||||||
|
for _, featureID := range featureIDs {
|
||||||
|
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(featureID))
|
||||||
|
|
||||||
|
_, err = stmt.Step()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
const (
|
const (
|
||||||
URIPing = "/ping"
|
URIPing = "/ping"
|
||||||
URIHardFail = "/hard-fail"
|
URIHardFail = "/hard-fail"
|
||||||
URISoftFail = "/soft-fail"
|
URISoftFail = "/soft-fail"
|
||||||
URITilepack = "/tilepack"
|
URITilepack = "/tilepack"
|
||||||
URIHandshake = "/handshake"
|
URIHandshake = "/handshake"
|
||||||
URIData = "/data"
|
URIData = "/data"
|
||||||
URIDataPeople = "/data/people"
|
URIDataPeople = "/data/people"
|
||||||
URIDataExtra = "/data/extra"
|
URIDataExtra = "/data/extra"
|
||||||
URIDataFeatures = "/data/features"
|
URIDataFeatures = "/data/features"
|
||||||
URITileserver = "/tileserver/*x"
|
URITileserverRoot = "/tileserver"
|
||||||
|
URITileserver = URITileserverRoot + "/*x"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user