remove description and category, introduce properties

* fatures no longer have distinct description and category fields
* features now have a json field 'properties'
This commit is contained in:
zegkljan 2022-01-30 10:31:41 +01:00
parent 5deff38890
commit b512bc0d2f
3 changed files with 53 additions and 54 deletions

View File

@ -20,12 +20,11 @@ type User struct {
type Feature struct { type Feature struct {
// ID is an ID of the feature. // 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. // 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"` 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"` Properties map[string]interface{} `json:"properties"`
Category *string `json:"category"` 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.
// When the feature is retrieved from the server, the IDs can be used to retrieve the photos. // 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). // 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).

View File

@ -10,8 +10,7 @@ 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 REFERENCES users(id) ON DELETE CASCADE,
name text NOT NULL, name text NOT NULL,
description text, properties text NOT NULL,
category text,
geom text NOT NULL geom text NOT NULL
); );

View File

@ -274,10 +274,10 @@ 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.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 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.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) id := stmt.ColumnInt64(0)
@ -288,24 +288,21 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
name := stmt.ColumnText(2) name := stmt.ColumnText(2)
var description *string propertiesRaw := stmt.ColumnText(3)
if stmt.ColumnType(3) != sqlite.SQLITE_NULL { var properties map[string]interface{}
description = ptrString(stmt.ColumnText(3)) err := json.Unmarshal([]byte(propertiesRaw), &properties)
}
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)
if err != nil { 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 var photos []models.FeaturePhotoID
err = json.Unmarshal([]byte(photosRaw), &photos) err = json.Unmarshal([]byte(photosRaw), &photos)
if err != nil { if err != nil {
@ -313,13 +310,12 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
} }
feature := models.Feature{ feature := models.Feature{
ID: models.FeatureID(id), ID: models.FeatureID(id),
OwnerID: (*models.UserID)(ownerID), OwnerID: (*models.UserID)(ownerID),
Name: name, Name: name,
Description: description, Properties: properties,
Category: category, Geometry: geom,
Geometry: geom, PhotoIDs: photos,
PhotoIDs: photos,
} }
features = append(features, feature) 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) { 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 { if err != nil {
return nil, fmt.Errorf("failed to prepare statement: %w", err) 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 { } else {
stmt.BindInt64(1, int64(*feature.OwnerID)) stmt.BindInt64(1, int64(*feature.OwnerID))
} }
stmt.BindText(2, feature.Name) stmt.BindText(2, feature.Name)
if feature.Description == nil {
stmt.BindNull(3) if feature.Properties == nil {
stmt.BindText(3, "{}")
} else { } else {
stmt.BindText(3, *feature.Description) propertiesBytes, err := json.Marshal(feature.Properties)
} if err != nil {
if feature.Category == nil { return nil, fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err)
stmt.BindNull(4) }
} else { stmt.BindText(3, string(propertiesBytes))
stmt.BindText(4, *feature.Category)
} }
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(5, string(geomBytes)) stmt.BindText(4, string(geomBytes))
_, err = stmt.Step() _, err = stmt.Step()
if err != nil { 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 { 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 { if err != nil {
return fmt.Errorf("failed to prepare statement: %w", err) 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 { } else {
stmt.BindInt64(1, int64(*feature.OwnerID)) stmt.BindInt64(1, int64(*feature.OwnerID))
} }
stmt.BindText(2, feature.Name) stmt.BindText(2, feature.Name)
if feature.Description == nil {
stmt.BindNull(3) if feature.Properties == nil {
stmt.BindText(3, "{}")
} else { } else {
stmt.BindText(3, *feature.Description) propertiesBytes, err := json.Marshal(feature.Properties)
} if err != nil {
if feature.Category == nil { return fmt.Errorf("failed to marshal properties of feature id=%d: %w", feature.ID, err)
stmt.BindNull(4) }
} else { stmt.BindText(3, string(propertiesBytes))
stmt.BindText(4, *feature.Category)
} }
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(5, string(geomBytes)) stmt.BindText(4, string(geomBytes))
stmt.BindInt64(6, int64(feature.ID))
stmt.BindInt64(5, int64(feature.ID))
_, err = stmt.Step() _, err = stmt.Step()
if err != nil { if err != nil {