diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index 74ccdad0..b19092c9 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -17,8 +17,11 @@ from urllib.parse import parse_qs, quote, urlencode, urlparse, urlunparse from app import EPSG, HOSTNAME, TOP_VALUE +from asyncpg.exceptions import UniqueViolationError from asyncpg.types import Range from dateutil import parser +from fastapi import status +from fastapi.responses import JSONResponse _USERNAME_RE = re.compile(r"^[a-zA-Z0-9_]{3,63}$") @@ -269,6 +272,29 @@ def validate_required_keys(payload, required_keys): raise Exception(f"Missing required fields: {', '.join(missing)}") +def handle_duplicate_error(e=None): + message = "Entity already exists." + + if e and hasattr(e, "detail") and e.detail: + # Example detail: "Key (name)=(duplicate test) already exists." + # We can extract the column name and the value from here + import re + match = re.search(r"Key \((.*?)\)=\((.*?)\) already exists", e.detail) + if match: + column = match.group(1) + value = match.group(2) + message = f"An entity with {column} '{value}' already exists." + + return JSONResponse( + status_code=status.HTTP_409_CONFLICT, + content={ + "code": 409, + "type": "error", + "message": message, + }, + ) + + def validate_epsg(key): crs = key.get("crs") if crs is not None: diff --git a/api/app/v1/endpoints/create/feature_of_interest.py b/api/app/v1/endpoints/create/feature_of_interest.py index a70fa5e0..287408a9 100644 --- a/api/app/v1/endpoints/create/feature_of_interest.py +++ b/api/app/v1/endpoints/create/feature_of_interest.py @@ -14,9 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -100,6 +104,8 @@ async def create_feature_of_interest( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/create/location.py b/api/app/v1/endpoints/create/location.py index a21786da..99cfdbdd 100644 --- a/api/app/v1/endpoints/create/location.py +++ b/api/app/v1/endpoints/create/location.py @@ -14,9 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -81,6 +85,7 @@ async def create_location( async with pool.acquire() as connection: async with connection.transaction(): + if current_user is not None: await set_role(connection, current_user) @@ -100,6 +105,8 @@ async def create_location( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, @@ -115,7 +122,7 @@ async def create_location( content={ "code": 400, "type": "error", - "message": str(e), + "message": f"[{type(e).__name__}] {str(e)}" }, ) @@ -172,6 +179,8 @@ async def create_location_for_thing( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, @@ -187,6 +196,6 @@ async def create_location_for_thing( content={ "code": 400, "type": "error", - "message": str(e), + "message": f"[{type(e).__name__}] {str(e)}", }, ) diff --git a/api/app/v1/endpoints/create/network.py b/api/app/v1/endpoints/create/network.py index 48c5abac..88862902 100644 --- a/api/app/v1/endpoints/create/network.py +++ b/api/app/v1/endpoints/create/network.py @@ -13,9 +13,13 @@ # limitations under the License. from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -90,6 +94,8 @@ async def create_network( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError as e: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/create/observed_property.py b/api/app/v1/endpoints/create/observed_property.py index 1a4cbd9b..abd53e6a 100644 --- a/api/app/v1/endpoints/create/observed_property.py +++ b/api/app/v1/endpoints/create/observed_property.py @@ -14,9 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -98,6 +102,8 @@ async def create_observed_property( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/create/sensor.py b/api/app/v1/endpoints/create/sensor.py index 5f6c3f3d..445e8c85 100644 --- a/api/app/v1/endpoints/create/sensor.py +++ b/api/app/v1/endpoints/create/sensor.py @@ -14,9 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -100,6 +104,8 @@ async def create_sensor( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/create/thing.py b/api/app/v1/endpoints/create/thing.py index 1591d7a7..f8562d76 100644 --- a/api/app/v1/endpoints/create/thing.py +++ b/api/app/v1/endpoints/create/thing.py @@ -14,9 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys, validate_required_keys +from app.utils.utils import ( + handle_duplicate_error, + validate_payload_keys, + validate_required_keys, +) from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, Request, status from fastapi.responses import JSONResponse, Response @@ -98,6 +102,8 @@ async def create_thing( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError as e: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, @@ -171,6 +177,8 @@ async def create_thing_for_location( status_code=status.HTTP_201_CREATED, headers={"location": header}, ) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/feature_of_interest.py b/api/app/v1/endpoints/update/feature_of_interest.py index d1a22e2b..5d5f855b 100644 --- a/api/app/v1/endpoints/update/feature_of_interest.py +++ b/api/app/v1/endpoints/update/feature_of_interest.py @@ -14,13 +14,13 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import ( get_datastreams_from_foi, set_role, update_datastream_observedArea, ) -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -133,6 +133,8 @@ async def update_feature_of_interest( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/location.py b/api/app/v1/endpoints/update/location.py index 36ba4bee..7679d359 100644 --- a/api/app/v1/endpoints/update/location.py +++ b/api/app/v1/endpoints/update/location.py @@ -14,9 +14,9 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -115,6 +115,8 @@ async def update_location( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/network.py b/api/app/v1/endpoints/update/network.py index a3761668..15565f0b 100644 --- a/api/app/v1/endpoints/update/network.py +++ b/api/app/v1/endpoints/update/network.py @@ -13,9 +13,9 @@ # limitations under the License. from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -101,6 +101,8 @@ async def update_network( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/observed_property.py b/api/app/v1/endpoints/update/observed_property.py index a6b00ed0..3e02eef1 100644 --- a/api/app/v1/endpoints/update/observed_property.py +++ b/api/app/v1/endpoints/update/observed_property.py @@ -14,9 +14,9 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -117,6 +117,8 @@ async def update_observed_property( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/sensor.py b/api/app/v1/endpoints/update/sensor.py index 662ef659..a9fba78b 100644 --- a/api/app/v1/endpoints/update/sensor.py +++ b/api/app/v1/endpoints/update/sensor.py @@ -14,9 +14,9 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -110,6 +110,8 @@ async def update_sensor( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/api/app/v1/endpoints/update/thing.py b/api/app/v1/endpoints/update/thing.py index ac5a3b71..cbdc4944 100644 --- a/api/app/v1/endpoints/update/thing.py +++ b/api/app/v1/endpoints/update/thing.py @@ -14,9 +14,9 @@ from app import AUTHORIZATION, POSTGRES_PORT_WRITE, VERSIONING from app.db.asyncpg_db import get_pool, get_pool_w -from app.utils.utils import validate_payload_keys +from app.utils.utils import handle_duplicate_error, validate_payload_keys from app.v1.endpoints.functions import set_role -from asyncpg.exceptions import InsufficientPrivilegeError +from asyncpg.exceptions import InsufficientPrivilegeError, UniqueViolationError from fastapi import APIRouter, Body, Depends, Header, status from fastapi.responses import JSONResponse, Response @@ -107,6 +107,8 @@ async def update_thing( await connection.execute("RESET ROLE;") return Response(status_code=status.HTTP_200_OK) + except UniqueViolationError as e: + return handle_duplicate_error(e) except InsufficientPrivilegeError: return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED,