Files
romm/backend/endpoints/saves.py
Michael Manganiello 40326112bd misc: Improve API docs for Delete endpoints
Add more detailed descriptions for Delete endpoints.
2025-09-09 11:05:47 -03:00

282 lines
9.6 KiB
Python

from datetime import datetime, timezone
from typing import Annotated
from fastapi import Body, HTTPException, Request, UploadFile, status
from decorators.auth import protected_route
from endpoints.responses.assets import SaveSchema
from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException
from handler.auth.constants import Scope
from handler.database import db_rom_handler, db_save_handler, db_screenshot_handler
from handler.filesystem import fs_asset_handler
from handler.scan_handler import scan_save, scan_screenshot
from logger.formatter import BLUE
from logger.formatter import highlight as hl
from logger.logger import log
from utils.router import APIRouter
router = APIRouter(
prefix="/saves",
tags=["saves"],
)
@protected_route(router.post, "", [Scope.ASSETS_WRITE])
async def add_save(
request: Request,
rom_id: int,
emulator: str | None = None,
) -> SaveSchema:
data = await request.form()
rom = db_rom_handler.get_rom(rom_id)
if not rom:
raise RomNotFoundInDatabaseException(rom_id)
log.info(f"Uploading save of {rom.name}")
saves_path = fs_asset_handler.build_saves_file_path(
user=request.user,
platform_fs_slug=rom.platform.fs_slug,
rom_id=rom_id,
emulator=emulator,
)
if "saveFile" not in data:
log.error("No save file provided")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="No save file provided"
)
saveFile: UploadFile = data["saveFile"] # type: ignore
if not saveFile.filename:
log.error("Save file has no filename")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Save file has no filename"
)
rom = db_rom_handler.get_rom(rom_id)
if not rom:
raise RomNotFoundInDatabaseException(rom_id)
log.info(
f"Uploading save {hl(saveFile.filename)} for {hl(str(rom.name), color=BLUE)}"
)
saves_path = fs_asset_handler.build_saves_file_path(
user=request.user,
platform_fs_slug=rom.platform.fs_slug,
rom_id=rom.id,
emulator=emulator,
)
await fs_asset_handler.write_file(file=saveFile, path=saves_path)
# Scan or update save
scanned_save = await scan_save(
file_name=saveFile.filename,
user=request.user,
platform_fs_slug=rom.platform.fs_slug,
rom_id=rom_id,
emulator=emulator,
)
db_save = db_save_handler.get_save_by_filename(
user_id=request.user.id, rom_id=rom.id, file_name=saveFile.filename
)
if db_save:
db_save = db_save_handler.update_save(
db_save.id, {"file_size_bytes": scanned_save.file_size_bytes}
)
else:
scanned_save.rom_id = rom.id
scanned_save.user_id = request.user.id
scanned_save.emulator = emulator
db_save = db_save_handler.add_save(save=scanned_save)
screenshotFile: UploadFile | None = data.get("screenshotFile", None) # type: ignore
if screenshotFile and screenshotFile.filename:
screenshots_path = fs_asset_handler.build_screenshots_file_path(
user=request.user, platform_fs_slug=rom.platform_slug, rom_id=rom.id
)
await fs_asset_handler.write_file(file=screenshotFile, path=screenshots_path)
# Scan or update screenshot
scanned_screenshot = await scan_screenshot(
file_name=screenshotFile.filename,
user=request.user,
platform_fs_slug=rom.platform_slug,
rom_id=rom.id,
)
db_screenshot = db_screenshot_handler.get_screenshot(
filename=screenshotFile.filename,
rom_id=rom.id,
user_id=request.user.id,
)
if db_screenshot:
db_screenshot = db_screenshot_handler.update_screenshot(
db_screenshot.id,
{"file_size_bytes": scanned_screenshot.file_size_bytes},
)
else:
scanned_screenshot.rom_id = rom.id
scanned_screenshot.user_id = request.user.id
db_screenshot_handler.add_screenshot(screenshot=scanned_screenshot)
# Set the last played time for the current user
rom_user = db_rom_handler.get_rom_user(rom_id=rom.id, user_id=request.user.id)
if not rom_user:
rom_user = db_rom_handler.add_rom_user(rom_id=rom.id, user_id=request.user.id)
db_rom_handler.update_rom_user(
rom_user.id, {"last_played": datetime.now(timezone.utc)}
)
# Refetch the rom to get updated saves
rom = db_rom_handler.get_rom(rom_id)
if not rom:
raise RomNotFoundInDatabaseException(rom_id)
return SaveSchema.model_validate(db_save)
@protected_route(router.get, "", [Scope.ASSETS_READ])
def get_saves(
request: Request, rom_id: int | None = None, platform_id: int | None = None
) -> list[SaveSchema]:
saves = db_save_handler.get_saves(
user_id=request.user.id, rom_id=rom_id, platform_id=platform_id
)
return [SaveSchema.model_validate(save) for save in saves]
@protected_route(router.get, "/{id}", [Scope.ASSETS_READ])
def get_save(request: Request, id: int) -> SaveSchema:
save = db_save_handler.get_save(user_id=request.user.id, id=id)
if not save:
error = f"Save with ID {id} not found"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
return SaveSchema.model_validate(save)
@protected_route(router.put, "/{id}", [Scope.ASSETS_WRITE])
async def update_save(request: Request, id: int) -> SaveSchema:
data = await request.form()
db_save = db_save_handler.get_save(user_id=request.user.id, id=id)
if not db_save:
error = f"Save with ID {id} not found"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
if "saveFile" in data:
saveFile: UploadFile = data["saveFile"] # type: ignore
await fs_asset_handler.write_file(file=saveFile, path=db_save.file_path)
db_save = db_save_handler.update_save(
db_save.id, {"file_size_bytes": saveFile.size}
)
screenshotFile: UploadFile | None = data.get("screenshotFile", None) # type: ignore
if screenshotFile and screenshotFile.filename:
screenshots_path = fs_asset_handler.build_screenshots_file_path(
user=request.user,
platform_fs_slug=db_save.rom.platform_slug,
rom_id=db_save.rom.id,
)
await fs_asset_handler.write_file(file=screenshotFile, path=screenshots_path)
# Scan or update screenshot
scanned_screenshot = await scan_screenshot(
file_name=screenshotFile.filename,
user=request.user,
platform_fs_slug=db_save.rom.platform_slug,
rom_id=db_save.rom.id,
)
db_screenshot = db_screenshot_handler.get_screenshot(
filename=screenshotFile.filename,
rom_id=db_save.rom.id,
user_id=request.user.id,
)
if db_screenshot:
db_screenshot = db_screenshot_handler.update_screenshot(
db_screenshot.id,
{"file_size_bytes": scanned_screenshot.file_size_bytes},
)
else:
scanned_screenshot.rom_id = db_save.rom.id
scanned_screenshot.user_id = request.user.id
db_screenshot_handler.add_screenshot(screenshot=scanned_screenshot)
# Set the last played time for the current user
rom_user = db_rom_handler.get_rom_user(db_save.rom_id, request.user.id)
if not rom_user:
rom_user = db_rom_handler.add_rom_user(db_save.rom_id, request.user.id)
db_rom_handler.update_rom_user(
rom_user.id, {"last_played": datetime.now(timezone.utc)}
)
# Refetch the save to get updated fields
return SaveSchema.model_validate(db_save)
@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 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"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
db_save_handler.delete_save(save_id)
log.info(
f"Deleting save {hl(save.file_name)} [{save.rom.platform_slug}] from filesystem"
)
try:
file_path = f"{save.file_path}/{save.file_name}"
await fs_asset_handler.remove_file(file_path=file_path)
except FileNotFoundError:
error = f"Save file {hl(save.file_name)} not found for platform {hl(save.rom.platform_display_name, color=BLUE)}[{hl(save.rom.platform_slug)}]"
log.error(error)
if save.screenshot:
db_screenshot_handler.delete_screenshot(save.screenshot.id)
try:
file_path = f"{save.screenshot.file_path}/{save.screenshot.file_name}"
await fs_asset_handler.remove_file(file_path=file_path)
except FileNotFoundError:
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 saves