Add asset preview management endpoints (PUT/DELETE /api/assets/{id}/preview)

- PUT /api/assets/{id}/preview: sets the preview asset for an existing asset,
  returns the full updated Asset object. Returns 404 if asset ID doesn't exist.
- DELETE /api/assets/{id}/preview: clears the preview asset link, returns 204.
  Returns 404 if asset ID doesn't exist.

Added OpenAPI spec entries under the assets tag with no x-runtime restriction.
Implemented handlers using the existing set_asset_preview service function.
Added integration tests covering happy paths and 404 cases.

Co-authored-by: Matt Miller <MillerMedia@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-05-06 23:56:26 +00:00
committed by Matt Miller
parent 65045730a6
commit 9bd5710acb
3 changed files with 233 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ from app.assets.services import (
list_tags,
remove_tags,
resolve_asset_for_download,
set_asset_preview,
update_asset_metadata,
upload_from_temp_path,
)
@@ -545,6 +546,70 @@ async def delete_asset_route(request: web.Request) -> web.Response:
return web.Response(status=204)
@ROUTES.put(f"/api/assets/{{id:{UUID_RE}}}/preview")
@_require_assets_feature_enabled
async def set_asset_preview_route(request: web.Request) -> web.Response:
reference_id = str(uuid.UUID(request.match_info["id"]))
try:
payload = await request.json()
except Exception:
return _build_error_response(
400, "INVALID_JSON", "Request body must be valid JSON."
)
preview_id = payload.get("preview_id")
if not preview_id:
return _build_error_response(
400, "INVALID_BODY", "preview_id is required."
)
try:
result = set_asset_preview(
reference_id=reference_id,
preview_reference_id=preview_id,
owner_id=USER_MANAGER.get_request_user_id(request),
)
response_payload = _build_asset_response(result)
except PermissionError as pe:
return _build_error_response(403, "FORBIDDEN", str(pe), {"id": reference_id})
except ValueError as ve:
return _build_error_response(
404, "ASSET_NOT_FOUND", str(ve), {"id": reference_id}
)
except Exception:
logging.exception(
"set_asset_preview failed for reference_id=%s",
reference_id,
)
return _build_error_response(500, "INTERNAL", "Unexpected server error.")
return web.json_response(response_payload.model_dump(mode="json", exclude_none=True), status=200)
@ROUTES.delete(f"/api/assets/{{id:{UUID_RE}}}/preview")
@_require_assets_feature_enabled
async def clear_asset_preview_route(request: web.Request) -> web.Response:
reference_id = str(uuid.UUID(request.match_info["id"]))
try:
result = set_asset_preview(
reference_id=reference_id,
preview_reference_id=None,
owner_id=USER_MANAGER.get_request_user_id(request),
)
except PermissionError as pe:
return _build_error_response(403, "FORBIDDEN", str(pe), {"id": reference_id})
except ValueError as ve:
return _build_error_response(
404, "ASSET_NOT_FOUND", str(ve), {"id": reference_id}
)
except Exception:
logging.exception(
"clear_asset_preview failed for reference_id=%s",
reference_id,
)
return _build_error_response(500, "INTERNAL", "Unexpected server error.")
return web.Response(status=204)
@ROUTES.get("/api/tags")
@_require_assets_feature_enabled
async def get_tags(request: web.Request) -> web.Response: