Skip to content

Commit

Permalink
create a table to store bad and new geoms (#2046)
Browse files Browse the repository at this point in the history
* feat: create geometrylog table to store bad and new geoms

* feat: add geometrylog migration in fmtm base schema
  • Loading branch information
Sujanadh authored Jan 2, 2025
1 parent bc08780 commit d05d4c3
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 4 deletions.
7 changes: 7 additions & 0 deletions src/backend/app/db/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,10 @@ class XLSFormType(StrEnum, Enum):
# religious = "religious"
# landusage = "landusage"
# waterways = "waterways"


class GeomStatus(StrEnum, Enum):
"""Geometry status."""

NEW = "NEW"
BAD = "BAD"
61 changes: 61 additions & 0 deletions src/backend/app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
BackgroundTaskStatus,
CommunityType,
EntityState,
GeomStatus,
HTTPStatus,
MappingLevel,
MappingState,
Expand Down Expand Up @@ -70,6 +71,7 @@
BackgroundTaskUpdate,
BasemapIn,
BasemapUpdate,
GeometryLogIn,
ProjectIn,
ProjectUpdate,
)
Expand Down Expand Up @@ -1741,3 +1743,62 @@ def slugify(name: Optional[str]) -> Optional[str]:
# Remove consecutive hyphens
slug = sub(r"[-\s]+", "-", slug)
return slug


class DbGeometryLog(BaseModel):
"""Table geometry log."""

geom: dict
status: GeomStatus
project_id: Optional[int]
task_id: Optional[int]

@classmethod
async def create(
cls,
db: Connection,
geometry_log_in: "GeometryLogIn",
) -> Self:
"""Creates a new geometry log with its status."""
model_dump = dump_and_check_model(geometry_log_in)
columns = []
value_placeholders = []

for key in model_dump.keys():
columns.append(key)
if key == "geom":
value_placeholders.append(f"ST_GeomFromGeoJSON(%({key})s)")
# Must be string json for db input
model_dump[key] = json.dumps(model_dump[key])
else:
value_placeholders.append(f"%({key})s")
async with db.cursor(row_factory=class_row(cls)) as cur:
await cur.execute(
f"""
INSERT INTO geometrylog
({", ".join(columns)})
VALUES
({", ".join(value_placeholders)})
RETURNING
*,
ST_AsGeoJSON(geom)::jsonb AS geom;
""",
model_dump,
)
new_geomlog = await cur.fetchone()
return new_geomlog

@classmethod
async def delete(
cls,
db: Connection,
id: int,
) -> bool:
"""Delete a geometry."""
async with db.cursor() as cur:
await cur.execute(
"""
DELETE FROM geometrylog WHERE id = %(id)s;
""",
{"id": id},
)
24 changes: 24 additions & 0 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from app.db.models import (
DbBackgroundTask,
DbBasemap,
DbGeometryLog,
DbOdkEntities,
DbProject,
DbTask,
Expand Down Expand Up @@ -1254,3 +1255,26 @@ async def download_task_boundaries(
}

return Response(content=out, headers=headers)


@router.post("/{project_id}/geometries")
async def create_geom_log(
geom_log: project_schemas.GeometryLogIn,
db: Annotated[Connection, Depends(db_conn)],
):
"""Creates a new entry in the geometries log table.
Returns:
geometries (DbGeometryLog): The created geometries log entry.
Raises:
HTTPException: If the geometries log creation fails.
"""
geometries = await DbGeometryLog.create(db, geom_log)
if not geometries:
raise HTTPException(
status_code=HTTPStatus.CONFLICT,
detail="geometries log creation failed.",
)

return geometries
29 changes: 25 additions & 4 deletions src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@

from app.central.central_schemas import ODKCentralDecrypted, ODKCentralIn
from app.config import decrypt_value, encrypt_value, settings
from app.db.enums import (
BackgroundTaskStatus,
ProjectPriority,
)
from app.db.enums import BackgroundTaskStatus, GeomStatus, ProjectPriority
from app.db.models import DbBackgroundTask, DbBasemap, DbProject, slugify
from app.db.postgis_utils import (
geojson_to_featcol,
Expand All @@ -46,6 +43,30 @@
)


class GeometryLogIn(BaseModel):
"""Geometry log insert."""

status: GeomStatus
geom: dict
project_id: Optional[int]
task_id: Optional[int]

@field_validator("geom", mode="before")
@classmethod
def parse_input_geometry(
cls,
value: FeatureCollection | Feature | MultiPolygon | Polygon,
) -> Optional[dict]:
"""Parse any format geojson into featurecollection.
Return geometry only.
"""
if value is None:
return None
featcol = geojson_to_featcol(value)
return featcol.get("features")[0].get("geometry")


class ProjectInBase(DbProject):
"""Base model for project insert / update (validators)."""

Expand Down
21 changes: 21 additions & 0 deletions src/backend/migrations/002-create-geometry-log.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- ## Migration to create a table to store new and bad geometries.

-- Start a transaction

BEGIN;

CREATE TABLE geometrylog (
id SERIAL PRIMARY KEY,
geom GEOMETRY NOT NULL,
status geomstatus,
project_id int,
task_id int
);

ALTER TABLE geometrylog OWNER TO fmtm;

-- Indexes for efficient querying
CREATE INDEX idx_geometrylog ON geometrylog USING gist (geom);

-- Commit the transaction
COMMIT;
14 changes: 14 additions & 0 deletions src/backend/migrations/init/fmtm_base_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,15 @@ ALTER TABLE public.submission_photos_id_seq OWNER TO fmtm;
ALTER SEQUENCE public.submission_photos_id_seq
OWNED BY public.submission_photos.id;

CREATE TABLE geometrylog (
id SERIAL PRIMARY KEY,
geom GEOMETRY NOT NULL,
status geomstatus,
project_id int,
task_id int
);
ALTER TABLE geometrylog OWNER TO fmtm;

-- nextval for primary keys (autoincrement)

ALTER TABLE ONLY public.organisations ALTER COLUMN id SET DEFAULT nextval(
Expand Down Expand Up @@ -458,6 +467,9 @@ ADD CONSTRAINT xlsforms_title_key UNIQUE (title);
ALTER TABLE ONLY public.submission_photos
ADD CONSTRAINT submission_photos_pkey PRIMARY KEY (id);

ALTER TABLE ONLY public.idx_geometrylog
ADD CONSTRAINT geometrylog_pkey PRIMARY KEY (id);

-- Indexing

CREATE INDEX idx_projects_outline ON public.projects USING gist (outline);
Expand Down Expand Up @@ -504,6 +516,8 @@ CREATE INDEX idx_entities_task_id
ON public.odk_entities USING btree (
entity_id, task_id
);
CREATE INDEX idx_geometrylog
ON geometrylog USING gist (geom);

-- Foreign keys

Expand Down
11 changes: 11 additions & 0 deletions src/backend/migrations/revert/002-create-geometry-log.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- ## Migration to delete geometrylog table.

-- Start a transaction

BEGIN;

DROP TABLE IF EXISTS geometrylog;

-- Commit the transaction

COMMIT;

0 comments on commit d05d4c3

Please sign in to comment.