deadline of features

* features can have a deadline
* features with non-null deadline < now are deleted upon request for data
This commit is contained in:
zegkljan 2022-02-13 02:40:28 +01:00
parent 3615deb985
commit 7b4eb8d5d2
3 changed files with 54 additions and 13 deletions

View File

@ -2,6 +2,7 @@ package models
import ( import (
"io" "io"
"time"
geojson "github.com/paulmach/go.geojson" geojson "github.com/paulmach/go.geojson"
) )
@ -23,6 +24,7 @@ type Feature struct {
ID FeatureID `json:"id"` ID FeatureID `json:"id"`
OwnerID UserID `json:"owner_id"` OwnerID UserID `json:"owner_id"`
Name string `json:"name"` Name string `json:"name"`
Deadline *time.Time `json:"deadline,omitempty"`
Properties map[string]interface{} `json:"properties"` Properties map[string]interface{} `json:"properties"`
Geometry geojson.Geometry `json:"geometry"` Geometry geojson.Geometry `json:"geometry"`
// PhotoIDs contains a list IDs of photos associated with the feature. // PhotoIDs contains a list IDs of photos associated with the feature.

View File

@ -10,6 +10,7 @@ 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,
deadline text,
properties text NOT NULL, properties text NOT NULL,
geom text NOT NULL geom text NOT NULL
); );

View File

@ -194,6 +194,10 @@ func (s *Server) getData() (models.Data, error) {
return func() (data models.Data, err error) { return func() (data models.Data, err error) {
defer sqlitex.Save(conn)(&err) 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) people, err := s.getPeople(conn)
if err != nil { if err != nil {
return models.Data{}, fmt.Errorf("failed to retreive people: %w", err) 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) { func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) {
if conn == nil { if conn == nil {
conn = s.getDbConn() conn = s.getDbConn()
@ -290,7 +309,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
} }
features := make([]models.Feature, 0, 100) 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 from features f
left join feature_photos p on f.id = p.feature_id 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 { 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) 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{} var properties map[string]interface{}
err := json.Unmarshal([]byte(propertiesRaw), &properties) err := json.Unmarshal([]byte(propertiesRaw), &properties)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse properties for feature id=%d: %w", id, err) 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 var geom geojson.Geometry
err = json.Unmarshal([]byte(geomRaw), &geom) err = json.Unmarshal([]byte(geomRaw), &geom)
if err != nil { if err != nil {
return fmt.Errorf("failed to parse geometry for feature id=%d: %w", id, err) 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 var photos []models.FeaturePhotoID
err = json.Unmarshal([]byte(photosRaw), &photos) err = json.Unmarshal([]byte(photosRaw), &photos)
if err != nil { if err != nil {
@ -326,6 +351,7 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
ID: models.FeatureID(id), ID: models.FeatureID(id),
OwnerID: models.UserID(ownerID), OwnerID: models.UserID(ownerID),
Name: name, Name: name,
Deadline: deadline,
Properties: properties, Properties: properties,
Geometry: geom, Geometry: geom,
PhotoIDs: photos, 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) { 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 { if err != nil {
return nil, fmt.Errorf("failed to prepare statement: %w", err) 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) stmt.BindText(2, feature.Name)
if feature.Deadline == nil {
stmt.BindNull(3)
} else {
stmt.BindInt64(3, feature.Deadline.Unix())
}
if feature.Properties == nil { if feature.Properties == nil {
stmt.BindText(3, "{}") stmt.BindText(4, "{}")
} else { } else {
propertiesBytes, err := json.Marshal(feature.Properties) propertiesBytes, err := json.Marshal(feature.Properties)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err) 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) geomBytes, err := json.Marshal(feature.Geometry)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal geometry of feature id=%d: %w", feature.ID, err) 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() _, err = stmt.Step()
if err != nil { 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 { 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 { if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err) 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) stmt.BindText(2, feature.Name)
if feature.Deadline == nil {
stmt.BindNull(3)
} else {
stmt.BindInt64(3, feature.Deadline.Unix())
}
if feature.Properties == nil { if feature.Properties == nil {
stmt.BindText(3, "{}") stmt.BindText(4, "{}")
} else { } else {
propertiesBytes, err := json.Marshal(feature.Properties) propertiesBytes, err := json.Marshal(feature.Properties)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err) 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) geomBytes, err := json.Marshal(feature.Geometry)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal geometry of feature ID %d: %w", feature.ID, err) 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() _, err = stmt.Step()
if err != nil { if err != nil {