diff --git a/backend/endpoints/collections.py b/backend/endpoints/collections.py index f09b3fdc8..146262583 100644 --- a/backend/endpoints/collections.py +++ b/backend/endpoints/collections.py @@ -1,7 +1,9 @@ import json from io import BytesIO +from typing import Annotated -from fastapi import Request, UploadFile +from fastapi import Path as PathVar +from fastapi import Request, UploadFile, status from config import str_to_bool from decorators.auth import protected_route @@ -388,22 +390,18 @@ async def update_smart_collection( return SmartCollectionSchema.model_validate(smart_collection) -@protected_route(router.delete, "/{id}", [Scope.COLLECTIONS_WRITE]) -async def delete_collection(request: Request, id: int) -> None: - """Delete collections endpoint - - Args: - request (Request): Fastapi Request object - { - "collections": List of rom's ids to delete - } - - Raises: - HTTPException: Collection not found - """ - +@protected_route( + router.delete, + "/{id}", + [Scope.COLLECTIONS_WRITE], + responses={status.HTTP_404_NOT_FOUND: {}}, +) +async def delete_collection( + request: Request, + id: Annotated[int, PathVar(description="Collection internal id.", ge=1)], +) -> None: + """Delete a collection by ID.""" collection = db_collection_handler.get_collection(id) - if not collection: raise CollectionNotFoundInDatabaseException(id) @@ -418,17 +416,18 @@ async def delete_collection(request: Request, id: int) -> None: ) -@protected_route(router.delete, "/smart/{id}", [Scope.COLLECTIONS_WRITE]) -async def delete_smart_collection(request: Request, id: int) -> None: - """Delete smart collection endpoint - - Args: - request (Request): Fastapi Request object - id (int): Smart collection id - """ - +@protected_route( + router.delete, + "/smart/{id}", + [Scope.COLLECTIONS_WRITE], + responses={status.HTTP_404_NOT_FOUND: {}}, +) +async def delete_smart_collection( + request: Request, + id: Annotated[int, PathVar(description="Smart collection internal id.", ge=1)], +) -> None: + """Delete a smart collection by ID.""" smart_collection = db_collection_handler.get_smart_collection(id) - if not smart_collection: raise CollectionNotFoundInDatabaseException(id) diff --git a/backend/endpoints/firmware.py b/backend/endpoints/firmware.py index e714ad7d7..efc5ee0e7 100644 --- a/backend/endpoints/firmware.py +++ b/backend/endpoints/firmware.py @@ -1,4 +1,6 @@ -from fastapi import File, HTTPException, Request, UploadFile, status +from typing import Annotated + +from fastapi import Body, File, HTTPException, Request, UploadFile, status from fastapi.responses import FileResponse from config import DISABLE_DOWNLOAD_ENDPOINT_AUTH @@ -216,46 +218,46 @@ def get_firmware_content( @protected_route(router.post, "/delete", [Scope.FIRMWARE_WRITE]) async def delete_firmware( request: Request, + firmware: Annotated[ + list[int], + Body( + description="List of firmware ids to delete from database.", + embed=True, + ), + ], + delete_from_fs: Annotated[ + list[int], + Body( + description="List of firmware ids to delete from filesystem.", + default_factory=list, + embed=True, + ), + ], ) -> BulkOperationResponse: - """Delete firmware endpoint - - Args: - request (Request): Fastapi Request object. - { - "firmware": List of firmware IDs to delete - } - delete_from_fs (bool, optional): Flag to delete rom from filesystem. Defaults to False. - - Returns: - BulkOperationResponse: Bulk operation response with details - """ - - data: dict = await request.json() - firmware_ids: list = data["firmware"] - delete_from_fs: list = data["delete_from_fs"] + """Delete firmware.""" successful_items = 0 failed_items = 0 errors = [] - for id in firmware_ids: - firmware = db_firmware_handler.get_firmware(id) - if not firmware: + for id in firmware: + fw = db_firmware_handler.get_firmware(id) + if not fw: failed_items += 1 errors.append(f"Firmware with ID {id} not found") continue try: - log.info(f"Deleting {hl(firmware.file_name)} from database") + log.info(f"Deleting {hl(fw.file_name)} from database") db_firmware_handler.delete_firmware(id) if id in delete_from_fs: - log.info(f"Deleting {hl(firmware.file_name)} from filesystem") + log.info(f"Deleting {hl(fw.file_name)} from filesystem") try: - file_path = f"{firmware.file_path}/{firmware.file_name}" + file_path = f"{fw.file_path}/{fw.file_name}" await fs_firmware_handler.remove_file(file_path=file_path) except FileNotFoundError: - error = f"Firmware file {hl(firmware.file_name)} not found for platform {hl(firmware.platform.slug)}" + error = f"Firmware file {hl(fw.file_name)} not found for platform {hl(fw.platform.slug)}" log.error(error) errors.append(error) failed_items += 1 diff --git a/backend/endpoints/platform.py b/backend/endpoints/platform.py index 6c87c02ea..74459023b 100644 --- a/backend/endpoints/platform.py +++ b/backend/endpoints/platform.py @@ -204,7 +204,7 @@ async def delete_platform( request: Request, id: Annotated[int, PathVar(description="Platform id.", ge=1)], ) -> None: - """Delete a platform.""" + """Delete a platform by ID.""" platform = db_platform_handler.get_platform(id) if not platform: diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index 0c6e31cf4..cd9b65778 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -874,13 +874,17 @@ async def delete_roms( request: Request, roms: Annotated[ list[int], - Body(description="List of rom ids to delete from database."), + Body( + description="List of rom ids to delete from database.", + embed=True, + ), ], delete_from_fs: Annotated[ list[int], Body( description="List of rom ids to delete from filesystem.", default_factory=list, + embed=True, ), ], ) -> BulkOperationResponse: diff --git a/backend/endpoints/saves.py b/backend/endpoints/saves.py index cc8908d0a..46d465616 100644 --- a/backend/endpoints/saves.py +++ b/backend/endpoints/saves.py @@ -1,6 +1,7 @@ from datetime import datetime, timezone +from typing import Annotated -from fastapi import HTTPException, Request, UploadFile, status +from fastapi import Body, HTTPException, Request, UploadFile, status from decorators.auth import protected_route from endpoints.responses.assets import SaveSchema @@ -223,17 +224,32 @@ async def update_save(request: Request, id: int) -> SaveSchema: return SaveSchema.model_validate(db_save) -@protected_route(router.post, "/delete", [Scope.ASSETS_WRITE]) -async def delete_saves(request: Request) -> list[int]: - data: dict = await request.json() - save_ids: list = data["saves"] - - if not save_ids: +@protected_route( + router.post, + "/delete", + [Scope.ASSETS_WRITE], + responses={ + status.HTTP_400_BAD_REQUEST: {}, + status.HTTP_404_NOT_FOUND: {}, + }, +) +async def delete_saves( + request: Request, + saves: Annotated[ + list[int], + Body( + description="List of save ids to delete from database.", + embed=True, + ), + ], +) -> list[int]: + """Delete saves.""" + if not saves: error = "No saves were provided" log.error(error) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error) - for save_id in save_ids: + for save_id in saves: save = db_save_handler.get_save(user_id=request.user.id, id=save_id) if not save: error = f"Save with ID {save_id} not found" @@ -262,4 +278,4 @@ async def delete_saves(request: Request) -> list[int]: error = f"Screenshot file {hl(save.screenshot.file_name)} not found for save {hl(save.file_name)}[{hl(save.rom.platform_slug)}]" log.error(error) - return save_ids + return saves diff --git a/backend/endpoints/states.py b/backend/endpoints/states.py index 000258e37..f26d6d857 100644 --- a/backend/endpoints/states.py +++ b/backend/endpoints/states.py @@ -1,6 +1,7 @@ from datetime import datetime, timezone +from typing import Annotated -from fastapi import HTTPException, Request, UploadFile, status +from fastapi import Body, HTTPException, Request, UploadFile, status from decorators.auth import protected_route from endpoints.responses.assets import StateSchema @@ -227,17 +228,32 @@ async def update_state(request: Request, id: int) -> StateSchema: return StateSchema.model_validate(db_state) -@protected_route(router.post, "/delete", [Scope.ASSETS_WRITE]) -async def delete_states(request: Request) -> list[int]: - data: dict = await request.json() - state_ids: list = data["states"] - - if not state_ids: +@protected_route( + router.post, + "/delete", + [Scope.ASSETS_WRITE], + responses={ + status.HTTP_400_BAD_REQUEST: {}, + status.HTTP_404_NOT_FOUND: {}, + }, +) +async def delete_states( + request: Request, + states: Annotated[ + list[int], + Body( + description="List of states ids to delete from database.", + embed=True, + ), + ], +) -> list[int]: + """Delete states.""" + if not states: error = "No states were provided" log.error(error) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error) - for state_id in state_ids: + for state_id in states: state = db_state_handler.get_state(user_id=request.user.id, id=state_id) if not state: error = f"State with ID {state_id} not found" @@ -266,4 +282,4 @@ async def delete_states(request: Request) -> list[int]: error = f"Screenshot file {hl(state.screenshot.file_name)} not found for state {hl(state.file_name)}[{hl(state.rom.platform_slug)}]" log.error(error) - return state_ids + return states diff --git a/backend/endpoints/user.py b/backend/endpoints/user.py index e51f59be2..1103fe85b 100644 --- a/backend/endpoints/user.py +++ b/backend/endpoints/user.py @@ -336,13 +336,20 @@ async def update_user( return UserSchema.model_validate(db_user) -@protected_route(router.delete, "/{id}", [Scope.USERS_WRITE]) -async def delete_user(request: Request, id: int) -> None: - """Delete user endpoint - - Args: - request (Request): Fastapi Request object - user_id (int): User internal id +@protected_route( + router.delete, + "/{id}", + [Scope.USERS_WRITE], + responses={ + status.HTTP_400_BAD_REQUEST: {}, + status.HTTP_404_NOT_FOUND: {}, + }, +) +async def delete_user( + request: Request, + id: Annotated[int, PathVar(description="User internal id.", ge=1)], +) -> None: + """Delete a user by ID. Raises: HTTPException: User is not found in database