From 3fc5149a614cea15283ae37775cdcb98d46d3f7a Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 9 Mar 2024 20:00:37 +0100 Subject: [PATCH 01/63] added API description --- API/main.py | 4 +++- Dockerfile | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/API/main.py b/API/main.py index f887895a..d1dc110f 100644 --- a/API/main.py +++ b/API/main.py @@ -75,7 +75,9 @@ os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" -app = FastAPI(title="Raw Data API ", swagger_ui_parameters={"syntaxHighlight": False}) +app = FastAPI(title="Raw Data API ", + description="The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats", + swagger_ui_parameters={"syntaxHighlight": False}) app.include_router(auth_router) app.include_router(raw_data_router) app.include_router(tasks_router) diff --git a/Dockerfile b/Dockerfile index ca79a03b..9da8c374 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,7 @@ COPY --from=builder /root/.local /home/appuser/.local COPY README.md . # Enable this if you are using config.txt -# COPY config.txt ./config.txt +COPY config.txt ./config.txt COPY setup.py . COPY pyproject.toml . From 1368f4d99a071d0bae0294a6464c6f86aecfe87d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:07:32 +0100 Subject: [PATCH 02/63] update Dockerfile --- Dockerfile | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5541bfdd..eaef19d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,36 +55,8 @@ RUN pip install --user --no-cache-dir --upgrade pip setuptools wheel\ RUN python setup.py install --user -<<<<<<< HEAD -RUN apt-get update \ - && apt-get -y upgrade \ - && apt-get --no-install-recommends -y install libpq5 gdal-bin \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=builder /root/.local /home/appuser/.local -COPY README.md . - -# Enable this if you are using config.txt -COPY config.txt ./config.txt - -COPY setup.py . -COPY pyproject.toml . -COPY API/ ./API/ -COPY src/ ./src/ -# Use a separate stage to pull the tippecanoe image -FROM ghcr.io/hotosm/tippecanoe:main as tippecanoe-builder - -FROM runner as prod - -# Copy tippecanoe binaries from the tippecanoe stage -COPY --from=tippecanoe-builder /usr/local/bin/tippecanoe* /usr/local/bin/ -COPY --from=tippecanoe-builder /usr/local/bin/tile-join /usr/local/bin/ -======= FROM with-tippecanoe as prod COPY --from=python-builder /root/.local /home/appuser/.local ->>>>>>> 7235f0f23d099b3b9560917bfd639a244c9def21 RUN useradd --system --uid 900 --home-dir /home/appuser --shell /bin/false appuser \ && chown -R appuser:appuser /home/appuser From 3ab42180e3e9f0b5d43db14bc1288f7c0f6c440d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 9 Mar 2024 22:10:57 +0100 Subject: [PATCH 03/63] updated API description --- API/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/main.py b/API/main.py index d1dc110f..6776b690 100644 --- a/API/main.py +++ b/API/main.py @@ -76,7 +76,7 @@ os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" app = FastAPI(title="Raw Data API ", - description="The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats", + description="The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats.", swagger_ui_parameters={"syntaxHighlight": False}) app.include_router(auth_router) app.include_router(raw_data_router) From 3aced09ba6ec0640933aca55418ab26d6df482c1 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:40:31 +0100 Subject: [PATCH 04/63] added AGPL-3.0 license --- API/main.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/API/main.py b/API/main.py index 6776b690..e761913f 100644 --- a/API/main.py +++ b/API/main.py @@ -75,9 +75,25 @@ os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" -app = FastAPI(title="Raw Data API ", +tags_metadata = [ + { + "name": "Auth", + "description": "Operations with users authentication", + }, + { + "name": "Extract", + "description": "Manage items. So _fancy_ they have their own docs.", + }, +] + +app = FastAPI(openapi_tags=tags_metadata, + title="Raw Data API ", description="The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats.", - swagger_ui_parameters={"syntaxHighlight": False}) + license_info={ "name": "AGPL-3.0 license", + "url": "https://www.gnu.org/licenses/#AGPL", + }, + swagger_ui_parameters={"syntaxHighlight": False} + ) app.include_router(auth_router) app.include_router(raw_data_router) app.include_router(tasks_router) From ec40d838ececde2aee0c4931316ad2299ec29f5e Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:09:56 +0100 Subject: [PATCH 05/63] added contact and license --- API/main.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/API/main.py b/API/main.py index e761913f..1e06f9fe 100644 --- a/API/main.py +++ b/API/main.py @@ -75,25 +75,20 @@ os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" -tags_metadata = [ - { - "name": "Auth", - "description": "Operations with users authentication", - }, - { - "name": "Extract", - "description": "Manage items. So _fancy_ they have their own docs.", - }, -] - -app = FastAPI(openapi_tags=tags_metadata, - title="Raw Data API ", - description="The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats.", - license_info={ "name": "AGPL-3.0 license", - "url": "https://www.gnu.org/licenses/#AGPL", - }, - swagger_ui_parameters={"syntaxHighlight": False} - ) +app = FastAPI(title="Raw Data API ", + description="""The Raw Data API allows you to transform + and export OpenStreetMap (OSM) data in different GIS file formats""", + contact={ + "name": "Humanitarian OpenStreetmap Team", + "url": "https://hotosm.org", + "email": "info@hotosm.org", + }, + license_info={ + "name": "AGPL-3.0 license", + "url": "https://www.gnu.org/licenses/", + }, + swagger_ui_parameters={"syntaxHighlight": False}) + app.include_router(auth_router) app.include_router(raw_data_router) app.include_router(tasks_router) From e6b97323f2d32ae3392fd098522249ed01e2d8aa Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:23:04 +0100 Subject: [PATCH 06/63] removed contact and license --- API/main.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/API/main.py b/API/main.py index 1e06f9fe..845de3ed 100644 --- a/API/main.py +++ b/API/main.py @@ -78,17 +78,8 @@ app = FastAPI(title="Raw Data API ", description="""The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats""", - contact={ - "name": "Humanitarian OpenStreetmap Team", - "url": "https://hotosm.org", - "email": "info@hotosm.org", - }, - license_info={ - "name": "AGPL-3.0 license", - "url": "https://www.gnu.org/licenses/", - }, swagger_ui_parameters={"syntaxHighlight": False}) - + app.include_router(auth_router) app.include_router(raw_data_router) app.include_router(tasks_router) @@ -107,6 +98,17 @@ "info": { "title": "Raw Data API", "version": "1.0", + "description": """The Raw Data API allows you to transform + and export OpenStreetMap (OSM) data in different GIS file formats""", + "contact": { + "name": "Humanitarian OpenStreetmap Team", + "url": "https://hotosm.org", + "email": "info@hotosm.org", + }, + "license_info":{ + "name": "AGPL-3.0 license", + "url": "https://www.gnu.org/licenses/", + }, }, "security": [{"OAuth2PasswordBearer": []}], } From 72ac9760ea62635d89d6ac45c989ad356ca3b861 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 08:56:44 +0100 Subject: [PATCH 07/63] removed license and contact from API description --- API/main.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/API/main.py b/API/main.py index 845de3ed..a4240d96 100644 --- a/API/main.py +++ b/API/main.py @@ -98,17 +98,6 @@ "info": { "title": "Raw Data API", "version": "1.0", - "description": """The Raw Data API allows you to transform - and export OpenStreetMap (OSM) data in different GIS file formats""", - "contact": { - "name": "Humanitarian OpenStreetmap Team", - "url": "https://hotosm.org", - "email": "info@hotosm.org", - }, - "license_info":{ - "name": "AGPL-3.0 license", - "url": "https://www.gnu.org/licenses/", - }, }, "security": [{"OAuth2PasswordBearer": []}], } From 905e29ee66cef02a7f71a9243d009a984d1e5553 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:38:04 +0100 Subject: [PATCH 08/63] added 403, 404, and 500 responses to Auth --- API/auth/routers.py | 64 +++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 438a28e4..a15c4ea5 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -1,6 +1,7 @@ import json -from fastapi import APIRouter, Depends, Request +from fastapi.responses import JSONResponse +from fastapi import APIRouter, Depends, Request, HTTPException from pydantic import BaseModel from src.app import Users @@ -10,7 +11,20 @@ router = APIRouter(prefix="/auth", tags=["Auth"]) -@router.get("/login/") +class ErrorMessage(BaseModel): + detail: str + + +responses = { + 403: {"model": ErrorMessage, + "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, + 500: {"model": ErrorMessage, + "content": {"application/json": {"example": {"detail": "Internal Server Error"}}}}, +} + + +@router.get("/login/", responses={500: {"model": ErrorMessage}, + 200: {"content": {"application/json": {"example": {"loginUrl": ""}}}}}) def login_url(request: Request): """Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. Click on the download url returned to get access_token. @@ -21,11 +35,12 @@ def login_url(request: Request): - login_url (dict) - URL to authorize user to the application via. Openstreetmap OAuth2 with client_id, redirect_uri, and permission scope as query_string parameters """ + login_url = osm_auth.login() return login_url -@router.get("/callback/") +@router.get("/callback/", responses={500: {"model": ErrorMessage}}) def callback(request: Request): """Performs token exchange between OpenStreetMap and Raw Data API @@ -37,23 +52,27 @@ def callback(request: Request): Returns: - access_token (string) """ - access_token = osm_auth.callback(str(request.url)) - return access_token + access_token = osm_auth.callback(str(request.url)) + return access_token -@router.get("/me/", response_model=AuthUser) +@router.get("/me/", response_model=AuthUser, responses={**responses}) def my_data(user_data: AuthUser = Depends(login_required)): """Read the access token and provide user details from OSM user's API endpoint, also integrated with underpass . Parameters:None - Returns: user_data + Returns: user_data\n User Role : ADMIN = 1 STAFF = 2 GUEST = 3 + + Raises: + - HTTPException 403: Due to authentication error(Wrong access token). + - HTTPException 500: Internal server error. """ return user_data @@ -61,10 +80,10 @@ def my_data(user_data: AuthUser = Depends(login_required)): class User(BaseModel): osm_id: int role: int - + # Create user -@router.post("/users/", response_model=dict) +@router.post("/users/", response_model=dict, responses={**responses}) async def create_user(params: User, user_data: AuthUser = Depends(admin_required)): """ Creates a new user and returns the user's information. @@ -80,14 +99,15 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required - Dict[str, Any]: A dictionary containing the osm_id of the newly created user. Raises: - - HTTPException: If the user creation fails. + - HTTPException 403: If the user creation fails due to insufficient permission. + - HTTPException 500: If the user creation fails due to internal server error. """ auth = Users() return auth.create_user(params.osm_id, params.role) # Read user by osm_id -@router.get("/users/{osm_id}", response_model=dict) +@router.get("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): """ Retrieves user information based on the given osm_id. @@ -103,7 +123,9 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): - Dict[str, Any]: A dictionary containing user information. Raises: - - HTTPException: If the user with the given osm_id is not found. + - HTTPException 403: If the user has insufficient permission. + - HTTPException 404: If the user with the given osm_id is not found. + - HTTPException 500: If it fails due to internal server error. """ auth = Users() @@ -111,7 +133,7 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): # Update user by osm_id -@router.put("/users/{osm_id}", response_model=dict) +@router.put("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) async def update_user( osm_id: int, update_data: User, user_data: AuthUser = Depends(admin_required) ): @@ -129,14 +151,16 @@ async def update_user( - Dict[str, Any]: A dictionary containing the updated user information. Raises: - - HTTPException: If the user with the given osm_id is not found. + - HTTPException 403: If the user has insufficient permission. + - HTTPException 404: If the user with the given osm_id is not found. + - HTTPException 500: If it fails due to internal server error. """ auth = Users() return auth.update_user(osm_id, update_data) # Delete user by osm_id -@router.delete("/users/{osm_id}", response_model=dict) +@router.delete("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)): """ Deletes a user based on the given osm_id. @@ -148,14 +172,16 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required) - Dict[str, Any]: A dictionary containing the deleted user information. Raises: - - HTTPException: If the user with the given osm_id is not found. + - HTTPException 403: If the user has insufficient permission. + - HTTPException 404: If the user with the given osm_id is not found. + - HTTPException 500: If it fails due to internal server error. """ auth = Users() return auth.delete_user(osm_id) # Get all users -@router.get("/users/", response_model=list) +@router.get("/users/", response_model=list, responses={**responses}) async def read_users( skip: int = 0, limit: int = 10, user_data: AuthUser = Depends(staff_required) ): @@ -168,6 +194,10 @@ async def read_users( Returns: - List[Dict[str, Any]]: A list of dictionaries containing user information. + + Raises: + - HTTPException 403: If it fails due to insufficient permission. + - HTTPException 500: If it fails due to internal server error. """ auth = Users() return auth.read_users(skip, limit) From 617c03b98ca1ee6780e32d7b1964a222ca104203 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 14:01:21 +0100 Subject: [PATCH 09/63] removed trailing slashes from Auth --- API/auth/routers.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index a15c4ea5..f07fc5f8 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -1,7 +1,6 @@ import json -from fastapi.responses import JSONResponse -from fastapi import APIRouter, Depends, Request, HTTPException +from fastapi import APIRouter, Depends, Request from pydantic import BaseModel from src.app import Users @@ -23,7 +22,7 @@ class ErrorMessage(BaseModel): } -@router.get("/login/", responses={500: {"model": ErrorMessage}, +@router.get("/login", responses={500: {"model": ErrorMessage}, 200: {"content": {"application/json": {"example": {"loginUrl": ""}}}}}) def login_url(request: Request): """Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. @@ -40,7 +39,7 @@ def login_url(request: Request): return login_url -@router.get("/callback/", responses={500: {"model": ErrorMessage}}) +@router.get("/callback", responses={500: {"model": ErrorMessage}}) def callback(request: Request): """Performs token exchange between OpenStreetMap and Raw Data API @@ -57,7 +56,7 @@ def callback(request: Request): return access_token -@router.get("/me/", response_model=AuthUser, responses={**responses}) +@router.get("/me", response_model=AuthUser, responses={**responses}) def my_data(user_data: AuthUser = Depends(login_required)): """Read the access token and provide user details from OSM user's API endpoint, also integrated with underpass . @@ -83,7 +82,7 @@ class User(BaseModel): # Create user -@router.post("/users/", response_model=dict, responses={**responses}) +@router.post("/users", response_model=dict, responses={**responses}) async def create_user(params: User, user_data: AuthUser = Depends(admin_required)): """ Creates a new user and returns the user's information. @@ -181,7 +180,7 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required) # Get all users -@router.get("/users/", response_model=list, responses={**responses}) +@router.get("/users", response_model=list, responses={**responses}) async def read_users( skip: int = 0, limit: int = 10, user_data: AuthUser = Depends(staff_required) ): From 29241e82687824e975b5e3bbc125d99f3fd42d84 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:34:43 +0100 Subject: [PATCH 10/63] removed trailing slashes --- API/raw_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/API/raw_data.py b/API/raw_data.py index 46c8e068..0577d59e 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -51,7 +51,7 @@ redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) -@router.get("/status/", response_model=StatusResponse) +@router.get("/status", response_model=StatusResponse) @version(1) def check_database_last_updated(): """Gives status about how recent the osm data is , it will give the last time that database was updated completely""" @@ -59,7 +59,7 @@ def check_database_last_updated(): return {"last_updated": result} -@router.post("/snapshot/", response_model=SnapshotResponse) +@router.post("/snapshot", response_model=SnapshotResponse) @limiter.limit(f"{export_rate_limit}/minute") @version(1) def get_osm_current_snapshot_as_file( @@ -462,7 +462,7 @@ def get_osm_current_snapshot_as_file( ) -@router.post("/snapshot/plain/") +@router.post("/snapshot/plain") @version(1) def get_osm_current_snapshot_as_plain_geojson( request: Request, @@ -494,14 +494,14 @@ def get_osm_current_snapshot_as_plain_geojson( return result -@router.get("/countries/") +@router.get("/countries") @version(1) def get_countries(q: str = ""): result = RawData().get_countries_list(q) return result -@router.get("/osm_id/") +@router.get("/osm_id") @version(1) def get_osm_feature(osm_id: int): return RawData().get_osm_feature(osm_id) From ab6ff22561362f3716fc899678fe676186fe7151 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:05:30 +0100 Subject: [PATCH 11/63] moved error message model and responses to models.py --- API/auth/routers.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index f07fc5f8..75f62964 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -4,24 +4,13 @@ from pydantic import BaseModel from src.app import Users +from src.validation.models import ErrorMessage, common_responses from . import AuthUser, admin_required, login_required, osm_auth, staff_required router = APIRouter(prefix="/auth", tags=["Auth"]) -class ErrorMessage(BaseModel): - detail: str - - -responses = { - 403: {"model": ErrorMessage, - "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, - 500: {"model": ErrorMessage, - "content": {"application/json": {"example": {"detail": "Internal Server Error"}}}}, -} - - @router.get("/login", responses={500: {"model": ErrorMessage}, 200: {"content": {"application/json": {"example": {"loginUrl": ""}}}}}) def login_url(request: Request): @@ -56,7 +45,7 @@ def callback(request: Request): return access_token -@router.get("/me", response_model=AuthUser, responses={**responses}) +@router.get("/me", response_model=AuthUser, responses={**common_responses}) def my_data(user_data: AuthUser = Depends(login_required)): """Read the access token and provide user details from OSM user's API endpoint, also integrated with underpass . @@ -82,7 +71,7 @@ class User(BaseModel): # Create user -@router.post("/users", response_model=dict, responses={**responses}) +@router.post("/users", response_model=dict, responses={**common_responses}) async def create_user(params: User, user_data: AuthUser = Depends(admin_required)): """ Creates a new user and returns the user's information. @@ -106,7 +95,8 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required # Read user by osm_id -@router.get("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) +@router.get("/users/{osm_id}", response_model=dict, + responses={**common_responses, 404:{"model": ErrorMessage}}) async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): """ Retrieves user information based on the given osm_id. @@ -132,7 +122,8 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): # Update user by osm_id -@router.put("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) +@router.put("/users/{osm_id}", response_model=dict, + responses={**common_responses, 404:{"model": ErrorMessage}}) async def update_user( osm_id: int, update_data: User, user_data: AuthUser = Depends(admin_required) ): @@ -159,7 +150,8 @@ async def update_user( # Delete user by osm_id -@router.delete("/users/{osm_id}", response_model=dict, responses={**responses, 404:{"model": ErrorMessage}}) +@router.delete("/users/{osm_id}", response_model=dict, + responses={**common_responses, 404:{"model": ErrorMessage}}) async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)): """ Deletes a user based on the given osm_id. @@ -180,7 +172,7 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required) # Get all users -@router.get("/users", response_model=list, responses={**responses}) +@router.get("/users", response_model=list, responses={**common_responses}) async def read_users( skip: int = 0, limit: int = 10, user_data: AuthUser = Depends(staff_required) ): From 91259e8d35a83f6bc4ac464576013b11395757c3 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:06:40 +0100 Subject: [PATCH 12/63] added error model and common responses --- src/validation/models.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/validation/models.py b/src/validation/models.py index 0e3a3a63..40418f35 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -288,6 +288,16 @@ class Config: json_schema_extra = {"example": {"lastUpdated": "2022-06-27 19:59:24+05:45"}} +class ErrorMessage(BaseModel): + detail: str + +common_responses = { + 403: {"model": ErrorMessage, + "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, + 500: {"model": ErrorMessage, + "content": {"application/json": {"example": {"detail": "Internal Server Error"}}}}, +} + class StatsRequestParams(BaseModel, GeometryValidatorMixin): iso3: Optional[str] = Field( default=None, From 5962515b07dac64c77f43c1150dc27c9cd88ba7c Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:28:13 +0100 Subject: [PATCH 13/63] 403, 404, and 500 responses for Extract endpoints --- API/raw_data.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/API/raw_data.py b/API/raw_data.py index 0577d59e..4ea6d1d4 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -41,6 +41,8 @@ RawDataCurrentParamsBase, SnapshotResponse, StatusResponse, + ErrorMessage, + common_responses ) from .api_worker import process_raw_data @@ -51,7 +53,8 @@ redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) -@router.get("/status", response_model=StatusResponse) + +@router.get("/status", response_model=StatusResponse, responses={'500': {"model": ErrorMessage}}) @version(1) def check_database_last_updated(): """Gives status about how recent the osm data is , it will give the last time that database was updated completely""" @@ -59,7 +62,7 @@ def check_database_last_updated(): return {"last_updated": result} -@router.post("/snapshot", response_model=SnapshotResponse) +@router.post("/snapshot", response_model=SnapshotResponse, responses={**common_responses, 404:{"model": ErrorMessage}}) @limiter.limit(f"{export_rate_limit}/minute") @version(1) def get_osm_current_snapshot_as_file( @@ -462,7 +465,7 @@ def get_osm_current_snapshot_as_file( ) -@router.post("/snapshot/plain") +@router.post("/snapshot/plain", responses={**common_responses, 404:{"model": ErrorMessage}}) @version(1) def get_osm_current_snapshot_as_plain_geojson( request: Request, @@ -494,14 +497,14 @@ def get_osm_current_snapshot_as_plain_geojson( return result -@router.get("/countries") +@router.get("/countries", responses={'500': {"model": ErrorMessage}}) @version(1) def get_countries(q: str = ""): result = RawData().get_countries_list(q) return result -@router.get("/osm_id") +@router.get("/osm_id", responses={'500': {"model": ErrorMessage}}) @version(1) def get_osm_feature(osm_id: int): return RawData().get_osm_feature(osm_id) From 9a67c7e76d29d549d8bf9807b9655189fdd07757 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:57:11 +0100 Subject: [PATCH 14/63] added 403, 404, and 500 responses to Task endpoints --- API/tasks.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/API/tasks.py b/API/tasks.py index bca37813..d82304f2 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -8,7 +8,7 @@ from fastapi_versioning import version from src.config import CELERY_BROKER_URL, DAEMON_QUEUE_NAME, DEFAULT_QUEUE_NAME -from src.validation.models import SnapshotTaskResponse +from src.validation.models import SnapshotTaskResponse, ErrorMessage, common_responses from .api_worker import celery from .auth import AuthUser, admin_required, login_required, staff_required @@ -16,7 +16,8 @@ router = APIRouter(prefix="/tasks", tags=["Tasks"]) -@router.get("/status/{task_id}/", response_model=SnapshotTaskResponse) +@router.get("/status/{task_id}/", response_model=SnapshotTaskResponse, + responses={'500': {"model": ErrorMessage}}) @version(1) def get_task_status( task_id, @@ -78,7 +79,7 @@ def get_task_status( return JSONResponse(result) -@router.get("/revoke/{task_id}/") +@router.get("/revoke/{task_id}/", responses={**common_responses}) @version(1) def revoke_task(task_id, user: AuthUser = Depends(staff_required)): """Revokes task , Terminates if it is executing @@ -93,7 +94,7 @@ def revoke_task(task_id, user: AuthUser = Depends(staff_required)): return JSONResponse({"id": task_id}) -@router.get("/inspect/") +@router.get("/inspect/", responses={'500': {"model": ErrorMessage}}) @version(1) def inspect_workers( request: Request, @@ -134,7 +135,7 @@ def inspect_workers( return JSONResponse(content=response_data) -@router.get("/ping/") +@router.get("/ping/", responses={'500': {"model": ErrorMessage}}) @version(1) def ping_workers(): """Pings available workers @@ -145,7 +146,7 @@ def ping_workers(): return JSONResponse(inspected_ping) -@router.get("/purge/") +@router.get("/purge/", responses={**common_responses}) @version(1) def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): """ @@ -159,7 +160,7 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): queues = [DEFAULT_QUEUE_NAME, DAEMON_QUEUE_NAME] -@router.get("/queue/") +@router.get("/queue/", responses={'500': {"model": ErrorMessage}}) @version(1) def get_queue_info(): queue_info = {} @@ -176,7 +177,8 @@ def get_queue_info(): return JSONResponse(content=queue_info) -@router.get("/queue/details/{queue_name}/") +@router.get("/queue/details/{queue_name}/", + responses={**common_responses, '404': {"model": ErrorMessage}}) @version(1) def get_list_details( queue_name: str, From 9bc533a3a0584b8ea908fb36250a47f0817e879a Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 12 Mar 2024 22:58:48 +0100 Subject: [PATCH 15/63] removed trailing slashes from Tasks endpoints --- API/tasks.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/API/tasks.py b/API/tasks.py index d82304f2..390b237a 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -16,7 +16,7 @@ router = APIRouter(prefix="/tasks", tags=["Tasks"]) -@router.get("/status/{task_id}/", response_model=SnapshotTaskResponse, +@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'500': {"model": ErrorMessage}}) @version(1) def get_task_status( @@ -79,7 +79,7 @@ def get_task_status( return JSONResponse(result) -@router.get("/revoke/{task_id}/", responses={**common_responses}) +@router.get("/revoke/{task_id}", responses={**common_responses}) @version(1) def revoke_task(task_id, user: AuthUser = Depends(staff_required)): """Revokes task , Terminates if it is executing @@ -94,7 +94,7 @@ def revoke_task(task_id, user: AuthUser = Depends(staff_required)): return JSONResponse({"id": task_id}) -@router.get("/inspect/", responses={'500': {"model": ErrorMessage}}) +@router.get("/inspect", responses={'500': {"model": ErrorMessage}}) @version(1) def inspect_workers( request: Request, @@ -135,7 +135,7 @@ def inspect_workers( return JSONResponse(content=response_data) -@router.get("/ping/", responses={'500': {"model": ErrorMessage}}) +@router.get("/ping", responses={'500': {"model": ErrorMessage}}) @version(1) def ping_workers(): """Pings available workers @@ -146,7 +146,7 @@ def ping_workers(): return JSONResponse(inspected_ping) -@router.get("/purge/", responses={**common_responses}) +@router.get("/purge", responses={**common_responses}) @version(1) def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): """ @@ -160,7 +160,7 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): queues = [DEFAULT_QUEUE_NAME, DAEMON_QUEUE_NAME] -@router.get("/queue/", responses={'500': {"model": ErrorMessage}}) +@router.get("/queue", responses={'500': {"model": ErrorMessage}}) @version(1) def get_queue_info(): queue_info = {} @@ -177,7 +177,7 @@ def get_queue_info(): return JSONResponse(content=queue_info) -@router.get("/queue/details/{queue_name}/", +@router.get("/queue/details/{queue_name}", responses={**common_responses, '404': {"model": ErrorMessage}}) @version(1) def get_list_details( From 6fe7b07a111c8be06fc474ed8fbb70fb69400931 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:48:08 +0100 Subject: [PATCH 16/63] updated 500 response --- src/validation/models.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/validation/models.py b/src/validation/models.py index 40418f35..d3c29b06 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -292,10 +292,13 @@ class ErrorMessage(BaseModel): detail: str common_responses = { - 403: {"model": ErrorMessage, - "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, - 500: {"model": ErrorMessage, - "content": {"application/json": {"example": {"detail": "Internal Server Error"}}}}, + 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, + 500: {}, +} + +login_responses = { + 200: {"content": {"application/json": {"example": {"login_url": "https://www.openstreetmap.org/oauth2/authorize/"}}}}, + 500: {}, } class StatsRequestParams(BaseModel, GeometryValidatorMixin): From 0b6907228e564bed97c5b3213240b1c4bdf3b594 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:49:04 +0100 Subject: [PATCH 17/63] added login responses --- API/auth/routers.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 75f62964..611c807d 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -4,15 +4,14 @@ from pydantic import BaseModel from src.app import Users -from src.validation.models import ErrorMessage, common_responses +from src.validation.models import ErrorMessage, common_responses, login_responses from . import AuthUser, admin_required, login_required, osm_auth, staff_required router = APIRouter(prefix="/auth", tags=["Auth"]) -@router.get("/login", responses={500: {"model": ErrorMessage}, - 200: {"content": {"application/json": {"example": {"loginUrl": ""}}}}}) +@router.get("/login", responses={**login_responses}) def login_url(request: Request): """Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. Click on the download url returned to get access_token. @@ -95,8 +94,7 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required # Read user by osm_id -@router.get("/users/{osm_id}", response_model=dict, - responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.get("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): """ Retrieves user information based on the given osm_id. @@ -122,8 +120,7 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): # Update user by osm_id -@router.put("/users/{osm_id}", response_model=dict, - responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.put("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) async def update_user( osm_id: int, update_data: User, user_data: AuthUser = Depends(admin_required) ): @@ -150,8 +147,7 @@ async def update_user( # Delete user by osm_id -@router.delete("/users/{osm_id}", response_model=dict, - responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.delete("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)): """ Deletes a user based on the given osm_id. From fe6cf0ef0d41ab0f88771874ae877c4e510ecce6 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:30:38 +0100 Subject: [PATCH 18/63] login 200 response description --- src/validation/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/models.py b/src/validation/models.py index d3c29b06..1d2044dc 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -297,7 +297,7 @@ class ErrorMessage(BaseModel): } login_responses = { - 200: {"content": {"application/json": {"example": {"login_url": "https://www.openstreetmap.org/oauth2/authorize/"}}}}, + 200: {"description": "A Login URL", "content": {"application/json": {"example": {"login_url": "https://www.openstreetmap.org/oauth2/authorize/"}}}}, 500: {}, } From 7b320e77082ee998a4ccea7ddd041a73339cf18c Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:32:40 +0100 Subject: [PATCH 19/63] updated login docstring --- API/auth/routers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 611c807d..670fde05 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -13,7 +13,8 @@ @router.get("/login", responses={**login_responses}) def login_url(request: Request): - """Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. + """ + Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. Click on the download url returned to get access_token. Parameters: None From 474220b664f2754bfa171648b90fbb414e592b0d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:13:57 +0100 Subject: [PATCH 20/63] added parameter description --- API/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 0ff71d79..c7338b12 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -43,7 +43,7 @@ def get_osm_auth_user(access_token): return user -def login_required(access_token: str = Header(...)): +def login_required(access_token: str = Header(..., description="Access token to authorize user")): return get_osm_auth_user(access_token) From 235eacd4809dd71a5b43fbc5363b912e26007543 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:17:01 +0100 Subject: [PATCH 21/63] updated 500 response --- API/auth/routers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 670fde05..e03eaad6 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -28,7 +28,7 @@ def login_url(request: Request): return login_url -@router.get("/callback", responses={500: {"model": ErrorMessage}}) +@router.get("/callback", responses={500: {}}) def callback(request: Request): """Performs token exchange between OpenStreetMap and Raw Data API From 870fab864e1b474626f6d9f8408926ceffc08d56 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:18:05 +0100 Subject: [PATCH 22/63] added 200 response description --- API/auth/routers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index e03eaad6..15ad0658 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -45,7 +45,7 @@ def callback(request: Request): return access_token -@router.get("/me", response_model=AuthUser, responses={**common_responses}) +@router.get("/me", response_model=AuthUser, responses={**common_responses}, response_description="User Information") def my_data(user_data: AuthUser = Depends(login_required)): """Read the access token and provide user details from OSM user's API endpoint, also integrated with underpass . From a9f0b18227fe6c074dec9d67219017511a4f9565 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:37:35 +0100 Subject: [PATCH 23/63] added schema example for AuthUser --- API/auth/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index c7338b12..8b86cad7 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -21,6 +21,16 @@ class AuthUser(BaseModel): img_url: Union[str, None] role: UserRole = Field(default=UserRole.GUEST.value) + class Config: + json_schema_extra = { + "example": { + "id": "123", + "username": "HOT Team", + "img_url": "https://hotteamimage.com", + "role": UserRole.GUEST.value, + } + } + osm_auth = Auth(*get_oauth_credentials()) From 2c2142b941f26c63356edda28885608bb3676c08 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:45:26 +0100 Subject: [PATCH 24/63] added parameter example --- API/auth/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 8b86cad7..2c0679de 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -53,7 +53,8 @@ def get_osm_auth_user(access_token): return user -def login_required(access_token: str = Header(..., description="Access token to authorize user")): +def login_required(access_token: str = + Header(..., description="Access Token to Authorize User", example="ujkq0jfr4jhv9e3buj0xsvynio8")): return get_osm_auth_user(access_token) From 824b146167c4d94e1d84676ececc9a357da094e9 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:58:24 +0100 Subject: [PATCH 25/63] updated error model --- src/validation/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/validation/models.py b/src/validation/models.py index 1d2044dc..7b755ed5 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -288,8 +288,11 @@ class Config: json_schema_extra = {"example": {"lastUpdated": "2022-06-27 19:59:24+05:45"}} +class ErrorDetail(BaseModel): + msg: str class ErrorMessage(BaseModel): - detail: str + detail: List[ErrorDetail] + common_responses = { 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, From 020f9e297841329b0588f2c834687e0f1126fc8d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:59:16 +0100 Subject: [PATCH 26/63] added User model example --- API/auth/routers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/API/auth/routers.py b/API/auth/routers.py index 15ad0658..15f52f86 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -68,6 +68,9 @@ def my_data(user_data: AuthUser = Depends(login_required)): class User(BaseModel): osm_id: int role: int + + class Config: + json_schema_extra = {"example": {"osm_id": 123, "role": 3}} # Create user From bc102d84796198429c423d059659d88954661856 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:01:20 +0100 Subject: [PATCH 27/63] access token example --- API/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 2c0679de..65135873 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -54,7 +54,7 @@ def get_osm_auth_user(access_token): def login_required(access_token: str = - Header(..., description="Access Token to Authorize User", example="ujkq0jfr4jhv9e3buj0xsvynio8")): + Header(..., description="Access Token to Authorize User", example="UJkq0jfR4JHV9e3Buj0xSVYNio8TWQ")): return get_osm_auth_user(access_token) From 21e53fd3fe5d8f464abc5f2b152e51b7a89a0b84 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:05:19 +0100 Subject: [PATCH 28/63] updated HTTPException message --- API/auth/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 65135873..5544dcbc 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -69,7 +69,7 @@ def get_optional_user(access_token: str = Header(default=None)) -> AuthUser: def admin_required(user: AuthUser = Depends(login_required)): db_user = get_user_from_db(user.id) if not db_user["role"] is UserRole.ADMIN.value: - raise HTTPException(status_code=403, detail="User is not an admin") + raise HTTPException(status_code=403, detail=[{"msg": "User is not an admin"}]) return user @@ -81,5 +81,5 @@ def staff_required(user: AuthUser = Depends(login_required)): db_user["role"] is UserRole.STAFF.value or db_user["role"] is UserRole.ADMIN.value ): - raise HTTPException(status_code=403, detail="User is not a staff") + raise HTTPException(status_code=403, detail=[{"msg": "User is not a staff"}]) return user From 8e19f59645abfad04b335e1a3299ee571d876345 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:37:23 +0100 Subject: [PATCH 29/63] added response example --- API/auth/routers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 15f52f86..5dc90e58 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -74,7 +74,8 @@ class Config: # Create user -@router.post("/users", response_model=dict, responses={**common_responses}) +@router.post("/users", response_model=dict, + responses={**common_responses, '200':{"content": {"application/json": {"example": {"osm_id": 123}}}}}) async def create_user(params: User, user_data: AuthUser = Depends(admin_required)): """ Creates a new user and returns the user's information. From a367e53b58fcb36ba725e0fd586f6504f8131458 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:38:39 +0100 Subject: [PATCH 30/63] updated example to match error message model --- src/validation/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validation/models.py b/src/validation/models.py index 7b755ed5..bd569dc2 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -295,7 +295,7 @@ class ErrorMessage(BaseModel): common_responses = { - 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": "OSM Authentication failed"}}}}, + 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": [{"msg": "OSM Authentication failed"}] }}}}, 500: {}, } From 2b845d90452e86287e5bcc4350dac60a40c8f30b Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:22:28 +0100 Subject: [PATCH 31/63] added parameter description --- API/auth/routers.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 5dc90e58..f2ae9e73 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -1,6 +1,6 @@ import json -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, Depends, Request, Query, Path from pydantic import BaseModel from src.app import Users @@ -100,7 +100,8 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required # Read user by osm_id @router.get("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) -async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): +async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retrieve"), + user_data: AuthUser = Depends(staff_required)): """ Retrieves user information based on the given osm_id. User Role : @@ -127,7 +128,8 @@ async def read_user(osm_id: int, user_data: AuthUser = Depends(staff_required)): # Update user by osm_id @router.put("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) async def update_user( - osm_id: int, update_data: User, user_data: AuthUser = Depends(admin_required) + update_data: User, user_data: AuthUser = Depends(admin_required), + osm_id: int=Path(description="The OSM ID of the User to Retrieve") ): """ Updates user information based on the given osm_id. @@ -175,7 +177,9 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required) # Get all users @router.get("/users", response_model=list, responses={**common_responses}) async def read_users( - skip: int = 0, limit: int = 10, user_data: AuthUser = Depends(staff_required) + skip: int = Query(0, description="The Number of Users to Skip"), + limit: int = Query(10, description="The Maximum Number of Users to Retrieve"), + user_data: AuthUser = Depends(staff_required) ): """ Retrieves a list of users with optional pagination. From 6516ea72cba3a108ffc67397b323fdfc8112cf39 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:23:13 +0100 Subject: [PATCH 32/63] updated 500 response --- API/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/API/tasks.py b/API/tasks.py index 390b237a..ad206b61 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -16,8 +16,7 @@ router = APIRouter(prefix="/tasks", tags=["Tasks"]) -@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, - responses={'500': {"model": ErrorMessage}}) +@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'500': {}}) @version(1) def get_task_status( task_id, From 16773b1fcbe6fb2de1f972bb607f824fd2872871 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:51:22 +0100 Subject: [PATCH 33/63] updated 500 response --- API/raw_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/raw_data.py b/API/raw_data.py index 4ea6d1d4..98cc4486 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -54,7 +54,7 @@ -@router.get("/status", response_model=StatusResponse, responses={'500': {"model": ErrorMessage}}) +@router.get("/status", response_model=StatusResponse, responses={'500': {}}) @version(1) def check_database_last_updated(): """Gives status about how recent the osm data is , it will give the last time that database was updated completely""" From 61a58947d6ace624b74831397040d985829f5ecb Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:55:16 +0100 Subject: [PATCH 34/63] updated example to match schema fields --- src/validation/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validation/models.py b/src/validation/models.py index bd569dc2..dd7c1366 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -244,8 +244,8 @@ class SnapshotResponse(BaseModel): class Config: json_schema_extra = { "example": { - "task_id": "aa539af6-83d4-4aa3-879e-abf14fffa03f", - "track_link": "/tasks/status/aa539af6-83d4-4aa3-879e-abf14fffa03f/", + "taskId": "aa539af6-83d4-4aa3-879e-abf14fffa03f", + "trackLink": "/tasks/status/aa539af6-83d4-4aa3-879e-abf14fffa03f/", } } From 8ab9fb216d60ebce9b10d7bf9daa36495e626526 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 10:59:38 +0100 Subject: [PATCH 35/63] header description --- API/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 5544dcbc..be45913f 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -58,7 +58,7 @@ def login_required(access_token: str = return get_osm_auth_user(access_token) -def get_optional_user(access_token: str = Header(default=None)) -> AuthUser: +def get_optional_user(access_token: str = Header(default=None, description="Access Token to Authorize User")) -> AuthUser: if access_token: return get_osm_auth_user(access_token) else: From 655437d8f421a5cc404b8f4b381f67a98d59dc2b Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:05:48 +0100 Subject: [PATCH 36/63] updated 500 response --- API/raw_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/raw_data.py b/API/raw_data.py index 98cc4486..853b76af 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -497,14 +497,14 @@ def get_osm_current_snapshot_as_plain_geojson( return result -@router.get("/countries", responses={'500': {"model": ErrorMessage}}) +@router.get("/countries", responses={'500': {}}) @version(1) def get_countries(q: str = ""): result = RawData().get_countries_list(q) return result -@router.get("/osm_id", responses={'500': {"model": ErrorMessage}}) +@router.get("/osm_id", responses={'500': {}}) @version(1) def get_osm_feature(osm_id: int): return RawData().get_osm_feature(osm_id) From 6b62b4db84737708ad4cd81175d51cccaede341d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 11:40:28 +0100 Subject: [PATCH 37/63] added parameter description --- API/raw_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API/raw_data.py b/API/raw_data.py index 853b76af..53eb3f1f 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -23,7 +23,7 @@ import redis from area import area -from fastapi import APIRouter, Body, Depends, HTTPException, Request +from fastapi import APIRouter, Body, Depends, HTTPException, Request, Path from fastapi.responses import JSONResponse from fastapi_versioning import version @@ -506,5 +506,5 @@ def get_countries(q: str = ""): @router.get("/osm_id", responses={'500': {}}) @version(1) -def get_osm_feature(osm_id: int): +def get_osm_feature(osm_id: int=Path(description="The OSM ID of the User")): return RawData().get_osm_feature(osm_id) From 968eb7350b55af0461abd5e0ebfc0ab6d5233de9 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:21:38 +0100 Subject: [PATCH 38/63] remoded parameter example --- API/auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index be45913f..2ab998dd 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -54,7 +54,7 @@ def get_osm_auth_user(access_token): def login_required(access_token: str = - Header(..., description="Access Token to Authorize User", example="UJkq0jfR4JHV9e3Buj0xSVYNio8TWQ")): + Header(..., description="Access Token to Authorize User")): return get_osm_auth_user(access_token) From 7b51f6d6c59dce7d23d8876a296b7e8781bd4c04 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:25:27 +0100 Subject: [PATCH 39/63] added 404 response --- API/raw_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/raw_data.py b/API/raw_data.py index 53eb3f1f..530ac97f 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -504,7 +504,7 @@ def get_countries(q: str = ""): return result -@router.get("/osm_id", responses={'500': {}}) +@router.get("/osm_id", responses={'404':{"model": ErrorMessage}, '500': {}}) @version(1) def get_osm_feature(osm_id: int=Path(description="The OSM ID of the User")): return RawData().get_osm_feature(osm_id) From db04615f324a8854133182c6bd1380bd5ca26c17 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:30:19 +0100 Subject: [PATCH 40/63] added 401 response --- src/validation/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/validation/models.py b/src/validation/models.py index dd7c1366..b2a6e51b 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -295,6 +295,7 @@ class ErrorMessage(BaseModel): common_responses = { + 401: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": [{"msg": "OSM Authentication failed"}] }}}}, 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": [{"msg": "OSM Authentication failed"}] }}}}, 500: {}, } From a69b2e49d8fe573e2db821e29ae34ea4c11a2fc1 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:36:12 +0100 Subject: [PATCH 41/63] added 404 response --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index ad206b61..e53a7894 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -16,7 +16,7 @@ router = APIRouter(prefix="/tasks", tags=["Tasks"]) -@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'500': {}}) +@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'404':{"model": ErrorMessage}, '500': {}}) @version(1) def get_task_status( task_id, From cd801f49d61663761ca8894fbd456dbc71c57bd2 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 12:47:29 +0100 Subject: [PATCH 42/63] added parameter description --- API/tasks.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/API/tasks.py b/API/tasks.py index e53a7894..4886f9d7 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -3,7 +3,7 @@ import redis from celery.result import AsyncResult -from fastapi import APIRouter, Depends, HTTPException, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request, Path from fastapi.responses import JSONResponse from fastapi_versioning import version @@ -19,7 +19,7 @@ @router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'404':{"model": ErrorMessage}, '500': {}}) @version(1) def get_task_status( - task_id, + task_id= Path(description="Unique id provided on response from */snapshot/*"), only_args: bool = Query( default=False, description="Fetches arguments of task", @@ -80,7 +80,8 @@ def get_task_status( @router.get("/revoke/{task_id}", responses={**common_responses}) @version(1) -def revoke_task(task_id, user: AuthUser = Depends(staff_required)): +def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot/*"), + user: AuthUser = Depends(staff_required)): """Revokes task , Terminates if it is executing Args: From 326e1d34b98b7713e49d2fc6f51d60dd23ee61b1 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 13:17:52 +0100 Subject: [PATCH 43/63] added 404 response --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index 4886f9d7..28399fe0 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -78,7 +78,7 @@ def get_task_status( return JSONResponse(result) -@router.get("/revoke/{task_id}", responses={**common_responses}) +@router.get("/revoke/{task_id}", responses={**common_responses, '404':{"model": ErrorMessage}}) @version(1) def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot/*"), user: AuthUser = Depends(staff_required)): From 6ce8e8de39c99c219bc00740377c297be3cf6227 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 13:21:59 +0100 Subject: [PATCH 44/63] updated 500 response --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index 28399fe0..d3d67ba0 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -94,7 +94,7 @@ def revoke_task(task_id= Path(description="Unique id provided on response from * return JSONResponse({"id": task_id}) -@router.get("/inspect", responses={'500': {"model": ErrorMessage}}) +@router.get("/inspect", responses={'500': {}}) @version(1) def inspect_workers( request: Request, From a85e6764c7c10863844bb8a4dc130a7917776ec6 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 13:22:38 +0100 Subject: [PATCH 45/63] updated 500 response --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index d3d67ba0..6662f1da 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -160,7 +160,7 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): queues = [DEFAULT_QUEUE_NAME, DAEMON_QUEUE_NAME] -@router.get("/queue", responses={'500': {"model": ErrorMessage}}) +@router.get("/queue", responses={'500': {}}) @version(1) def get_queue_info(): queue_info = {} From 6d4914788d7725371bde139f923b0280064e8ffd Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sat, 16 Mar 2024 13:23:15 +0100 Subject: [PATCH 46/63] updated 500 response --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index 6662f1da..affe3a2d 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -135,7 +135,7 @@ def inspect_workers( return JSONResponse(content=response_data) -@router.get("/ping", responses={'500': {"model": ErrorMessage}}) +@router.get("/ping", responses={'500': {}}) @version(1) def ping_workers(): """Pings available workers From 210f89d516c6c3a3ee58e5d99fc32466351c2bb9 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:17:58 +0100 Subject: [PATCH 47/63] added response code example --- API/tasks.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/API/tasks.py b/API/tasks.py index affe3a2d..dedb4fc7 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -94,7 +94,8 @@ def revoke_task(task_id= Path(description="Unique id provided on response from * return JSONResponse({"id": task_id}) -@router.get("/inspect", responses={'500': {}}) +@router.get("/inspect", responses={'500': {}, + '200':{"content": {"application/json": {"example": {"active": [{"celery@default_worker": {}}]}}}}}) @version(1) def inspect_workers( request: Request, @@ -135,7 +136,8 @@ def inspect_workers( return JSONResponse(content=response_data) -@router.get("/ping", responses={'500': {}}) +@router.get("/ping", responses={'500': {}, + '200':{"content": {"application/json": {"example": {"celery@default_worker": {"ok": "pong"}}}}}}) @version(1) def ping_workers(): """Pings available workers @@ -146,7 +148,8 @@ def ping_workers(): return JSONResponse(inspected_ping) -@router.get("/purge", responses={**common_responses}) +@router.get("/purge", responses={**common_responses, + '200':{"content": {"application/json": {"example": {"tasks_discarded": 0}}}}}) @version(1) def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): """ @@ -160,7 +163,8 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): queues = [DEFAULT_QUEUE_NAME, DAEMON_QUEUE_NAME] -@router.get("/queue", responses={'500': {}}) +@router.get("/queue", responses={'500': {}, + '200':{"content": {"application/json": {"example": {"raw_daemon": {"length": 0}}}}}}) @version(1) def get_queue_info(): queue_info = {} @@ -188,7 +192,7 @@ def get_list_details( ), ): if queue_name not in queues: - raise HTTPException(status_code=404, detail=f"Queue '{queue_name}' not found") + raise HTTPException(status_code=404, detail=[{"msg": f"Queue '{queue_name}' not found"}]) redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) list_items = redis_client.lrange(queue_name, 0, -1) From d118d0cbbc0db43a88ab1985e651c1db207feedd Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:36:13 +0100 Subject: [PATCH 48/63] updated endpoint description --- API/tasks.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/API/tasks.py b/API/tasks.py index dedb4fc7..8a5d282e 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -154,7 +154,11 @@ def ping_workers(): def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): """ Discards all waiting tasks from the queue + Returns : Number of tasks discarded + + Raises: + - HTTPException 403: If purge fails due to insufficient permission. """ purged = celery.control.purge() return JSONResponse({"tasks_discarded": purged}) @@ -167,6 +171,12 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): '200':{"content": {"application/json": {"example": {"raw_daemon": {"length": 0}}}}}}) @version(1) def get_queue_info(): + """ + Get all the queues + + Returns : The queues names and their lengths + """ + queue_info = {} redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) @@ -191,6 +201,15 @@ def get_list_details( description="Includes arguments of task", ), ): + """ + Retrieves queue information based on the given queue name + + Args: + - queue_name (str): The name of the queue to retrieve. + + Returns : The queue details + """ + if queue_name not in queues: raise HTTPException(status_code=404, detail=[{"msg": f"Queue '{queue_name}' not found"}]) redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) From afe8b58fdef96c4b2109f357f1890e85e4f77460 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:42:02 +0100 Subject: [PATCH 49/63] added path parameter description --- API/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index 8a5d282e..9a8022ab 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -195,7 +195,7 @@ def get_queue_info(): responses={**common_responses, '404': {"model": ErrorMessage}}) @version(1) def get_list_details( - queue_name: str, + queue_name= Path(description="Name of queue to retrieve"), args: bool = Query( default=False, description="Includes arguments of task", From 03bbf0027ebe3c614d62bea01640cd6260992a5c Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:50:14 +0100 Subject: [PATCH 50/63] 200 response example --- API/tasks.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/API/tasks.py b/API/tasks.py index 9a8022ab..3d0ca7d1 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -78,7 +78,9 @@ def get_task_status( return JSONResponse(result) -@router.get("/revoke/{task_id}", responses={**common_responses, '404':{"model": ErrorMessage}}) +@router.get("/revoke/{task_id}", responses={**common_responses, + '404':{"model": ErrorMessage}, + '200':{"content": {"application/json": {"example": {"id": "aa539af6-83d4-4aa3-879e-abf14fffa03f"}}}}}) @version(1) def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot/*"), user: AuthUser = Depends(staff_required)): From c73050b1078c4b16439cde2c6da17c8bf94a5f09 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 10:02:05 +0100 Subject: [PATCH 51/63] 429 error response --- API/raw_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/raw_data.py b/API/raw_data.py index 530ac97f..175101a1 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -62,7 +62,8 @@ def check_database_last_updated(): return {"last_updated": result} -@router.post("/snapshot", response_model=SnapshotResponse, responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.post("/snapshot", response_model=SnapshotResponse, responses={**common_responses, 404:{"model": ErrorMessage}, + 429:{"model": ErrorMessage}}) @limiter.limit(f"{export_rate_limit}/minute") @version(1) def get_osm_current_snapshot_as_file( From c8bfcd155a161d20f25c37e4e15ba4595105bfbb Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:28:54 +0100 Subject: [PATCH 52/63] added general security header --- API/auth/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index 2ab998dd..bc4adb38 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -1,6 +1,7 @@ from enum import Enum from typing import Union +from fastapi.security import APIKeyHeader from fastapi import Depends, Header, HTTPException from osm_login_python.core import Auth from pydantic import BaseModel, Field @@ -9,6 +10,8 @@ from src.config import get_oauth_credentials +Raw_Data_Access_Token = APIKeyHeader(name='Access_Token', description="Access Token to Authorize User") + class UserRole(Enum): ADMIN = 1 STAFF = 2 @@ -53,8 +56,7 @@ def get_osm_auth_user(access_token): return user -def login_required(access_token: str = - Header(..., description="Access Token to Authorize User")): +def login_required(access_token: str = Depends(Raw_Data_Access_Token)): return get_osm_auth_user(access_token) From a0afa5b5a9c0f07946431f3c799b601a6df9b27a Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 17 Mar 2024 17:38:11 +0100 Subject: [PATCH 53/63] updated response example --- API/auth/routers.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index f2ae9e73..d95a5a47 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -99,7 +99,9 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required # Read user by osm_id -@router.get("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.get("/users/{osm_id}", responses={**common_responses, + '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 2}}}}, + '404':{"model": ErrorMessage}},) async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retrieve"), user_data: AuthUser = Depends(staff_required)): """ @@ -126,7 +128,9 @@ async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retr # Update user by osm_id -@router.put("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.put("/users/{osm_id}", responses={**common_responses, + '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, + '404':{"model": ErrorMessage}}) async def update_user( update_data: User, user_data: AuthUser = Depends(admin_required), osm_id: int=Path(description="The OSM ID of the User to Retrieve") @@ -154,7 +158,9 @@ async def update_user( # Delete user by osm_id -@router.delete("/users/{osm_id}", response_model=dict, responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.delete("/users/{osm_id}", responses={**common_responses, + '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, + '404':{"model": ErrorMessage}}) async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)): """ Deletes a user based on the given osm_id. @@ -175,7 +181,8 @@ async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required) # Get all users -@router.get("/users", response_model=list, responses={**common_responses}) +@router.get("/users", response_model=list, responses={**common_responses, + '200':{"content": {"application/json": {"example": [{"osm_id": 1, "role": 2}]}}},}) async def read_users( skip: int = Query(0, description="The Number of Users to Skip"), limit: int = Query(10, description="The Maximum Number of Users to Retrieve"), From 74fa50faf67eeb7aab16189aa960f9edfb060b73 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:27:04 +0100 Subject: [PATCH 54/63] added path parameter description --- API/auth/routers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index d95a5a47..21a2eb17 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -161,7 +161,8 @@ async def update_user( @router.delete("/users/{osm_id}", responses={**common_responses, '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, '404':{"model": ErrorMessage}}) -async def delete_user(osm_id: int, user_data: AuthUser = Depends(admin_required)): +async def delete_user(user_data: AuthUser = Depends(admin_required), + osm_id: int=Path(description="The OSM ID of the User to Retrieve")): """ Deletes a user based on the given osm_id. From b45e30699a491f240862f09f64771dae7809c27f Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:01:26 +0100 Subject: [PATCH 55/63] removed trailing slashes --- API/custom_exports.py | 2 +- API/hdx.py | 6 +++--- API/s3.py | 2 +- API/stats.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/API/custom_exports.py b/API/custom_exports.py index da4bcea1..1a41054d 100644 --- a/API/custom_exports.py +++ b/API/custom_exports.py @@ -13,7 +13,7 @@ router = APIRouter(prefix="/custom", tags=["Custom Exports"]) -@router.post("/snapshot/") +@router.post("/snapshot") @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def process_custom_requests( diff --git a/API/hdx.py b/API/hdx.py index b5d89a20..e031001b 100644 --- a/API/hdx.py +++ b/API/hdx.py @@ -15,7 +15,7 @@ router = APIRouter(prefix="/hdx", tags=["HDX"]) -@router.post("/", response_model=dict) +@router.post("", response_model=dict) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def create_hdx( @@ -36,7 +36,7 @@ async def create_hdx( return hdx_instance.create_hdx(hdx_data) -@router.get("/", response_model=List[dict]) +@router.get("", response_model=List[dict]) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def read_hdx_list( @@ -70,7 +70,7 @@ async def read_hdx_list( return hdx_list -@router.get("/search/", response_model=List[dict]) +@router.get("/search", response_model=List[dict]) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def search_hdx( diff --git a/API/s3.py b/API/s3.py index 767f2952..708cc52d 100644 --- a/API/s3.py +++ b/API/s3.py @@ -32,7 +32,7 @@ paginator = s3.get_paginator("list_objects_v2") -@router.get("/files/") +@router.get("/files") @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def list_s3_files( diff --git a/API/stats.py b/API/stats.py index b2ca5414..c8ebd6ee 100644 --- a/API/stats.py +++ b/API/stats.py @@ -11,7 +11,7 @@ router = APIRouter(prefix="/stats", tags=["Stats"]) -@router.post("/polygon/") +@router.post("/polygon") @limiter.limit(f"{POLYGON_STATISTICS_API_RATE_LIMIT}/minute") @version(1) async def get_polygon_stats( From b96201bf20ca44fe5d9a780b29bbfd56bc94bcb3 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:38:26 +0100 Subject: [PATCH 56/63] added parameter description --- API/hdx.py | 15 ++++++++------- API/s3.py | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/API/hdx.py b/API/hdx.py index e031001b..c9a717f2 100644 --- a/API/hdx.py +++ b/API/hdx.py @@ -1,6 +1,6 @@ from typing import Dict, List -from fastapi import APIRouter, Depends, HTTPException, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request, Path from fastapi_versioning import version from src.app import HDX @@ -41,8 +41,8 @@ async def create_hdx( @version(1) async def read_hdx_list( request: Request, - skip: int = 0, - limit: int = 10, + skip: int = Query(0, description="Number of entries to skip."), + limit: int = Query(10, description="Maximum number of entries to retrieve.") ): """ Retrieve a list of HDX entries based on provided filters. @@ -101,7 +101,7 @@ async def search_hdx( @router.get("/{hdx_id}", response_model=dict) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) -async def read_hdx(request: Request, hdx_id: int): +async def read_hdx(request: Request, hdx_id: int=Path(description="ID of the HDX entry to retrieve")): """ Retrieve a specific HDX entry by its ID. @@ -127,8 +127,8 @@ async def read_hdx(request: Request, hdx_id: int): @version(1) async def update_hdx( request: Request, - hdx_id: int, hdx_data: dict, + hdx_id: int=Path(description="ID of the HDX entry to update"), user_data: AuthUser = Depends(staff_required), ): """ @@ -159,8 +159,8 @@ async def update_hdx( @version(1) async def patch_hdx( request: Request, - hdx_id: int, hdx_data: Dict, + hdx_id: int=Path(description="ID of the HDX entry to update"), user_data: AuthUser = Depends(staff_required), ): """ @@ -190,7 +190,8 @@ async def patch_hdx( @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def delete_hdx( - request: Request, hdx_id: int, user_data: AuthUser = Depends(admin_required) + request: Request, hdx_id: int=Path(description="ID of the HDX entry to delete"), + user_data: AuthUser = Depends(admin_required) ): """ Delete an existing HDX entry. diff --git a/API/s3.py b/API/s3.py index 708cc52d..f40119d8 100644 --- a/API/s3.py +++ b/API/s3.py @@ -37,7 +37,7 @@ @version(1) async def list_s3_files( request: Request, - folder: str = Query(default="/HDX"), + folder: str = Query("/HDX", description="Folder in S3"), prettify: bool = Query( default=False, description="Display size & date in human-readable format" ), From 4065a53711b53a350bc922666624679d6ebc5428 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 22 Mar 2024 14:49:41 +0100 Subject: [PATCH 57/63] added\updated parameter description --- API/auth/routers.py | 4 ++-- API/raw_data.py | 24 +++++++++++++++++++++--- API/tasks.py | 4 ++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 21a2eb17..32ea9973 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -133,7 +133,7 @@ async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retr '404':{"model": ErrorMessage}}) async def update_user( update_data: User, user_data: AuthUser = Depends(admin_required), - osm_id: int=Path(description="The OSM ID of the User to Retrieve") + osm_id: int=Path(description="The OSM ID of the User to Update") ): """ Updates user information based on the given osm_id. @@ -162,7 +162,7 @@ async def update_user( '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, '404':{"model": ErrorMessage}}) async def delete_user(user_data: AuthUser = Depends(admin_required), - osm_id: int=Path(description="The OSM ID of the User to Retrieve")): + osm_id: int=Path(description="The OSM ID of the User to Delete")): """ Deletes a user based on the given osm_id. diff --git a/API/raw_data.py b/API/raw_data.py index 175101a1..3e48ab68 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -23,7 +23,7 @@ import redis from area import area -from fastapi import APIRouter, Body, Depends, HTTPException, Request, Path +from fastapi import APIRouter, Body, Depends, HTTPException, Request, Path, Query from fastapi.responses import JSONResponse from fastapi_versioning import version @@ -500,12 +500,30 @@ def get_osm_current_snapshot_as_plain_geojson( @router.get("/countries", responses={'500': {}}) @version(1) -def get_countries(q: str = ""): +def get_countries(q: str = Query("", description="Query parameter for filtering countries")): + """ + Gets Countries list from the database + + Args: + q (str): query parameter for filtering countries + + Returns: + featurecollection: geojson of country + """ result = RawData().get_countries_list(q) return result @router.get("/osm_id", responses={'404':{"model": ErrorMessage}, '500': {}}) @version(1) -def get_osm_feature(osm_id: int=Path(description="The OSM ID of the User")): +def get_osm_feature(osm_id: int=Path(description="The OSM ID of feature")): + """ + Gets geometry of osm_id in geojson + + Args: + osm_id (int): osm_id of feature + + Returns: + featurecollection: Geojson + """ return RawData().get_osm_feature(osm_id) diff --git a/API/tasks.py b/API/tasks.py index 3d0ca7d1..894f0230 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -82,9 +82,9 @@ def get_task_status( '404':{"model": ErrorMessage}, '200':{"content": {"application/json": {"example": {"id": "aa539af6-83d4-4aa3-879e-abf14fffa03f"}}}}}) @version(1) -def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot/*"), +def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot*"), user: AuthUser = Depends(staff_required)): - """Revokes task , Terminates if it is executing + """Revokes task, Terminates if it is executing Args: task_id (_type_): task id of raw data task From b667f260ca6a5eb2c0be1d96220ec334fe75236d Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:22:50 +0100 Subject: [PATCH 58/63] ran black formatter --- API/auth/routers.py | 91 ++++++++++++++++++++++++++++++------------- API/hdx.py | 19 +++++---- API/raw_data.py | 30 +++++++++----- API/tasks.py | 95 +++++++++++++++++++++++++++++++++++---------- 4 files changed, 171 insertions(+), 64 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 32ea9973..62c948c6 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -42,10 +42,15 @@ def callback(request: Request): """ access_token = osm_auth.callback(str(request.url)) - return access_token + return access_token -@router.get("/me", response_model=AuthUser, responses={**common_responses}, response_description="User Information") +@router.get( + "/me", + response_model=AuthUser, + responses={**common_responses}, + response_description="User Information", +) def my_data(user_data: AuthUser = Depends(login_required)): """Read the access token and provide user details from OSM user's API endpoint, also integrated with underpass . @@ -57,7 +62,7 @@ def my_data(user_data: AuthUser = Depends(login_required)): ADMIN = 1 STAFF = 2 GUEST = 3 - + Raises: - HTTPException 403: Due to authentication error(Wrong access token). - HTTPException 500: Internal server error. @@ -71,11 +76,17 @@ class User(BaseModel): class Config: json_schema_extra = {"example": {"osm_id": 123, "role": 3}} - + # Create user -@router.post("/users", response_model=dict, - responses={**common_responses, '200':{"content": {"application/json": {"example": {"osm_id": 123}}}}}) +@router.post( + "/users", + response_model=dict, + responses={ + **common_responses, + "200": {"content": {"application/json": {"example": {"osm_id": 123}}}}, + }, +) async def create_user(params: User, user_data: AuthUser = Depends(admin_required)): """ Creates a new user and returns the user's information. @@ -99,11 +110,18 @@ async def create_user(params: User, user_data: AuthUser = Depends(admin_required # Read user by osm_id -@router.get("/users/{osm_id}", responses={**common_responses, - '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 2}}}}, - '404':{"model": ErrorMessage}},) -async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retrieve"), - user_data: AuthUser = Depends(staff_required)): +@router.get( + "/users/{osm_id}", + responses={ + **common_responses, + "200": {"content": {"application/json": {"example": {"osm_id": 1, "role": 2}}}}, + "404": {"model": ErrorMessage}, + }, +) +async def read_user( + osm_id: int = Path(description="The OSM ID of the User to Retrieve"), + user_data: AuthUser = Depends(staff_required), +): """ Retrieves user information based on the given osm_id. User Role : @@ -128,12 +146,18 @@ async def read_user(osm_id: int=Path(description="The OSM ID of the User to Retr # Update user by osm_id -@router.put("/users/{osm_id}", responses={**common_responses, - '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, - '404':{"model": ErrorMessage}}) +@router.put( + "/users/{osm_id}", + responses={ + **common_responses, + "200": {"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, + "404": {"model": ErrorMessage}, + }, +) async def update_user( - update_data: User, user_data: AuthUser = Depends(admin_required), - osm_id: int=Path(description="The OSM ID of the User to Update") + update_data: User, + user_data: AuthUser = Depends(admin_required), + osm_id: int = Path(description="The OSM ID of the User to Update"), ): """ Updates user information based on the given osm_id. @@ -158,11 +182,18 @@ async def update_user( # Delete user by osm_id -@router.delete("/users/{osm_id}", responses={**common_responses, - '200':{"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, - '404':{"model": ErrorMessage}}) -async def delete_user(user_data: AuthUser = Depends(admin_required), - osm_id: int=Path(description="The OSM ID of the User to Delete")): +@router.delete( + "/users/{osm_id}", + responses={ + **common_responses, + "200": {"content": {"application/json": {"example": {"osm_id": 1, "role": 1}}}}, + "404": {"model": ErrorMessage}, + }, +) +async def delete_user( + user_data: AuthUser = Depends(admin_required), + osm_id: int = Path(description="The OSM ID of the User to Delete"), +): """ Deletes a user based on the given osm_id. @@ -182,12 +213,20 @@ async def delete_user(user_data: AuthUser = Depends(admin_required), # Get all users -@router.get("/users", response_model=list, responses={**common_responses, - '200':{"content": {"application/json": {"example": [{"osm_id": 1, "role": 2}]}}},}) +@router.get( + "/users", + response_model=list, + responses={ + **common_responses, + "200": { + "content": {"application/json": {"example": [{"osm_id": 1, "role": 2}]}} + }, + }, +) async def read_users( - skip: int = Query(0, description="The Number of Users to Skip"), - limit: int = Query(10, description="The Maximum Number of Users to Retrieve"), - user_data: AuthUser = Depends(staff_required) + skip: int = Query(0, description="The Number of Users to Skip"), + limit: int = Query(10, description="The Maximum Number of Users to Retrieve"), + user_data: AuthUser = Depends(staff_required), ): """ Retrieves a list of users with optional pagination. diff --git a/API/hdx.py b/API/hdx.py index c9a717f2..302ef52f 100644 --- a/API/hdx.py +++ b/API/hdx.py @@ -25,8 +25,8 @@ async def create_hdx( Create a new HDX entry. Args: - request (Request): The request object. - hdx_data (dict): Data for creating the HDX entry. + request (Request): The request object.\n + hdx_data (dict): Data for creating the HDX entry.\n user_data (AuthUser): User authentication data. Returns: @@ -42,7 +42,7 @@ async def create_hdx( async def read_hdx_list( request: Request, skip: int = Query(0, description="Number of entries to skip."), - limit: int = Query(10, description="Maximum number of entries to retrieve.") + limit: int = Query(10, description="Maximum number of entries to retrieve."), ): """ Retrieve a list of HDX entries based on provided filters. @@ -101,7 +101,9 @@ async def search_hdx( @router.get("/{hdx_id}", response_model=dict) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) -async def read_hdx(request: Request, hdx_id: int=Path(description="ID of the HDX entry to retrieve")): +async def read_hdx( + request: Request, hdx_id: int = Path(description="ID of the HDX entry to retrieve") +): """ Retrieve a specific HDX entry by its ID. @@ -128,7 +130,7 @@ async def read_hdx(request: Request, hdx_id: int=Path(description="ID of the HDX async def update_hdx( request: Request, hdx_data: dict, - hdx_id: int=Path(description="ID of the HDX entry to update"), + hdx_id: int = Path(description="ID of the HDX entry to update"), user_data: AuthUser = Depends(staff_required), ): """ @@ -160,7 +162,7 @@ async def update_hdx( async def patch_hdx( request: Request, hdx_data: Dict, - hdx_id: int=Path(description="ID of the HDX entry to update"), + hdx_id: int = Path(description="ID of the HDX entry to update"), user_data: AuthUser = Depends(staff_required), ): """ @@ -190,8 +192,9 @@ async def patch_hdx( @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def delete_hdx( - request: Request, hdx_id: int=Path(description="ID of the HDX entry to delete"), - user_data: AuthUser = Depends(admin_required) + request: Request, + hdx_id: int = Path(description="ID of the HDX entry to delete"), + user_data: AuthUser = Depends(admin_required), ): """ Delete an existing HDX entry. diff --git a/API/raw_data.py b/API/raw_data.py index 3e48ab68..a741407c 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -42,7 +42,7 @@ SnapshotResponse, StatusResponse, ErrorMessage, - common_responses + common_responses, ) from .api_worker import process_raw_data @@ -53,8 +53,7 @@ redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) - -@router.get("/status", response_model=StatusResponse, responses={'500': {}}) +@router.get("/status", response_model=StatusResponse, responses={"500": {}}) @version(1) def check_database_last_updated(): """Gives status about how recent the osm data is , it will give the last time that database was updated completely""" @@ -62,8 +61,15 @@ def check_database_last_updated(): return {"last_updated": result} -@router.post("/snapshot", response_model=SnapshotResponse, responses={**common_responses, 404:{"model": ErrorMessage}, - 429:{"model": ErrorMessage}}) +@router.post( + "/snapshot", + response_model=SnapshotResponse, + responses={ + **common_responses, + 404: {"model": ErrorMessage}, + 429: {"model": ErrorMessage}, + }, +) @limiter.limit(f"{export_rate_limit}/minute") @version(1) def get_osm_current_snapshot_as_file( @@ -466,7 +472,9 @@ def get_osm_current_snapshot_as_file( ) -@router.post("/snapshot/plain", responses={**common_responses, 404:{"model": ErrorMessage}}) +@router.post( + "/snapshot/plain", responses={**common_responses, 404: {"model": ErrorMessage}} +) @version(1) def get_osm_current_snapshot_as_plain_geojson( request: Request, @@ -498,9 +506,11 @@ def get_osm_current_snapshot_as_plain_geojson( return result -@router.get("/countries", responses={'500': {}}) +@router.get("/countries", responses={"500": {}}) @version(1) -def get_countries(q: str = Query("", description="Query parameter for filtering countries")): +def get_countries( + q: str = Query("", description="Query parameter for filtering countries") +): """ Gets Countries list from the database @@ -514,9 +524,9 @@ def get_countries(q: str = Query("", description="Query parameter for filtering return result -@router.get("/osm_id", responses={'404':{"model": ErrorMessage}, '500': {}}) +@router.get("/osm_id", responses={"404": {"model": ErrorMessage}, "500": {}}) @version(1) -def get_osm_feature(osm_id: int=Path(description="The OSM ID of feature")): +def get_osm_feature(osm_id: int = Path(description="The OSM ID of feature")): """ Gets geometry of osm_id in geojson diff --git a/API/tasks.py b/API/tasks.py index 894f0230..04fc78f9 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -16,10 +16,14 @@ router = APIRouter(prefix="/tasks", tags=["Tasks"]) -@router.get("/status/{task_id}", response_model=SnapshotTaskResponse, responses={'404':{"model": ErrorMessage}, '500': {}}) +@router.get( + "/status/{task_id}", + response_model=SnapshotTaskResponse, + responses={"404": {"model": ErrorMessage}, "500": {}}, +) @version(1) def get_task_status( - task_id= Path(description="Unique id provided on response from */snapshot/*"), + task_id=Path(description="Unique id provided on response from */snapshot/*"), only_args: bool = Query( default=False, description="Fetches arguments of task", @@ -78,12 +82,25 @@ def get_task_status( return JSONResponse(result) -@router.get("/revoke/{task_id}", responses={**common_responses, - '404':{"model": ErrorMessage}, - '200':{"content": {"application/json": {"example": {"id": "aa539af6-83d4-4aa3-879e-abf14fffa03f"}}}}}) +@router.get( + "/revoke/{task_id}", + responses={ + **common_responses, + "404": {"model": ErrorMessage}, + "200": { + "content": { + "application/json": { + "example": {"id": "aa539af6-83d4-4aa3-879e-abf14fffa03f"} + } + } + }, + }, +) @version(1) -def revoke_task(task_id= Path(description="Unique id provided on response from */snapshot*"), - user: AuthUser = Depends(staff_required)): +def revoke_task( + task_id=Path(description="Unique id provided on response from */snapshot*"), + user: AuthUser = Depends(staff_required), +): """Revokes task, Terminates if it is executing Args: @@ -96,8 +113,19 @@ def revoke_task(task_id= Path(description="Unique id provided on response from * return JSONResponse({"id": task_id}) -@router.get("/inspect", responses={'500': {}, - '200':{"content": {"application/json": {"example": {"active": [{"celery@default_worker": {}}]}}}}}) +@router.get( + "/inspect", + responses={ + "500": {}, + "200": { + "content": { + "application/json": { + "example": {"active": [{"celery@default_worker": {}}]} + } + } + }, + }, +) @version(1) def inspect_workers( request: Request, @@ -138,8 +166,19 @@ def inspect_workers( return JSONResponse(content=response_data) -@router.get("/ping", responses={'500': {}, - '200':{"content": {"application/json": {"example": {"celery@default_worker": {"ok": "pong"}}}}}}) +@router.get( + "/ping", + responses={ + "500": {}, + "200": { + "content": { + "application/json": { + "example": {"celery@default_worker": {"ok": "pong"}} + } + } + }, + }, +) @version(1) def ping_workers(): """Pings available workers @@ -150,8 +189,13 @@ def ping_workers(): return JSONResponse(inspected_ping) -@router.get("/purge", responses={**common_responses, - '200':{"content": {"application/json": {"example": {"tasks_discarded": 0}}}}}) +@router.get( + "/purge", + responses={ + **common_responses, + "200": {"content": {"application/json": {"example": {"tasks_discarded": 0}}}}, + }, +) @version(1) def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): """ @@ -169,8 +213,15 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): queues = [DEFAULT_QUEUE_NAME, DAEMON_QUEUE_NAME] -@router.get("/queue", responses={'500': {}, - '200':{"content": {"application/json": {"example": {"raw_daemon": {"length": 0}}}}}}) +@router.get( + "/queue", + responses={ + "500": {}, + "200": { + "content": {"application/json": {"example": {"raw_daemon": {"length": 0}}}} + }, + }, +) @version(1) def get_queue_info(): """ @@ -193,11 +244,13 @@ def get_queue_info(): return JSONResponse(content=queue_info) -@router.get("/queue/details/{queue_name}", - responses={**common_responses, '404': {"model": ErrorMessage}}) +@router.get( + "/queue/details/{queue_name}", + responses={**common_responses, "404": {"model": ErrorMessage}}, +) @version(1) def get_list_details( - queue_name= Path(description="Name of queue to retrieve"), + queue_name=Path(description="Name of queue to retrieve"), args: bool = Query( default=False, description="Includes arguments of task", @@ -208,12 +261,14 @@ def get_list_details( Args: - queue_name (str): The name of the queue to retrieve. - + Returns : The queue details """ if queue_name not in queues: - raise HTTPException(status_code=404, detail=[{"msg": f"Queue '{queue_name}' not found"}]) + raise HTTPException( + status_code=404, detail=[{"msg": f"Queue '{queue_name}' not found"}] + ) redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) list_items = redis_client.lrange(queue_name, 0, -1) From cdffd72b35299fdf4bf883f8cad4f3c831a7a5e9 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:27:53 +0100 Subject: [PATCH 59/63] formatted with black formatter --- API/main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/API/main.py b/API/main.py index a4240d96..c781f96e 100644 --- a/API/main.py +++ b/API/main.py @@ -75,11 +75,13 @@ os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" -app = FastAPI(title="Raw Data API ", - description="""The Raw Data API allows you to transform +app = FastAPI( + title="Raw Data API ", + description="""The Raw Data API allows you to transform and export OpenStreetMap (OSM) data in different GIS file formats""", - swagger_ui_parameters={"syntaxHighlight": False}) - + swagger_ui_parameters={"syntaxHighlight": False}, +) + app.include_router(auth_router) app.include_router(raw_data_router) app.include_router(tasks_router) From 3a7542c535636d51548e2d55e96b1ead91ca8afc Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:32:32 +0100 Subject: [PATCH 60/63] updated responses --- src/validation/models.py | 100 ++++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/src/validation/models.py b/src/validation/models.py index b2a6e51b..eda3fa82 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -290,21 +290,47 @@ class Config: class ErrorDetail(BaseModel): msg: str + + class ErrorMessage(BaseModel): detail: List[ErrorDetail] common_responses = { - 401: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": [{"msg": "OSM Authentication failed"}] }}}}, - 403: {"model": ErrorMessage, "content": {"application/json": {"example": {"detail": [{"msg": "OSM Authentication failed"}] }}}}, - 500: {}, + 401: { + "model": ErrorMessage, + "content": { + "application/json": { + "example": {"detail": [{"msg": "OSM Authentication failed"}]} + } + }, + }, + 403: { + "model": ErrorMessage, + "content": { + "application/json": { + "example": {"detail": [{"msg": "OSM Authentication failed"}]} + } + }, + }, + 500: {"model": ErrorMessage}, } login_responses = { - 200: {"description": "A Login URL", "content": {"application/json": {"example": {"login_url": "https://www.openstreetmap.org/oauth2/authorize/"}}}}, - 500: {}, + 200: { + "description": "A Login URL", + "content": { + "application/json": { + "example": { + "login_url": "https://www.openstreetmap.org/oauth2/authorize/" + } + } + }, + }, + 500: {"model": ErrorMessage}, } + class StatsRequestParams(BaseModel, GeometryValidatorMixin): iso3: Optional[str] = Field( default=None, @@ -313,22 +339,22 @@ class StatsRequestParams(BaseModel, GeometryValidatorMixin): max_length=3, example="NPL", ) - geometry: Optional[ - Union[Polygon, MultiPolygon, Feature, FeatureCollection] - ] = Field( - default=None, - example={ - "type": "Polygon", - "coordinates": [ - [ - [83.96919250488281, 28.194446860487773], - [83.99751663208006, 28.194446860487773], - [83.99751663208006, 28.214869548073377], - [83.96919250488281, 28.214869548073377], - [83.96919250488281, 28.194446860487773], - ] - ], - }, + geometry: Optional[Union[Polygon, MultiPolygon, Feature, FeatureCollection]] = ( + Field( + default=None, + example={ + "type": "Polygon", + "coordinates": [ + [ + [83.96919250488281, 28.194446860487773], + [83.99751663208006, 28.194446860487773], + [83.99751663208006, 28.214869548073377], + [83.96919250488281, 28.214869548073377], + [83.96919250488281, 28.194446860487773], + ] + ], + }, + ) ) @validator("geometry", pre=True, always=True) @@ -625,22 +651,22 @@ class DynamicCategoriesModel(BaseModel, GeometryValidatorMixin): } ], ) - geometry: Optional[ - Union[Polygon, MultiPolygon, Feature, FeatureCollection] - ] = Field( - default=None, - example={ - "type": "Polygon", - "coordinates": [ - [ - [83.96919250488281, 28.194446860487773], - [83.99751663208006, 28.194446860487773], - [83.99751663208006, 28.214869548073377], - [83.96919250488281, 28.214869548073377], - [83.96919250488281, 28.194446860487773], - ] - ], - }, + geometry: Optional[Union[Polygon, MultiPolygon, Feature, FeatureCollection]] = ( + Field( + default=None, + example={ + "type": "Polygon", + "coordinates": [ + [ + [83.96919250488281, 28.194446860487773], + [83.99751663208006, 28.194446860487773], + [83.99751663208006, 28.214869548073377], + [83.96919250488281, 28.214869548073377], + [83.96919250488281, 28.194446860487773], + ] + ], + }, + ) ) @validator("geometry", pre=True, always=True) From c507bdc1ae2429c2cbe0eaf921be8e63a5d76d17 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:32:51 +0100 Subject: [PATCH 61/63] updated format --- API/auth/__init__.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/API/auth/__init__.py b/API/auth/__init__.py index bc4adb38..6428d559 100644 --- a/API/auth/__init__.py +++ b/API/auth/__init__.py @@ -10,7 +10,10 @@ from src.config import get_oauth_credentials -Raw_Data_Access_Token = APIKeyHeader(name='Access_Token', description="Access Token to Authorize User") +Raw_Data_Access_Token = APIKeyHeader( + name="Access_Token", description="Access Token to Authorize User" +) + class UserRole(Enum): ADMIN = 1 @@ -27,11 +30,11 @@ class AuthUser(BaseModel): class Config: json_schema_extra = { "example": { - "id": "123", - "username": "HOT Team", - "img_url": "https://hotteamimage.com", - "role": UserRole.GUEST.value, - } + "id": "123", + "username": "HOT Team", + "img_url": "https://hotteamimage.com", + "role": UserRole.GUEST.value, + } } @@ -60,7 +63,11 @@ def login_required(access_token: str = Depends(Raw_Data_Access_Token)): return get_osm_auth_user(access_token) -def get_optional_user(access_token: str = Header(default=None, description="Access Token to Authorize User")) -> AuthUser: +def get_optional_user( + access_token: str = Header( + default=None, description="Access Token to Authorize User" + ) +) -> AuthUser: if access_token: return get_osm_auth_user(access_token) else: From 35681103825714eca603953dc60fa6d8bcb313c8 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 24 Mar 2024 19:34:04 +0100 Subject: [PATCH 62/63] added stats response --- src/validation/models.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/validation/models.py b/src/validation/models.py index eda3fa82..eb477cc8 100644 --- a/src/validation/models.py +++ b/src/validation/models.py @@ -316,18 +316,36 @@ class ErrorMessage(BaseModel): 500: {"model": ErrorMessage}, } -login_responses = { - 200: { - "description": "A Login URL", +stats_response = { + "200": { "content": { "application/json": { "example": { - "login_url": "https://www.openstreetmap.org/oauth2/authorize/" + "summary": {"buildings": "", "roads": ""}, + "raw": { + "population": 0, + "populatedAreaKm2": 0, + "averageEditTime": 0, + "lastEditTime": 0, + "osmUsersCount": 0, + "osmBuildingCompletenessPercentage": 0, + "osmRoadsCompletenessPercentage": 0, + "osmBuildingsCount": 0, + "osmHighwayLengthKm": 0, + "aiBuildingsCountEstimation": 0, + "aiRoadCountEstimationKm": 0, + "buildingCount6Months": 0, + "highwayLength6MonthsKm": 0, + }, + "meta": { + "indicators": "https://github.com/hotosm/raw-data-api/tree/develop/docs/src/stats/indicators.md", + "metrics": "https://github.com/hotosm/raw-data-api/tree/develop/docs/src/stats/metrics.md", + }, } } - }, + } }, - 500: {"model": ErrorMessage}, + "500": {"model": ErrorMessage}, } From c1ce44c07ab4d1b6ce7f06e943c6970dc2ca2a36 Mon Sep 17 00:00:00 2001 From: Oluwanifemi Daramola <76186151+nifedara@users.noreply.github.com> Date: Sun, 24 Mar 2024 20:33:41 +0100 Subject: [PATCH 63/63] updated response model --- API/auth/routers.py | 23 ++++++++++--- API/custom_exports.py | 19 +++++++++-- API/hdx.py | 77 +++++++++++++++++++++++++++++-------------- API/raw_data.py | 11 +++++-- API/s3.py | 31 ++++++++++++----- API/stats.py | 7 ++-- API/tasks.py | 8 ++--- src/app.py | 17 ++++++---- 8 files changed, 138 insertions(+), 55 deletions(-) diff --git a/API/auth/routers.py b/API/auth/routers.py index 62c948c6..ab0100ae 100644 --- a/API/auth/routers.py +++ b/API/auth/routers.py @@ -4,14 +4,29 @@ from pydantic import BaseModel from src.app import Users -from src.validation.models import ErrorMessage, common_responses, login_responses +from src.validation.models import ErrorMessage, common_responses from . import AuthUser, admin_required, login_required, osm_auth, staff_required router = APIRouter(prefix="/auth", tags=["Auth"]) -@router.get("/login", responses={**login_responses}) +@router.get( + "/login", + responses={ + 200: { + "description": "A Login URL", + "content": { + "application/json": { + "example": { + "login_url": "https://www.openstreetmap.org/oauth2/authorize/" + } + } + }, + }, + 500: {"model": ErrorMessage}, + }, +) def login_url(request: Request): """ Generate Login URL for authentication using OAuth2 Application registered with OpenStreetMap. @@ -28,7 +43,7 @@ def login_url(request: Request): return login_url -@router.get("/callback", responses={500: {}}) +@router.get("/callback", responses={500: {"model": ErrorMessage}}) def callback(request: Request): """Performs token exchange between OpenStreetMap and Raw Data API @@ -75,7 +90,7 @@ class User(BaseModel): role: int class Config: - json_schema_extra = {"example": {"osm_id": 123, "role": 3}} + json_schema_extra = {"example": {"osm_id": 123, "role": 1}} # Create user diff --git a/API/custom_exports.py b/API/custom_exports.py index 1a41054d..f0a0706c 100644 --- a/API/custom_exports.py +++ b/API/custom_exports.py @@ -5,7 +5,7 @@ from src.config import DEFAULT_QUEUE_NAME from src.config import LIMITER as limiter from src.config import RATE_LIMIT_PER_MIN -from src.validation.models import DynamicCategoriesModel +from src.validation.models import DynamicCategoriesModel, common_responses from .api_worker import process_custom_request from .auth import AuthUser, UserRole, staff_required @@ -13,7 +13,22 @@ router = APIRouter(prefix="/custom", tags=["Custom Exports"]) -@router.post("/snapshot") +@router.post( + "/snapshot", + responses={ + **common_responses, + "200": { + "content": { + "application/json": { + "example": { + "task_id": "3fded368-456f-4ef4-a1b8-c099a7f77ca4", + "track_link": "/tasks/status/3fded368-456f-4ef4-a1b8-c099a7f77ca4/", + } + } + } + }, + }, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def process_custom_requests( diff --git a/API/hdx.py b/API/hdx.py index 302ef52f..cdac5afc 100644 --- a/API/hdx.py +++ b/API/hdx.py @@ -9,13 +9,20 @@ from .auth import AuthUser, admin_required, staff_required -# from src.validation.models import DynamicCategoriesModel +from src.validation.models import ErrorMessage, common_responses router = APIRouter(prefix="/hdx", tags=["HDX"]) -@router.post("", response_model=dict) +@router.post( + "", + response_model=dict, + responses={ + "200": {"content": {"application/json": {"example": {"create": True}}}}, + **common_responses, + }, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def create_hdx( @@ -36,7 +43,7 @@ async def create_hdx( return hdx_instance.create_hdx(hdx_data) -@router.get("", response_model=List[dict]) +@router.get("", response_model=List[dict], responses={"500": {"model": ErrorMessage}}) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def read_hdx_list( @@ -66,11 +73,15 @@ async def read_hdx_list( try: hdx_list = hdx_instance.get_hdx_list_with_filters(skip, limit, filters) except Exception as ex: - raise HTTPException(status_code=422, detail="Couldn't process query") + raise HTTPException(status_code=422, detail=[{"msg": "Couldn't process query"}]) return hdx_list -@router.get("/search", response_model=List[dict]) +@router.get( + "/search", + response_model=List[dict], + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def search_hdx( @@ -98,7 +109,11 @@ async def search_hdx( return hdx_list -@router.get("/{hdx_id}", response_model=dict) +@router.get( + "/{hdx_id}", + response_model=dict, + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def read_hdx( @@ -115,16 +130,20 @@ async def read_hdx( dict: Details of the requested HDX entry. Raises: - HTTPException: If the HDX entry is not found. + HTTPException 404: If the HDX entry is not found. """ hdx_instance = HDX() hdx = hdx_instance.get_hdx_by_id(hdx_id) if hdx: return hdx - raise HTTPException(status_code=404, detail="HDX not found") + raise HTTPException(status_code=404, detail=[{"msg": "HDX not found"}]) -@router.put("/{hdx_id}", response_model=dict) +@router.put( + "/{hdx_id}", + response_model=dict, + responses={**common_responses, "404": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def update_hdx( @@ -137,26 +156,30 @@ async def update_hdx( Update an existing HDX entry. Args: - request (Request): The request object. - hdx_id (int): ID of the HDX entry to update. - hdx_data (dict): Data for updating the HDX entry. + request (Request): The request object.\n + hdx_id (int): ID of the HDX entry to update.\n + hdx_data (dict): Data for updating the HDX entry.\n user_data (AuthUser): User authentication data. Returns: dict: Result of the HDX update process. Raises: - HTTPException: If the HDX entry is not found. + HTTPException 404: If the HDX entry is not found. """ hdx_instance = HDX() existing_hdx = hdx_instance.get_hdx_by_id(hdx_id) if not existing_hdx: - raise HTTPException(status_code=404, detail="HDX not found") + raise HTTPException(status_code=404, detail=[{"msg": "HDX not found"}]) hdx_instance_update = HDX() return hdx_instance_update.update_hdx(hdx_id, hdx_data) -@router.patch("/{hdx_id}", response_model=Dict) +@router.patch( + "/{hdx_id}", + response_model=Dict, + responses={**common_responses, "404": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def patch_hdx( @@ -169,26 +192,30 @@ async def patch_hdx( Partially update an existing HDX entry. Args: - request (Request): The request object. - hdx_id (int): ID of the HDX entry to update. - hdx_data (Dict): Data for partially updating the HDX entry. + request (Request): The request object.\n + hdx_id (int): ID of the HDX entry to update.\n + hdx_data (Dict): Data for partially updating the HDX entry.\n user_data (AuthUser): User authentication data. Returns: Dict: Result of the HDX update process. Raises: - HTTPException: If the HDX entry is not found. + HTTPException 404: If the HDX entry is not found. """ hdx_instance = HDX() existing_hdx = hdx_instance.get_hdx_by_id(hdx_id) if not existing_hdx: - raise HTTPException(status_code=404, detail="HDX not found") + raise HTTPException(status_code=404, detail=[{"msg": "HDX not found"}]) patch_instance = HDX() return patch_instance.patch_hdx(hdx_id, hdx_data) -@router.delete("/{hdx_id}", response_model=dict) +@router.delete( + "/{hdx_id}", + response_model=dict, + responses={**common_responses, "404": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def delete_hdx( @@ -200,19 +227,19 @@ async def delete_hdx( Delete an existing HDX entry. Args: - request (Request): The request object. - hdx_id (int): ID of the HDX entry to delete. + request (Request): The request object.\n + hdx_id (int): ID of the HDX entry to delete.\n user_data (AuthUser): User authentication data. Returns: dict: Result of the HDX deletion process. Raises: - HTTPException: If the HDX entry is not found. + HTTPException 404: If the HDX entry is not found. """ hdx_instance = HDX() existing_hdx = hdx_instance.get_hdx_by_id(hdx_id) if not existing_hdx: - raise HTTPException(status_code=404, detail="HDX not found") + raise HTTPException(status_code=404, detail=[{"msg": "HDX not found"}]) return hdx_instance.delete_hdx(hdx_id) diff --git a/API/raw_data.py b/API/raw_data.py index a741407c..24654661 100644 --- a/API/raw_data.py +++ b/API/raw_data.py @@ -53,7 +53,9 @@ redis_client = redis.StrictRedis.from_url(CELERY_BROKER_URL) -@router.get("/status", response_model=StatusResponse, responses={"500": {}}) +@router.get( + "/status", response_model=StatusResponse, responses={"500": {"model": ErrorMessage}} +) @version(1) def check_database_last_updated(): """Gives status about how recent the osm data is , it will give the last time that database was updated completely""" @@ -506,7 +508,7 @@ def get_osm_current_snapshot_as_plain_geojson( return result -@router.get("/countries", responses={"500": {}}) +@router.get("/countries", responses={"500": {"model": ErrorMessage}}) @version(1) def get_countries( q: str = Query("", description="Query parameter for filtering countries") @@ -524,7 +526,10 @@ def get_countries( return result -@router.get("/osm_id", responses={"404": {"model": ErrorMessage}, "500": {}}) +@router.get( + "/osm_id", + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, +) @version(1) def get_osm_feature(osm_id: int = Path(description="The OSM ID of feature")): """ diff --git a/API/s3.py b/API/s3.py index f40119d8..ea448c9b 100644 --- a/API/s3.py +++ b/API/s3.py @@ -18,6 +18,7 @@ from src.config import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, BUCKET_NAME from src.config import LIMITER as limiter from src.config import RATE_LIMIT_PER_MIN +from src.validation.models import ErrorMessage router = APIRouter(prefix="/s3", tags=["S3"]) @@ -32,7 +33,9 @@ paginator = s3.get_paginator("list_objects_v2") -@router.get("/files") +@router.get( + "/files", responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}} +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def list_s3_files( @@ -80,7 +83,9 @@ async def generate(): return StreamingResponse(content=generate(), media_type="application/json") except NoCredentialsError: - raise HTTPException(status_code=500, detail="AWS credentials not available") + raise HTTPException( + status_code=500, detail=[{"msg": "AWS credentials not available"}] + ) async def check_object_existence(bucket_name, file_path): @@ -88,10 +93,12 @@ async def check_object_existence(bucket_name, file_path): try: s3.head_object(Bucket=bucket_name, Key=file_path) except NoCredentialsError: - raise HTTPException(status_code=500, detail="AWS credentials not available") + raise HTTPException( + status_code=500, detail=[{"msg": "AWS credentials not available"}] + ) except Exception as e: raise HTTPException( - status_code=404, detail=f"File or folder not found: {file_path}" + status_code=404, detail=[{"msg": f"File or folder not found: {file_path}"}] ) @@ -103,11 +110,14 @@ async def read_meta_json(bucket_name, file_path): return content except Exception as e: raise HTTPException( - status_code=500, detail=f"Error reading meta.json: {str(e)}" + status_code=500, detail=[{"msg": f"Error reading meta.json: {str(e)}"}] ) -@router.head("/get/{file_path:path}") +@router.head( + "/get/{file_path:path}", + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def head_s3_file( @@ -131,10 +141,15 @@ async def head_s3_file( if e.response["Error"]["Code"] == "404": return Response(status_code=404) else: - raise HTTPException(status_code=500, detail=f"AWS Error: {str(e)}") + raise HTTPException( + status_code=500, detail=[{"msg": f"AWS Error: {str(e)}"}] + ) -@router.get("/get/{file_path:path}") +@router.get( + "/get/{file_path:path}", + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, +) @limiter.limit(f"{RATE_LIMIT_PER_MIN}/minute") @version(1) async def get_s3_file( diff --git a/API/stats.py b/API/stats.py index c8ebd6ee..49a12bb2 100644 --- a/API/stats.py +++ b/API/stats.py @@ -6,12 +6,15 @@ from src.app import PolygonStats from src.config import LIMITER as limiter from src.config import POLYGON_STATISTICS_API_RATE_LIMIT -from src.validation.models import StatsRequestParams +from src.validation.models import StatsRequestParams, stats_response router = APIRouter(prefix="/stats", tags=["Stats"]) -@router.post("/polygon") +@router.post( + "/polygon", + responses={**stats_response}, +) @limiter.limit(f"{POLYGON_STATISTICS_API_RATE_LIMIT}/minute") @version(1) async def get_polygon_stats( diff --git a/API/tasks.py b/API/tasks.py index 04fc78f9..914a1c4b 100644 --- a/API/tasks.py +++ b/API/tasks.py @@ -19,7 +19,7 @@ @router.get( "/status/{task_id}", response_model=SnapshotTaskResponse, - responses={"404": {"model": ErrorMessage}, "500": {}}, + responses={"404": {"model": ErrorMessage}, "500": {"model": ErrorMessage}}, ) @version(1) def get_task_status( @@ -116,7 +116,7 @@ def revoke_task( @router.get( "/inspect", responses={ - "500": {}, + "500": {"model": ErrorMessage}, "200": { "content": { "application/json": { @@ -169,7 +169,7 @@ def inspect_workers( @router.get( "/ping", responses={ - "500": {}, + "500": {"model": ErrorMessage}, "200": { "content": { "application/json": { @@ -216,7 +216,7 @@ def discard_all_waiting_tasks(user: AuthUser = Depends(admin_required)): @router.get( "/queue", responses={ - "500": {}, + "500": {"model": ErrorMessage}, "200": { "content": {"application/json": {"example": {"raw_daemon": {"length": 0}}}} }, diff --git a/src/app.py b/src/app.py index 8294c2ac..fd3f6908 100644 --- a/src/app.py +++ b/src/app.py @@ -942,7 +942,8 @@ def __init__(self, geojson=None, iso3=None): self.API_URL = POLYGON_STATISTICS_API_URL if geojson is None and iso3 is None: raise HTTPException( - status_code=404, detail="Either geojson or iso3 should be passed" + status_code=404, + detail=[{"msg": "Either geojson or iso3 should be passed"}], ) if iso3: @@ -952,7 +953,9 @@ def __init__(self, geojson=None, iso3=None): cur.execute(get_country_geom_from_iso(iso3)) result = cur.fetchone() if result is None: - raise HTTPException(status_code=404, detail="Invalid iso3 code") + raise HTTPException( + status_code=404, detail=[{"msg": "Invalid iso3 code"}] + ) self.INPUT_GEOM = result[0] else: self.INPUT_GEOM = dumps(geojson) @@ -1936,7 +1939,7 @@ def create_hdx(self, hdx_data): result = self.cur.fetchone() if result: return {"create": True} - raise HTTPException(status_code=500, detail="Insert failed") + raise HTTPException(status_code=500, detail=[{"msg": "Insert failed"}]) def get_hdx_list_with_filters( self, skip: int = 0, limit: int = 10, filters: dict = {} @@ -2025,7 +2028,7 @@ def get_hdx_by_id(self, hdx_id: int): self.d_b.close_conn() if result: return orjson.loads(result[0]) - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail=[{"msg": "Item not found"}]) def update_hdx(self, hdx_id: int, hdx_data): """ @@ -2067,7 +2070,7 @@ def update_hdx(self, hdx_id: int, hdx_data): self.d_b.close_conn() if result: return {"update": True} - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail=[{"msg": "Item not found"}]) def patch_hdx(self, hdx_id: int, hdx_data: dict): """ @@ -2107,7 +2110,7 @@ def patch_hdx(self, hdx_id: int, hdx_data: dict): if result: return {"update": True} - raise HTTPException(status_code=404, detail="Item not found") + raise HTTPException(status_code=404, detail=[{"msg": "Item not found"}]) def delete_hdx(self, hdx_id: int): """ @@ -2135,4 +2138,4 @@ def delete_hdx(self, hdx_id: int): self.d_b.close_conn() if result: return dict(result[0]) - raise HTTPException(status_code=404, detail="HDX item not found") + raise HTTPException(status_code=404, detail=[{"msg": "HDX item not found"}])