From b512bc0d2fc90f4f91810a27b5f64873246f4179 Mon Sep 17 00:00:00 2001 From: zegkljan Date: Sun, 30 Jan 2022 10:31:41 +0100 Subject: [PATCH] remove description and category, introduce properties * fatures no longer have distinct description and category fields * features now have a json field 'properties' --- models/models.go | 11 +++--- server/initdb.sql | 3 +- server/server.go | 93 ++++++++++++++++++++++++----------------------- 3 files changed, 53 insertions(+), 54 deletions(-) diff --git a/models/models.go b/models/models.go index 1388053..ec73370 100644 --- a/models/models.go +++ b/models/models.go @@ -20,12 +20,11 @@ type User struct { type Feature struct { // ID is an ID of the feature. // When the feature is submitted by a client for creation (i.e. in Update.Create) it is considered a 'local' ID which must be unique across all submitted features. - ID FeatureID `json:"id"` - OwnerID *UserID `json:"owner_id"` - Name string `json:"name"` - Description *string `json:"description"` - Category *string `json:"category"` - Geometry geojson.Geometry `json:"geometry"` + ID FeatureID `json:"id"` + OwnerID *UserID `json:"owner_id"` + Name string `json:"name"` + Properties map[string]interface{} `json:"properties"` + Geometry geojson.Geometry `json:"geometry"` // PhotoIDs contains a list IDs of photos associated with the feature. // When the feature is retrieved from the server, the IDs can be used to retrieve the photos. // When the feature is submitted by a client (created or updated, i.e. in Update.Create or Update.Update), this list is ignored (as photos are managed through Update.CreatePhotos and Update.DeletePhotos fields). diff --git a/server/initdb.sql b/server/initdb.sql index 2e18a47..3391a8b 100644 --- a/server/initdb.sql +++ b/server/initdb.sql @@ -10,8 +10,7 @@ CREATE TABLE IF NOT EXISTS features ( id integer PRIMARY KEY AUTOINCREMENT, owner_id integer REFERENCES users(id) ON DELETE CASCADE, name text NOT NULL, - description text, - category text, + properties text NOT NULL, geom text NOT NULL ); diff --git a/server/server.go b/server/server.go index 344deca..4fd94ac 100644 --- a/server/server.go +++ b/server/server.go @@ -274,10 +274,10 @@ 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.description, f.category, f.geom, '[' || coalesce(group_concat(p.id, ', '), '') || ']' + err := sqlitex.Exec(conn, `select f.id, f.owner_id, f.name, 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.description, f.category, 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 { id := stmt.ColumnInt64(0) @@ -288,24 +288,21 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { name := stmt.ColumnText(2) - var description *string - if stmt.ColumnType(3) != sqlite.SQLITE_NULL { - description = ptrString(stmt.ColumnText(3)) - } - - var category *string - if stmt.ColumnType(4) != sqlite.SQLITE_NULL { - category = ptrString(stmt.ColumnText(4)) - } - - geomRaw := stmt.ColumnText(5) - var geom geojson.Geometry - err := json.Unmarshal([]byte(geomRaw), &geom) + propertiesRaw := stmt.ColumnText(3) + var properties map[string]interface{} + err := json.Unmarshal([]byte(propertiesRaw), &properties) if err != nil { - return fmt.Errorf("failed to parse geometry for point id=%d: %w", id, err) + return fmt.Errorf("failed to parse properties for feature id=%d: %w", id, err) } - photosRaw := stmt.ColumnText(6) + geomRaw := stmt.ColumnText(4) + 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) var photos []models.FeaturePhotoID err = json.Unmarshal([]byte(photosRaw), &photos) if err != nil { @@ -313,13 +310,12 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) { } feature := models.Feature{ - ID: models.FeatureID(id), - OwnerID: (*models.UserID)(ownerID), - Name: name, - Description: description, - Category: category, - Geometry: geom, - PhotoIDs: photos, + ID: models.FeatureID(id), + OwnerID: (*models.UserID)(ownerID), + Name: name, + Properties: properties, + Geometry: geom, + PhotoIDs: photos, } features = append(features, feature) @@ -332,7 +328,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, description, category, geom) values(?, ?, ?, ?, ?)") + stmt, err := conn.Prepare("insert into features(owner_id, name, properties, geom) values(?, ?, ?, ?)") if err != nil { return nil, fmt.Errorf("failed to prepare statement: %w", err) } @@ -357,22 +353,24 @@ func (s *Server) addFeatures(conn *sqlite.Conn, features []models.Feature) (map[ } else { stmt.BindInt64(1, int64(*feature.OwnerID)) } + stmt.BindText(2, feature.Name) - if feature.Description == nil { - stmt.BindNull(3) + + if feature.Properties == nil { + stmt.BindText(3, "{}") } else { - stmt.BindText(3, *feature.Description) - } - if feature.Category == nil { - stmt.BindNull(4) - } else { - stmt.BindText(4, *feature.Category) + 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)) } + 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) + return nil, fmt.Errorf("failed to marshal geometry of feature id=%d: %w", feature.ID, err) } - stmt.BindText(5, string(geomBytes)) + stmt.BindText(4, string(geomBytes)) _, err = stmt.Step() if err != nil { @@ -457,7 +455,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 = ?, description = ?, category = ?, geom = ? where id = ?") + stmt, err := conn.Prepare("update features set owner_id = ?, name = ?, properties = ?, geom = ? where id = ?") if err != nil { return fmt.Errorf("failed to prepare statement: %w", err) } @@ -478,23 +476,26 @@ func (s *Server) updateFeatures(conn *sqlite.Conn, features []models.Feature) er } else { stmt.BindInt64(1, int64(*feature.OwnerID)) } + stmt.BindText(2, feature.Name) - if feature.Description == nil { - stmt.BindNull(3) + + if feature.Properties == nil { + stmt.BindText(3, "{}") } else { - stmt.BindText(3, *feature.Description) - } - if feature.Category == nil { - stmt.BindNull(4) - } else { - stmt.BindText(4, *feature.Category) + 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)) } + 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)) + stmt.BindText(4, string(geomBytes)) + + stmt.BindInt64(5, int64(feature.ID)) _, err = stmt.Step() if err != nil {