diff --git a/src/backend/app/central/central_crud.py b/src/backend/app/central/central_crud.py index 44f701633b..243ee8efcb 100644 --- a/src/backend/app/central/central_crud.py +++ b/src/backend/app/central/central_crud.py @@ -593,6 +593,34 @@ async def create_entity_list( ) +async def create_entity( + odk_creds: central_schemas.ODKCentralDecrypted, + odk_id: int, + properties: list[str], + entity: central_schemas.EntityDict, + dataset_name: str = "features", +) -> None: + """Create a new Entity in ODK.""" + log.info(f"Creating ODK Entity in dataset '{dataset_name}' (ODK ID: {odk_id})") + try: + properties = central_schemas.entity_fields_to_list(properties) + + label = entity.get("label") + data = entity.get("data") + + if not label or not data: + log.error("Missing required entity fields: 'label' or 'data'") + raise ValueError("Entity must contain 'label' and 'data' fields") + + async with central_deps.get_odk_dataset(odk_creds) as odk_central: + await odk_central.createEntity(odk_id, dataset_name, label, data) + log.info(f"Entity '{label}' successfully created in ODK") + + except Exception as e: + log.exception(f"Failed to create entity in ODK: {str(e)}") + raise + + async def get_entities_geojson( odk_creds: central_schemas.ODKCentralDecrypted, odk_id: int, diff --git a/src/backend/app/projects/project_routes.py b/src/backend/app/projects/project_routes.py index 79540f437e..2533d8c53a 100644 --- a/src/backend/app/projects/project_routes.py +++ b/src/backend/app/projects/project_routes.py @@ -21,7 +21,7 @@ import os from io import BytesIO from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated, Any, Dict, Optional import requests from fastapi import ( @@ -69,11 +69,13 @@ DbUserRole, ) from app.db.postgis_utils import ( + add_required_geojson_properties, check_crs, featcol_keep_single_geom_type, flatgeobuf_to_featcol, merge_polygons, parse_geojson_file_to_featcol, + split_geojson_by_task_areas, ) from app.organisations import organisation_deps from app.projects import project_crud, project_deps, project_schemas @@ -883,6 +885,58 @@ async def add_additional_entity_list( return Response(status_code=HTTPStatus.OK) +@router.post("/{project_id}/create-entity") +async def add_new_entity( + db: Annotated[Connection, Depends(db_conn)], + project_user_dict: Annotated[ProjectUserDict, Depends(project_manager)], + geojson: Dict[str, Any], +): + """Create an Entity for the project in ODK.""" + try: + project = project_user_dict.get("project") + project_odk_id = project.odkid + project_odk_creds = project.odk_credentials + + features = geojson.get("features") + if not features or not isinstance(features, list): + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="Invalid GeoJSON format" + ) + + # Add required properties and extract entity data + featcol = add_required_geojson_properties(geojson) + properties = list(featcol["features"][0]["properties"].keys()) + task_geojson = await split_geojson_by_task_areas(db, featcol, project.id) + entities_list = await central_crud.task_geojson_dict_to_entity_values( + task_geojson + ) + + if not entities_list: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, detail="No valid entities found" + ) + + # Create entity in ODK + await central_crud.create_entity( + project_odk_creds, + project_odk_id, + properties=properties, + entity=entities_list[0], + dataset_name="features", + ) + + return Response(status_code=HTTPStatus.OK) + except HTTPException as http_err: + log.error(f"HTTP error: {http_err.detail}") + raise + except Exception as e: + log.exception("Unexpected error during entity creation") + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Entity creation failed", + ) from e + + @router.post("/{project_id}/generate-project-data") async def generate_files( db: Annotated[Connection, Depends(db_conn)],