diff --git a/db/query/geometries.sql b/db/query/geometries.sql index 2838f59..afb9f8c 100644 --- a/db/query/geometries.sql +++ b/db/query/geometries.sql @@ -20,7 +20,11 @@ UPDATE geometries SET geo_type = COALESCE(sqlc.narg('geo_type'), geo_type), draw_geometry = COALESCE(sqlc.narg('draw_geometry'), draw_geometry), - bound_with = COALESCE(sqlc.narg('bound_with'), bound_with), + bound_with = CASE + WHEN sqlc.narg('update_bound_with')::boolean = true THEN + sqlc.narg('bound_with') + ELSE bound_with + END, time_start = COALESCE(sqlc.narg('time_start'), time_start), time_end = COALESCE(sqlc.narg('time_end'), time_end), project_id = COALESCE(sqlc.narg('project_id'), project_id), diff --git a/internal/gen/sqlc/geometries.sql.go b/internal/gen/sqlc/geometries.sql.go index a9ed7c3..7fcab41 100644 --- a/internal/gen/sqlc/geometries.sql.go +++ b/internal/gen/sqlc/geometries.sql.go @@ -642,35 +642,40 @@ UPDATE geometries SET geo_type = COALESCE($1, geo_type), draw_geometry = COALESCE($2, draw_geometry), - bound_with = COALESCE($3, bound_with), - time_start = COALESCE($4, time_start), - time_end = COALESCE($5, time_end), - project_id = COALESCE($6, project_id), + bound_with = CASE + WHEN $3::boolean = true THEN + $4 + ELSE bound_with + END, + time_start = COALESCE($5, time_start), + time_end = COALESCE($6, time_end), + project_id = COALESCE($7, project_id), bbox = CASE - WHEN $7::boolean = true THEN - ST_MakeEnvelope($8::float8, $9::float8, $10::float8, $11::float8, 4326) + WHEN $8::boolean = true THEN + ST_MakeEnvelope($9::float8, $10::float8, $11::float8, $12::float8, 4326) ELSE bbox END, updated_at = now() -WHERE id = $12 AND is_deleted = false +WHERE id = $13 AND is_deleted = false RETURNING id, geo_type, draw_geometry, bound_with, time_start, time_end, project_id, ST_XMin(bbox)::float8 as min_lng, ST_YMin(bbox)::float8 as min_lat, ST_XMax(bbox)::float8 as max_lng, ST_YMax(bbox)::float8 as max_lat, is_deleted, created_at, updated_at ` type UpdateGeometryParams struct { - GeoType pgtype.Int2 `json:"geo_type"` - DrawGeometry []byte `json:"draw_geometry"` - BoundWith pgtype.UUID `json:"bound_with"` - TimeStart pgtype.Int4 `json:"time_start"` - TimeEnd pgtype.Int4 `json:"time_end"` - ProjectID pgtype.UUID `json:"project_id"` - UpdateBbox pgtype.Bool `json:"update_bbox"` - MinLng pgtype.Float8 `json:"min_lng"` - MinLat pgtype.Float8 `json:"min_lat"` - MaxLng pgtype.Float8 `json:"max_lng"` - MaxLat pgtype.Float8 `json:"max_lat"` - ID pgtype.UUID `json:"id"` + GeoType pgtype.Int2 `json:"geo_type"` + DrawGeometry []byte `json:"draw_geometry"` + UpdateBoundWith pgtype.Bool `json:"update_bound_with"` + BoundWith pgtype.UUID `json:"bound_with"` + TimeStart pgtype.Int4 `json:"time_start"` + TimeEnd pgtype.Int4 `json:"time_end"` + ProjectID pgtype.UUID `json:"project_id"` + UpdateBbox pgtype.Bool `json:"update_bbox"` + MinLng pgtype.Float8 `json:"min_lng"` + MinLat pgtype.Float8 `json:"min_lat"` + MaxLng pgtype.Float8 `json:"max_lng"` + MaxLat pgtype.Float8 `json:"max_lat"` + ID pgtype.UUID `json:"id"` } type UpdateGeometryRow struct { @@ -694,6 +699,7 @@ func (q *Queries) UpdateGeometry(ctx context.Context, arg UpdateGeometryParams) row := q.db.QueryRow(ctx, updateGeometry, arg.GeoType, arg.DrawGeometry, + arg.UpdateBoundWith, arg.BoundWith, arg.TimeStart, arg.TimeEnd, diff --git a/internal/services/submissionService.go b/internal/services/submissionService.go index 04333c6..ffcb9d1 100644 --- a/internal/services/submissionService.go +++ b/internal/services/submissionService.go @@ -705,6 +705,13 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec refGeometryMap[g.ID] = true } + validGeometries := make(map[string]bool) + for _, g := range snapshotData.Geometries { + if g.Operation != "delete" { + validGeometries[g.ID] = true + } + } + newGeometries := make([]*request.GeometrySnapshot, 0, len(snapshotData.Geometries)) for i, geo := range snapshotData.Geometries { if geo.Operation == "delete" { @@ -716,15 +723,6 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec return fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID") } - var boundWith pgtype.UUID - if geo.BoundWith != nil && *geo.BoundWith != "" { - var err error - boundWith, err = convert.StringToUUID(*geo.BoundWith) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, "Invalid bound_with geometry ID") - } - } - geoTypeCode := int16(0) if geo.Type != "" { if n, err := strconv.ParseInt(geo.Type, 10, 16); err == nil { @@ -734,13 +732,13 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec if _, ok := persistCurrentItemIDs[geo.ID]; ok { params := sqlc.UpdateGeometryParams{ - ID: geometryUUID, - GeoType: pgtype.Int2{Int16: geoTypeCode, Valid: true}, - DrawGeometry: geo.DrawGeometry, - BoundWith: boundWith, - TimeStart: convert.PtrFloat64ToInt4(geo.TimeStart), - TimeEnd: convert.PtrFloat64ToInt4(geo.TimeEnd), - ProjectID: projectUUID, + ID: geometryUUID, + GeoType: pgtype.Int2{Int16: geoTypeCode, Valid: true}, + DrawGeometry: geo.DrawGeometry, + UpdateBoundWith: pgtype.Bool{Bool: false, Valid: true}, + TimeStart: convert.PtrFloat64ToInt4(geo.TimeStart), + TimeEnd: convert.PtrFloat64ToInt4(geo.TimeEnd), + ProjectID: projectUUID, } if geo.BBox != nil { @@ -762,7 +760,7 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec ID: geometryUUID, GeoType: geoTypeCode, DrawGeometry: geo.DrawGeometry, - BoundWith: boundWith, + BoundWith: pgtype.UUID{Valid: false}, TimeStart: convert.PtrFloat64ToInt4(geo.TimeStart), TimeEnd: convert.PtrFloat64ToInt4(geo.TimeEnd), ProjectID: projectUUID, @@ -789,6 +787,41 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec } snapshotData.Geometries = newGeometries + for _, geo := range snapshotData.Geometries { + if geo.Operation == "delete" || geo.Source == "ref" { + continue + } + + geometryUUID, err := convert.StringToUUID(geo.ID) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID") + } + + var boundWith pgtype.UUID + if geo.BoundWith != nil && *geo.BoundWith != "" { + if !validGeometries[*geo.BoundWith] { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Geometry %s references a non-existent or deleted geometry %s as bound_with", geo.ID, *geo.BoundWith)) + } + if *geo.BoundWith == geo.ID { + return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Geometry %s cannot be bound to itself", geo.ID)) + } + + boundWith, err = convert.StringToUUID(*geo.BoundWith) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Invalid bound_with geometry ID") + } + } + params := sqlc.UpdateGeometryParams{ + ID: geometryUUID, + UpdateBoundWith: pgtype.Bool{Bool: true, Valid: true}, + BoundWith: boundWith, + } + _, err = geometryRepo.Update(ctx, params) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, "Failed to update geometry bound_with: "+err.Error()) + } + } + refWikiIDs := []string{} for _, w := range snapshotData.Wikis { if w.Source == "ref" { @@ -928,10 +961,6 @@ func (s *submissionService) applySnapshot(ctx context.Context, tx pgx.Tx, projec for _, e := range snapshotData.Entities { validEntities[e.ID] = true } - validGeometries := make(map[string]bool) - for _, g := range snapshotData.Geometries { - validGeometries[g.ID] = true - } validWikis := make(map[string]bool) for _, w := range snapshotData.Wikis { validWikis[w.ID] = true