Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create a table to store bad and new geoms #2046

Merged
merged 2 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1235,3 +1236,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
19 changes: 19 additions & 0 deletions src/backend/migrations/002-create-geometry-log.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- ## Migration to create a table to store new and bad geometries.

-- Start a transaction

BEGIN;

CREATE TABLE geometrylog (
Copy link
Member

@spwoodcock spwoodcock Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work πŸ‘

Don't forget the db init script though =)
(So the table exists on a new FMTM instance without migrations)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah. okay πŸ‘

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

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

-- Commit the transaction
COMMIT;
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;
Loading