From eee6a5bd9233f3dbf4e6ed3053e816f9baff46d9 Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Thu, 17 Jul 2025 10:48:19 +0200 Subject: [PATCH 1/8] initil api & controller --- api/src/api/app/apis/utils_api.py | 38 +++++++++++++++++++ .../api/app/controller/utils_controller.py | 25 ++++++++++++ api/src/api/server.py | 10 +++-- 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 api/src/api/app/apis/utils_api.py create mode 100644 api/src/api/app/controller/utils_controller.py diff --git a/api/src/api/app/apis/utils_api.py b/api/src/api/app/apis/utils_api.py new file mode 100644 index 0000000..0ad967d --- /dev/null +++ b/api/src/api/app/apis/utils_api.py @@ -0,0 +1,38 @@ +# coding: utf-8 + +import logging +from typing import List # noqa: F401 +from datetime import date +from pydantic import Field, StrictStr +from typing import List, Optional +from typing_extensions import Annotated +from fastapi import ( # noqa: F401 + APIRouter, + Body, + File, + Path, + Query, + Request, + UploadFile, +) + +from app.controller.utils_controller import UtilsController + + +router = APIRouter() +controller = UtilsController() + +log = logging.getLogger('API.Utils') +logging.basicConfig(level=logging.INFO) + +@router.post( + "/utils/share/casedata", + status_code=202, + tags=["Utils"], +) +async def validate_and_forward_shared_case_data( + file: UploadFile = File(None, description="csv file of case data to share with ESID") +) -> None: + """Share Case Data with ESID.""" + log.info(f'POST /utils/caseshare received...') + return await controller.handle_case_data(file) \ No newline at end of file diff --git a/api/src/api/app/controller/utils_controller.py b/api/src/api/app/controller/utils_controller.py new file mode 100644 index 0000000..b78e39f --- /dev/null +++ b/api/src/api/app/controller/utils_controller.py @@ -0,0 +1,25 @@ +# coding: utf-8 + +from typing import ClassVar, Dict, List, Tuple # noqa: F401 +from datetime import date, timedelta +from json import dumps +from pathlib import Path +from pydantic import Field, StrictBytes, StrictFloat, StrictInt, StrictStr +from typing import Any, Dict, List, Optional, Tuple, Union, Set +from typing_extensions import Annotated +from fastapi import HTTPException, UploadFile + + +class UtilsController: + + async def handle_case_data( + self, + file: UploadFile, + ) -> None: + """Validate the upladed file and forward it""" + # Check if actually a csv file + if not file or not file.filename.endswith('.csv'): + raise HTTPException( + status_code=400 + ) + return None diff --git a/api/src/api/server.py b/api/src/api/server.py index 9a2642e..7070a26 100644 --- a/api/src/api/server.py +++ b/api/src/api/server.py @@ -20,6 +20,7 @@ from app.apis.nodes_api import router as NodesApiRouter from app.apis.parameter_definitions_api import router as ParameterDefinitionsApiRouter from app.apis.scenarios_api import router as ScenariosApiRouter +from app.apis.utils_api import router as UtilsApiRouter from app.middlewares.authentication_middleware import AuthenticationMiddleware app = FastAPI( @@ -40,13 +41,14 @@ app.add_middleware(AuthenticationMiddleware) +app.include_router(ScenariosApiRouter) +app.include_router(ModelsApiRouter) app.include_router(CompartmentsApiRouter) app.include_router(GroupsApiRouter) -app.include_router(InterventionsApiRouter) -app.include_router(ModelsApiRouter) -app.include_router(NodesApiRouter) app.include_router(ParameterDefinitionsApiRouter) -app.include_router(ScenariosApiRouter) +app.include_router(NodesApiRouter) +app.include_router(InterventionsApiRouter) +app.include_router(UtilsApiRouter) """ @app.post("/test_celery/{message}") From 4ac93d005e961e4dfe48cff224fb9b46f4486bbb Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Tue, 29 Jul 2025 16:34:24 +0200 Subject: [PATCH 2/8] restructure env vars, add minio client creation --- api/.docker-env.template | 4 ++ api/src/api/app/apis/utils_api.py | 2 +- .../api/app/controller/scenario_controller.py | 6 +-- .../api/app/controller/utils_controller.py | 46 +++++++++++++++++-- .../middlewares/authentication_middleware.py | 2 +- api/src/api/core/config.py | 9 +++- api/src/api/requirements.txt | 1 + 7 files changed, 60 insertions(+), 10 deletions(-) diff --git a/api/.docker-env.template b/api/.docker-env.template index 1ff8af4..ee5d5c0 100644 --- a/api/.docker-env.template +++ b/api/.docker-env.template @@ -16,3 +16,7 @@ API_PORT=8000 DB_PORT=5432 API_PATH_PREFIX= + +UPLOAD_FORWARD_ENDPOINT= +UPLOAD_FORWARD_ACCESS_KEY= +UPLOAD_FORWARD_SECRET_KEY= diff --git a/api/src/api/app/apis/utils_api.py b/api/src/api/app/apis/utils_api.py index 0ad967d..743c7f6 100644 --- a/api/src/api/app/apis/utils_api.py +++ b/api/src/api/app/apis/utils_api.py @@ -35,4 +35,4 @@ async def validate_and_forward_shared_case_data( ) -> None: """Share Case Data with ESID.""" log.info(f'POST /utils/caseshare received...') - return await controller.handle_case_data(file) \ No newline at end of file + return await controller.handle_case_data_validation_upload(file) \ No newline at end of file diff --git a/api/src/api/app/controller/scenario_controller.py b/api/src/api/app/controller/scenario_controller.py index de1c9c3..13fac72 100644 --- a/api/src/api/app/controller/scenario_controller.py +++ b/api/src/api/app/controller/scenario_controller.py @@ -44,8 +44,8 @@ ) class LookupObject: - scenario: Scenario = None - model: Model = None + scenario: Scenario|None = None + model: Model|None = None groups: List[Group] = [] compartments: List[Compartment] = [] nodes: List[Node] @@ -118,7 +118,7 @@ async def import_scenario_data( file: UploadFile, ) -> ID: """Supply simulation data for a scenario.""" - if not file or not file.filename.endswith('.zip'): + if not file or not file.filename or not file.filename.endswith('.zip'): raise HTTPException( status_code=422, detail="No file uploaded with request or not a .zip file" diff --git a/api/src/api/app/controller/utils_controller.py b/api/src/api/app/controller/utils_controller.py index b78e39f..33e9111 100644 --- a/api/src/api/app/controller/utils_controller.py +++ b/api/src/api/app/controller/utils_controller.py @@ -1,5 +1,6 @@ # coding: utf-8 +import logging from typing import ClassVar, Dict, List, Tuple # noqa: F401 from datetime import date, timedelta from json import dumps @@ -8,18 +9,57 @@ from typing import Any, Dict, List, Optional, Tuple, Union, Set from typing_extensions import Annotated from fastapi import HTTPException, UploadFile +from core import config +from functools import lru_cache +from minio import Minio +from os import path +log = logging.getLogger('API.Utils') +logging.basicConfig(level=logging.INFO) class UtilsController: - async def handle_case_data( + @lru_cache + def create_minio_client() -> Minio: + """ + Create a Minio client to upload to a bucket + """ + client = Minio( + endpoint=str(config.UPLOAD_FORWARD_ENDPOINT), + access_key=str(config.UPLOAD_FORWARD_ACCESS_KEY), + secret_key=str(config.UPLOAD_FORWARD_SECRET_KEY), + ) + return client + + + async def handle_case_data_validation_upload( self, file: UploadFile, ) -> None: """Validate the upladed file and forward it""" # Check if actually a csv file - if not file or not file.filename.endswith('.csv'): + if not file or not file.filename or not file.filename.lower().endswith('.csv'): + raise HTTPException( + status_code=400, + detail='No CSV file sent' + ) + # Check mime type + valid_content_types = ['text/csv', 'application/vnd.ms-excel'] + if file.content_type not in valid_content_types: raise HTTPException( - status_code=400 + status_code=400, + detail=f"File has the wrong content type. Accepts {valid_content_types} but got '{file.content_type}'" ) + # Check first line + line = file.file.readline().decode(encoding='utf-8') + num_cols = len(line.split(';')) # This assumes ';' is always used as separator + if num_cols != 76: # This is also assumes the file always hass this magic number of columns + raise HTTPException( + status_code=400, + detail=f"File has the wrong amount of columns. Needs 76 but has '{num_cols}'" + ) + + # Validation successful, upload to minio bucket + object_path_in_bucket = path.join("arrivals", "", file.filename) + return None diff --git a/api/src/api/app/middlewares/authentication_middleware.py b/api/src/api/app/middlewares/authentication_middleware.py index c3013f3..2316dac 100644 --- a/api/src/api/app/middlewares/authentication_middleware.py +++ b/api/src/api/app/middlewares/authentication_middleware.py @@ -9,7 +9,7 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): # authenticate all methods except GET and OPTIONS - protected_methods = ['POST', 'PUT', 'DELETE'] + protected_methods = [] #['POST', 'PUT', 'DELETE'] if request.method in protected_methods: try: # try to verify token and extract user information diff --git a/api/src/api/core/config.py b/api/src/api/core/config.py index 37f083c..24357e7 100644 --- a/api/src/api/core/config.py +++ b/api/src/api/core/config.py @@ -1,6 +1,6 @@ from databases import DatabaseURL from starlette.config import Config -from starlette.datastructures import Secret +from starlette.datastructures import Secret, URL config = Config(".env") @@ -28,4 +28,9 @@ ) # OAuth2 settings -IDP_ROOT_URL = config("IDP_ROOT_URL", cast=str, default="https://dev.lokiam.de") \ No newline at end of file +IDP_ROOT_URL = config("IDP_ROOT_URL", cast=str, default="https://dev.lokiam.de") + +# Forward of uploaded case file settings +UPLOAD_FORWARD_ENDPOINT = config("UPLOAD_FORWARD_ENDPOINT", cast=URL) +UPLOAD_FORWARD_ACCESS_KEY = config("UPLOAD_FORWARD_ACCESS_KEY", cast=Secret) +UPLOAD_FORWARD_SECRET_KEY = config("UPLOAD_FORWARD_SECRET_KEY", cast=Secret) diff --git a/api/src/api/requirements.txt b/api/src/api/requirements.txt index cf4ebf0..728f093 100644 --- a/api/src/api/requirements.txt +++ b/api/src/api/requirements.txt @@ -19,3 +19,4 @@ aiofiles==23.1.0 numpy==1.26.4 pandas==1.5.3 h5py==3.10.0 +minio==7.2.15 \ No newline at end of file From 4b33df91ba2ec3774165d32ec42350a19bcea3e1 Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Fri, 8 Aug 2025 14:42:18 +0200 Subject: [PATCH 3/8] add minio and finish upload --- api/.docker-env.template | 3 + api/src/api/app/apis/utils_api.py | 3 +- .../api/app/controller/utils_controller.py | 87 +++++++++++++++---- .../middlewares/authentication_middleware.py | 4 +- api/src/api/app/models/__init__.py | 3 +- api/src/api/core/config.py | 3 +- api/src/api/requirements.txt | 3 +- 7 files changed, 85 insertions(+), 21 deletions(-) diff --git a/api/.docker-env.template b/api/.docker-env.template index ee5d5c0..8c7ea8f 100644 --- a/api/.docker-env.template +++ b/api/.docker-env.template @@ -20,3 +20,6 @@ API_PATH_PREFIX= UPLOAD_FORWARD_ENDPOINT= UPLOAD_FORWARD_ACCESS_KEY= UPLOAD_FORWARD_SECRET_KEY= + +IDP_ROOT_URL= +IDP_API_URL= \ No newline at end of file diff --git a/api/src/api/app/apis/utils_api.py b/api/src/api/app/apis/utils_api.py index 743c7f6..d00d50b 100644 --- a/api/src/api/app/apis/utils_api.py +++ b/api/src/api/app/apis/utils_api.py @@ -31,8 +31,9 @@ tags=["Utils"], ) async def validate_and_forward_shared_case_data( + request: Request, file: UploadFile = File(None, description="csv file of case data to share with ESID") ) -> None: """Share Case Data with ESID.""" log.info(f'POST /utils/caseshare received...') - return await controller.handle_case_data_validation_upload(file) \ No newline at end of file + return await controller.handle_case_data_validation_upload(file, request.state) \ No newline at end of file diff --git a/api/src/api/app/controller/utils_controller.py b/api/src/api/app/controller/utils_controller.py index 33e9111..c49ce5e 100644 --- a/api/src/api/app/controller/utils_controller.py +++ b/api/src/api/app/controller/utils_controller.py @@ -2,39 +2,30 @@ import logging from typing import ClassVar, Dict, List, Tuple # noqa: F401 -from datetime import date, timedelta +from datetime import datetime from json import dumps from pathlib import Path from pydantic import Field, StrictBytes, StrictFloat, StrictInt, StrictStr from typing import Any, Dict, List, Optional, Tuple, Union, Set from typing_extensions import Annotated from fastapi import HTTPException, UploadFile +from starlette.datastructures import State +from app.models.user_detail import UserDetail from core import config from functools import lru_cache from minio import Minio -from os import path +import os +import requests log = logging.getLogger('API.Utils') logging.basicConfig(level=logging.INFO) class UtilsController: - @lru_cache - def create_minio_client() -> Minio: - """ - Create a Minio client to upload to a bucket - """ - client = Minio( - endpoint=str(config.UPLOAD_FORWARD_ENDPOINT), - access_key=str(config.UPLOAD_FORWARD_ACCESS_KEY), - secret_key=str(config.UPLOAD_FORWARD_SECRET_KEY), - ) - return client - - async def handle_case_data_validation_upload( self, file: UploadFile, + request_state: State, ) -> None: """Validate the upladed file and forward it""" # Check if actually a csv file @@ -60,6 +51,70 @@ async def handle_case_data_validation_upload( ) # Validation successful, upload to minio bucket - object_path_in_bucket = path.join("arrivals", "", file.filename) + lha_id: str = request_state.realm + # Get lha display name + lha_name = next((realm['displayName'] for realm in get_realms() if realm['realm'] == lha_id), '') + uploader: UserDetail = request_state.user + + meta = { + 'lha': { + 'id': lha_id, + 'name': lha_name, + }, + 'uploader': { + 'id': uploader.userId, + 'email': uploader.email, + 'roles': uploader.role, + }, + 'upload_timestamp': datetime.now().isoformat(), + } + + object_path_in_bucket = os.path.join("arrivals", lha_id, file.filename) + log.info(f'uploading \"{file.filename}\" into \"{object_path_in_bucket}\"') + log.info(f'meta info: {meta}') + + client = create_minio_client() + # go to end of stream to read size + file.file.seek(0, os.SEEK_END) + size = file.file.tell() + # reset to start for upload + file.file.seek(0, 0) + try: + result = client.put_object( + bucket_name='private-lha-data', + object_name=object_path_in_bucket, + data=file.file, + length=size + ) + log.info(f'created: {result.object_name}, etag: {result.etag}, version: {result.version_id}') + except Exception as ex: + log.warning(f'Unable to upload file: {ex}') + raise HTTPException( + status_code=500, + detail='An error occurred during file upload. Ceck the logs or contact an administrator.' + ) return None + +@lru_cache +def get_realms() -> List[Any]: + """ + Request realm list from IDP API + """ + result_realms = requests.get(f'{str(config.IDP_API_URL)}/realms') + if result_realms.status_code != 200: + raise HTTPException(status_code=500, detail='IDP API unreachable to request realms') + return result_realms.json() + + +@lru_cache +def create_minio_client() -> Minio: + """ + Create a Minio client to upload to a bucket + """ + client = Minio( + endpoint=str(config.UPLOAD_FORWARD_ENDPOINT), + access_key=str(config.UPLOAD_FORWARD_ACCESS_KEY), + secret_key=str(config.UPLOAD_FORWARD_SECRET_KEY), + ) + return client \ No newline at end of file diff --git a/api/src/api/app/middlewares/authentication_middleware.py b/api/src/api/app/middlewares/authentication_middleware.py index bf7fe62..f27bc8f 100644 --- a/api/src/api/app/middlewares/authentication_middleware.py +++ b/api/src/api/app/middlewares/authentication_middleware.py @@ -4,12 +4,14 @@ from security_api import get_user, get_bearer, get_realm +from app.models.user_detail import UserDetail + # authentication middleware that filters requests before they reach the endpoints # so far it only checks if the user is authenticated for POST, PUT, DELETE methods class AuthenticationMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): # authenticate all methods except GET and OPTIONS - protected_methods = [] #['POST', 'PUT', 'DELETE'] + protected_methods = ['POST', 'PUT', 'DELETE'] if request.method in protected_methods: try: # try to verify token and extract user information diff --git a/api/src/api/app/models/__init__.py b/api/src/api/app/models/__init__.py index b3f0d0e..9258168 100644 --- a/api/src/api/app/models/__init__.py +++ b/api/src/api/app/models/__init__.py @@ -16,4 +16,5 @@ from .reduced_info import ReducedInfo from .reduced_scenario import ReducedScenario from .scenario import Scenario -from .tagged import Tagged \ No newline at end of file +from .tagged import Tagged +from .user_detail import UserDetail \ No newline at end of file diff --git a/api/src/api/core/config.py b/api/src/api/core/config.py index 6ebf483..023f1d7 100644 --- a/api/src/api/core/config.py +++ b/api/src/api/core/config.py @@ -28,7 +28,8 @@ ) # OAuth2 settings -IDP_ROOT_URL = config("IDP_ROOT_URL", cast=str, default="https://dev.lokiam.de") +IDP_ROOT_URL = config("IDP_ROOT_URL", cast=URL) +IDP_API_URL = config("IDP_API_URL", cast=URL) # Forward of uploaded case file settings UPLOAD_FORWARD_ENDPOINT = config("UPLOAD_FORWARD_ENDPOINT", cast=URL) diff --git a/api/src/api/requirements.txt b/api/src/api/requirements.txt index 728f093..c8a6d6a 100644 --- a/api/src/api/requirements.txt +++ b/api/src/api/requirements.txt @@ -19,4 +19,5 @@ aiofiles==23.1.0 numpy==1.26.4 pandas==1.5.3 h5py==3.10.0 -minio==7.2.15 \ No newline at end of file +minio==7.2.15 +requests==2.32.4 \ No newline at end of file From ececb3562ce3eb93bfda49d7852fe450337a8c73 Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Tue, 2 Sep 2025 10:16:21 +0200 Subject: [PATCH 4/8] fixed packages libgl1-mesa-glx to libglx-mesa0 when python image was upgraded to trixie (debian) transitional package deprecated --- api/src/worker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/worker/Dockerfile b/api/src/worker/Dockerfile index 08bb9fb..72098e2 100644 --- a/api/src/worker/Dockerfile +++ b/api/src/worker/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /worker RUN apt-get update \ && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 poppler-utils tesseract-ocr libtesseract-dev git \ - ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-glx\ + ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libglx-mesa0\ && apt-get clean COPY ./requirements.txt /worker/requirements.txt From 7a8bb12e340fa4e8f1ca3faa08777eda241d7ea3 Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Fri, 5 Sep 2025 13:33:30 +0200 Subject: [PATCH 5/8] add metadata to uploaded file --- api/src/api/app/controller/utils_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/api/app/controller/utils_controller.py b/api/src/api/app/controller/utils_controller.py index c49ce5e..8d49ab4 100644 --- a/api/src/api/app/controller/utils_controller.py +++ b/api/src/api/app/controller/utils_controller.py @@ -84,14 +84,15 @@ async def handle_case_data_validation_upload( bucket_name='private-lha-data', object_name=object_path_in_bucket, data=file.file, - length=size + length=size, + metadata=meta ) log.info(f'created: {result.object_name}, etag: {result.etag}, version: {result.version_id}') except Exception as ex: log.warning(f'Unable to upload file: {ex}') raise HTTPException( status_code=500, - detail='An error occurred during file upload. Ceck the logs or contact an administrator.' + detail='An error occurred during file upload. Check the logs or contact an administrator.' ) return None From 10397e9821ada9fccf88baef496fa92858b1d348 Mon Sep 17 00:00:00 2001 From: Moritz Zeumer <25636783+NXXR@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:25:21 +0200 Subject: [PATCH 6/8] Apply suggestions from self review remove leftover debug code, and linter fixes that don"t belong to this PR --- api/src/api/app/controller/scenario_controller.py | 6 +++--- api/src/api/app/middlewares/authentication_middleware.py | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/src/api/app/controller/scenario_controller.py b/api/src/api/app/controller/scenario_controller.py index 85b094b..92eb620 100644 --- a/api/src/api/app/controller/scenario_controller.py +++ b/api/src/api/app/controller/scenario_controller.py @@ -44,8 +44,8 @@ ) class LookupObject: - scenario: Scenario|None = None - model: Model|None = None + scenario: Scenario = None + model: Model = None groups: List[Group] = [] compartments: List[Compartment] = [] nodes: List[Node] @@ -118,7 +118,7 @@ async def import_scenario_data( file: UploadFile, ) -> ID: """Supply simulation data for a scenario.""" - if not file or not file.filename or not file.filename.endswith('.zip'): + if not file or not file.filename.endswith('.zip'): raise HTTPException( status_code=422, detail="No file uploaded with request or not a .zip file" diff --git a/api/src/api/app/middlewares/authentication_middleware.py b/api/src/api/app/middlewares/authentication_middleware.py index f27bc8f..5308980 100644 --- a/api/src/api/app/middlewares/authentication_middleware.py +++ b/api/src/api/app/middlewares/authentication_middleware.py @@ -4,8 +4,6 @@ from security_api import get_user, get_bearer, get_realm -from app.models.user_detail import UserDetail - # authentication middleware that filters requests before they reach the endpoints # so far it only checks if the user is authenticated for POST, PUT, DELETE methods class AuthenticationMiddleware(BaseHTTPMiddleware): From 0e3c424ec521ff41bccf6e0c094e281c83be6ee2 Mon Sep 17 00:00:00 2001 From: Moritz Zeumer <25636783+NXXR@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:29:45 +0200 Subject: [PATCH 7/8] Revert Dockerfile fix --- api/src/worker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/worker/Dockerfile b/api/src/worker/Dockerfile index 72098e2..08bb9fb 100644 --- a/api/src/worker/Dockerfile +++ b/api/src/worker/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /worker RUN apt-get update \ && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 poppler-utils tesseract-ocr libtesseract-dev git \ - ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libglx-mesa0\ + ninja-build libglib2.0-0 libsm6 libxrender-dev libxext6 libgl1-mesa-glx\ && apt-get clean COPY ./requirements.txt /worker/requirements.txt From d4cacf42c681ddc4ada5d043b65a99f7d9d7991d Mon Sep 17 00:00:00 2001 From: "Zeumer, Moritz" Date: Wed, 12 Nov 2025 14:09:36 +0100 Subject: [PATCH 8/8] add logging for exceptions --- api/src/api/app/controller/utils_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/src/api/app/controller/utils_controller.py b/api/src/api/app/controller/utils_controller.py index 8d49ab4..43ad3dd 100644 --- a/api/src/api/app/controller/utils_controller.py +++ b/api/src/api/app/controller/utils_controller.py @@ -30,6 +30,7 @@ async def handle_case_data_validation_upload( """Validate the upladed file and forward it""" # Check if actually a csv file if not file or not file.filename or not file.filename.lower().endswith('.csv'): + log.warning(f'No CSV file') raise HTTPException( status_code=400, detail='No CSV file sent' @@ -37,6 +38,7 @@ async def handle_case_data_validation_upload( # Check mime type valid_content_types = ['text/csv', 'application/vnd.ms-excel'] if file.content_type not in valid_content_types: + log.warning(f"File has the wrong content type. Accepts {valid_content_types} but got '{file.content_type}'") raise HTTPException( status_code=400, detail=f"File has the wrong content type. Accepts {valid_content_types} but got '{file.content_type}'" @@ -45,6 +47,7 @@ async def handle_case_data_validation_upload( line = file.file.readline().decode(encoding='utf-8') num_cols = len(line.split(';')) # This assumes ';' is always used as separator if num_cols != 76: # This is also assumes the file always hass this magic number of columns + log.warning(f"File has the wrong amount of columns. Needs 76 but has '{num_cols}'") raise HTTPException( status_code=400, detail=f"File has the wrong amount of columns. Needs 76 but has '{num_cols}'" @@ -94,7 +97,7 @@ async def handle_case_data_validation_upload( status_code=500, detail='An error occurred during file upload. Check the logs or contact an administrator.' ) - + log.info('POST /utils/caseshare success') return None @lru_cache