mirror of
https://github.com/rommapp/romm.git
synced 2026-06-29 07:16:28 +00:00
171 lines
5.6 KiB
Python
171 lines
5.6 KiB
Python
from typing import Annotated
|
|
|
|
from fastapi import Body, File, HTTPException
|
|
from fastapi import Path as PathVar
|
|
from fastapi import Request, UploadFile, status
|
|
from fastapi.responses import Response
|
|
|
|
from decorators.auth import protected_route
|
|
from endpoints.responses.assets import ScreenshotSchema
|
|
from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException
|
|
from handler.auth.constants import Scope
|
|
from handler.database import db_rom_handler, db_screenshot_handler
|
|
from handler.filesystem import fs_asset_handler
|
|
from handler.scan_handler import scan_screenshot
|
|
from logger.formatter import BLUE
|
|
from logger.formatter import highlight as hl
|
|
from logger.logger import log
|
|
from utils.filesystem import sanitize_filename
|
|
from utils.router import APIRouter
|
|
from utils.screenshots import (
|
|
ALLOWED_SCREENSHOT_EXTENSIONS,
|
|
is_allowed_screenshot_file,
|
|
)
|
|
|
|
router = APIRouter(
|
|
prefix="/screenshots",
|
|
tags=["screenshots"],
|
|
)
|
|
|
|
SCREENSHOT_FILE_UPLOAD = File(..., description="Screenshot file to upload.")
|
|
|
|
|
|
@protected_route(router.post, "", [Scope.ASSETS_WRITE])
|
|
async def add_screenshot(
|
|
request: Request,
|
|
rom_id: int,
|
|
screenshotFile: UploadFile = SCREENSHOT_FILE_UPLOAD,
|
|
) -> ScreenshotSchema:
|
|
"""Upload a per-user gallery screenshot for a ROM.
|
|
|
|
Stored under the user's asset folder (not the ROM folder) and flagged
|
|
`is_gallery=True` so it surfaces in the gallery; `is_public=False` so it
|
|
stays private until the owner shares it.
|
|
"""
|
|
rom = db_rom_handler.get_rom(id=rom_id)
|
|
if not rom:
|
|
raise RomNotFoundInDatabaseException(rom_id)
|
|
|
|
current_user = request.user
|
|
log.info(f"Uploading screenshot to {hl(str(rom.name), color=BLUE)}")
|
|
|
|
screenshots_path = fs_asset_handler.build_screenshots_file_path(
|
|
user=request.user, platform_fs_slug=rom.platform_slug, rom_id=rom.id
|
|
)
|
|
|
|
if not screenshotFile.filename:
|
|
log.error("Screenshot file has no filename")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Screenshot file has no filename",
|
|
)
|
|
|
|
try:
|
|
sanitized_screenshot_filename = sanitize_filename(screenshotFile.filename)
|
|
except ValueError as exc:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Invalid screenshot filename: {str(exc)}",
|
|
) from exc
|
|
|
|
if not is_allowed_screenshot_file(sanitized_screenshot_filename):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=(
|
|
f"Unsupported image file type. Allowed: "
|
|
f"{', '.join(sorted(ALLOWED_SCREENSHOT_EXTENSIONS))}"
|
|
),
|
|
)
|
|
|
|
await fs_asset_handler.write_file(
|
|
file=screenshotFile,
|
|
path=screenshots_path,
|
|
filename=sanitized_screenshot_filename,
|
|
)
|
|
|
|
# Scan or update screenshot
|
|
scanned_screenshot = await scan_screenshot(
|
|
file_name=sanitized_screenshot_filename,
|
|
user=request.user,
|
|
platform_fs_slug=rom.platform_slug,
|
|
rom_id=rom.id,
|
|
)
|
|
db_screenshot = db_screenshot_handler.get_screenshot(
|
|
file_name=sanitized_screenshot_filename,
|
|
rom_id=rom.id,
|
|
user_id=current_user.id,
|
|
)
|
|
if db_screenshot:
|
|
db_screenshot = db_screenshot_handler.update_screenshot(
|
|
db_screenshot.id,
|
|
{
|
|
"file_size_bytes": scanned_screenshot.file_size_bytes,
|
|
"is_gallery": True,
|
|
"missing_from_fs": False,
|
|
},
|
|
)
|
|
else:
|
|
scanned_screenshot.rom_id = rom.id
|
|
scanned_screenshot.user_id = current_user.id
|
|
scanned_screenshot.is_gallery = True
|
|
db_screenshot = db_screenshot_handler.add_screenshot(
|
|
screenshot=scanned_screenshot
|
|
)
|
|
|
|
return ScreenshotSchema.model_validate(db_screenshot)
|
|
|
|
|
|
@protected_route(
|
|
router.put,
|
|
"/{id}",
|
|
[Scope.ASSETS_WRITE],
|
|
responses={status.HTTP_404_NOT_FOUND: {}},
|
|
)
|
|
async def update_screenshot(
|
|
request: Request,
|
|
id: Annotated[int, PathVar(description="Screenshot internal id.", ge=1)],
|
|
is_public: Annotated[bool, Body(embed=True)],
|
|
) -> ScreenshotSchema:
|
|
"""Toggle a gallery screenshot's public/private visibility (owner only)."""
|
|
screenshot = db_screenshot_handler.get_screenshot_by_id(id)
|
|
if not screenshot or screenshot.user_id != request.user.id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Screenshot not found",
|
|
)
|
|
|
|
updated = db_screenshot_handler.update_screenshot(id, {"is_public": is_public})
|
|
return ScreenshotSchema.model_validate(updated)
|
|
|
|
|
|
@protected_route(
|
|
router.delete,
|
|
"/{id}",
|
|
[Scope.ASSETS_WRITE],
|
|
responses={status.HTTP_404_NOT_FOUND: {}},
|
|
)
|
|
async def delete_screenshot(
|
|
request: Request,
|
|
id: Annotated[int, PathVar(description="Screenshot internal id.", ge=1)],
|
|
) -> Response:
|
|
"""Delete a gallery screenshot — its file and DB row (owner only)."""
|
|
screenshot = db_screenshot_handler.get_screenshot_by_id(id)
|
|
if not screenshot or screenshot.user_id != request.user.id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Screenshot not found",
|
|
)
|
|
|
|
try:
|
|
await fs_asset_handler.remove_file(file_path=screenshot.full_path)
|
|
except FileNotFoundError:
|
|
log.warning(
|
|
f"Screenshot file {hl(screenshot.full_path)} not found on disk; "
|
|
f"removing DB row anyway"
|
|
)
|
|
|
|
db_screenshot_handler.delete_screenshot(screenshot.id)
|
|
log.info(f"Deleted screenshot {hl(screenshot.file_name)}")
|
|
|
|
return Response()
|