From 7b4eb8d5d256f9de18821c364d91465ee281f94e Mon Sep 17 00:00:00 2001 From: zegkljan Date: Sun, 13 Feb 2022 02:40:28 +0100 Subject: [PATCH] deadline of features * features can have a deadline * features with non-null deadline < now are deleted upon request for data --- models/models.go | 2 ++ server/initdb.sql | 1 + server/server.go | 64 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/models/models.go b/models/models.go index 0f8f9a5..3c5b611 100644 --- a/models/models.go +++ b/models/models.go @@ -2,6 +2,7 @@ package models import ( "io" + "time" geojson "github.com/paulmach/go.geojson" ) @@ -23,6 +24,7 @@ type Feature struct { ID FeatureID `json:"id"` OwnerID UserID `json:"owner_id"` Name string `json:"name"` + Deadline *time.Time `json:"deadline,omitempty"` Properties map[string]interface{} `json:"properties"` Geometry geojson.Geometry `json:"geometry"` // PhotoIDs contains a list IDs of photos associated with the feature. diff --git a/server/initdb.sql b/server/initdb.sql index da1ba30..2fff2f3 100644 --- a/server/initdb.sql +++ b/server/initdb.sql @@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS features ( id integer PRIMARY KEY AUTOINCREMENT, owner_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, name text NOT NULL, + deadline text, properties text NOT NULL, geom text NOT NULL ); diff --git a/server/server.go b/server/server.go index eb3ad09..4e33f31 100644 --- a/server/server.go +++ b/server/server.go @@ -194,6 +194,10 @@ func (s *Server) getData() (models.Data, error) { return func() (data models.Data, err error) { defer sqlitex.Save(conn)(&err) + err = s.deleteExpiredFeatures(conn) + if err != nil { + return models.Data{}, fmt.Errorf("failed to delete expired featured: %w", err) + } people, err := s.getPeople(conn) if err != nil { return models.Data{}, fmt.Errorf("failed to retreive people: %w", err) @@ -263,6 +267,21 @@ func (s *Server) update(data models.Update, photos map[string]models.Photo) erro }() } +func (s *Server) deleteExpiredFeatures(conn *sqlite.Conn) error { + stmt, err := conn.Prepare("delete from features where deadline < ?") + if err != nil { + return fmt.Errorf("failed to prepare statement: %w", err) + } + defer stmt.Finalize() + + now := time.Now().Unix() + err = sqlitex.Exec(conn, "delete from features where deadline < ?", func(stmt *sqlite.Stmt) error { return nil }, now) + if err != nil { + return fmt.Errorf("failed to delete expired features: %w", err) + } + return nil +} + func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) { if conn == nil { conn = s.getDbConn() @@ -290,7 +309,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { } features := make([]models.Feature, 0, 100) - err := sqlitex.Exec(conn, `select f.id, f.owner_id, f.name, f.properties, f.geom, '[' || coalesce(group_concat(p.id, ', '), '') || ']' + err := sqlitex.Exec(conn, `select f.id, f.owner_id, f.name, f.deadline, f.properties, f.geom, '[' || coalesce(group_concat(p.id, ', '), '') || ']' from features f left join feature_photos p on f.id = p.feature_id group by f.id, f.owner_id, f.name, f.properties, f.geom`, func(stmt *sqlite.Stmt) error { @@ -301,21 +320,27 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { name := stmt.ColumnText(2) - propertiesRaw := stmt.ColumnText(3) + var deadline *time.Time + if stmt.ColumnType(3) != sqlite.SQLITE_NULL { + dl := time.Unix(stmt.ColumnInt64(3), 0) + deadline = &dl + } + + propertiesRaw := stmt.ColumnText(4) var properties map[string]interface{} err := json.Unmarshal([]byte(propertiesRaw), &properties) if err != nil { return fmt.Errorf("failed to parse properties for feature id=%d: %w", id, err) } - geomRaw := stmt.ColumnText(4) + geomRaw := stmt.ColumnText(5) var geom geojson.Geometry err = json.Unmarshal([]byte(geomRaw), &geom) if err != nil { return fmt.Errorf("failed to parse geometry for feature id=%d: %w", id, err) } - photosRaw := stmt.ColumnText(5) + photosRaw := stmt.ColumnText(6) var photos []models.FeaturePhotoID err = json.Unmarshal([]byte(photosRaw), &photos) if err != nil { @@ -326,6 +351,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { ID: models.FeatureID(id), OwnerID: models.UserID(ownerID), Name: name, + Deadline: deadline, Properties: properties, Geometry: geom, PhotoIDs: photos, @@ -341,7 +367,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { } func (s *Server) addFeatures(conn *sqlite.Conn, features []models.Feature) (map[models.FeatureID]models.FeatureID, error) { - stmt, err := conn.Prepare("insert into features(owner_id, name, properties, geom) values(?, ?, ?, ?)") + stmt, err := conn.Prepare("insert into features(owner_id, name, deadline, properties, geom) values(?, ?, ?, ?, ?)") if err != nil { return nil, fmt.Errorf("failed to prepare statement: %w", err) } @@ -365,21 +391,27 @@ func (s *Server) addFeatures(conn *sqlite.Conn, features []models.Feature) (map[ stmt.BindText(2, feature.Name) + if feature.Deadline == nil { + stmt.BindNull(3) + } else { + stmt.BindInt64(3, feature.Deadline.Unix()) + } + if feature.Properties == nil { - stmt.BindText(3, "{}") + stmt.BindText(4, "{}") } else { propertiesBytes, err := json.Marshal(feature.Properties) if err != nil { return nil, fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err) } - stmt.BindText(3, string(propertiesBytes)) + stmt.BindText(4, string(propertiesBytes)) } geomBytes, err := json.Marshal(feature.Geometry) if err != nil { return nil, fmt.Errorf("failed to marshal geometry of feature id=%d: %w", feature.ID, err) } - stmt.BindText(4, string(geomBytes)) + stmt.BindText(5, string(geomBytes)) _, err = stmt.Step() if err != nil { @@ -464,7 +496,7 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur } func (s *Server) updateFeatures(conn *sqlite.Conn, features []models.Feature) error { - stmt, err := conn.Prepare("update features set owner_id = ?, name = ?, properties = ?, geom = ? where id = ?") + stmt, err := conn.Prepare("update features set owner_id = ?, name = ?, deadline = ?, properties = ?, geom = ? where id = ?") if err != nil { return fmt.Errorf("failed to prepare statement: %w", err) } @@ -484,23 +516,29 @@ func (s *Server) updateFeatures(conn *sqlite.Conn, features []models.Feature) er stmt.BindText(2, feature.Name) + if feature.Deadline == nil { + stmt.BindNull(3) + } else { + stmt.BindInt64(3, feature.Deadline.Unix()) + } + if feature.Properties == nil { - stmt.BindText(3, "{}") + stmt.BindText(4, "{}") } else { propertiesBytes, err := json.Marshal(feature.Properties) if err != nil { return fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err) } - stmt.BindText(3, string(propertiesBytes)) + stmt.BindText(4, string(propertiesBytes)) } 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(4, string(geomBytes)) + stmt.BindText(5, string(geomBytes)) - stmt.BindInt64(5, int64(feature.ID)) + stmt.BindInt64(6, int64(feature.ID)) _, err = stmt.Step() if err != nil {