Add result delivery workflow

This commit is contained in:
2026-06-30 15:54:26 +08:00
parent 9c80b95385
commit 04210b0551
18 changed files with 2021 additions and 98 deletions
+183
View File
@@ -0,0 +1,183 @@
from __future__ import annotations
import mimetypes
import os
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import FileResponse
from pydantic import BaseModel, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import get_db
from ..models import AuthUserORM
from ..services.result_delivery_service import result_delivery_service
from .dependencies import _add_operation_audit_log, _get_current_user
router = APIRouter()
class ResultDeliveryCreateRequest(BaseModel):
channel: str
product_ids: Optional[List[int]] = None
compat_result_ids: Optional[List[int]] = None
package_mode: str = "directory"
include_checksums: Optional[bool] = None
@field_validator("channel", mode="before")
@classmethod
def _validate_channel(cls, value):
text = str(value or "").strip().lower()
if not text:
raise ValueError("channel is required")
return text
@field_validator("package_mode", mode="before")
@classmethod
def _validate_package_mode(cls, value):
text = str(value or "directory").strip().lower()
if text not in {"directory", "zip"}:
raise ValueError("package_mode must be directory or zip")
return text
@router.get("/result-deliveries/channels")
async def get_result_delivery_channels(
current_user: AuthUserORM = Depends(_get_current_user),
):
_ = current_user
return {"items": result_delivery_service.channels()}
@router.post("/result-deliveries", status_code=202)
async def create_result_delivery(
request: ResultDeliveryCreateRequest,
http_request: Request,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
try:
delivery = await result_delivery_service.create_delivery(
db,
user=current_user,
channel=request.channel,
product_ids=request.product_ids,
compat_result_ids=request.compat_result_ids,
package_mode=request.package_mode,
include_checksums=request.include_checksums,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
await _add_operation_audit_log(
db,
request=http_request,
action="result_delivery_created",
resource="result-deliveries",
detail={
"delivery_id": delivery.delivery_id,
"channel": delivery.channel,
"package_mode": delivery.package_mode,
"item_count": delivery.item_count,
"task_id": delivery.task_id,
"job_id": delivery.job_id,
},
user=current_user,
)
await db.commit()
return result_delivery_service.serialize_delivery(delivery)
@router.get("/result-deliveries")
async def list_result_deliveries(
mine: bool = True,
include_all: bool = False,
limit: int = 50,
offset: int = 0,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
allow_all = bool(include_all) and str(current_user.role or "").lower() == "admin"
if mine:
allow_all = False
return await result_delivery_service.list_deliveries(
db,
user=current_user,
include_all=allow_all,
include_items=True,
item_limit=5,
limit=limit,
offset=offset,
)
@router.get("/result-deliveries/{delivery_id}")
async def get_result_delivery(
delivery_id: str,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
delivery = await result_delivery_service.get_delivery(
db,
delivery_id=delivery_id,
user=current_user,
include_items=True,
)
if delivery is None:
raise HTTPException(status_code=404, detail="delivery not found")
return result_delivery_service.serialize_delivery(delivery, include_items=True)
@router.get("/result-deliveries/{delivery_id}/manifest")
async def download_result_delivery_manifest(
delivery_id: str,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
try:
path = await result_delivery_service.resolve_manifest_path(
db,
delivery_id=delivery_id,
user=current_user,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return FileResponse(path, filename=os.path.basename(path), media_type="application/json")
@router.get("/result-deliveries/{delivery_id}/archive/download")
async def download_result_delivery_archive(
delivery_id: str,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
try:
path = await result_delivery_service.resolve_archive_path(
db,
delivery_id=delivery_id,
user=current_user,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return FileResponse(path, filename=os.path.basename(path), media_type="application/zip")
@router.get("/result-deliveries/{delivery_id}/files/{item_id}/download")
async def download_result_delivery_item(
delivery_id: str,
item_id: int,
db: AsyncSession = Depends(get_db),
current_user: AuthUserORM = Depends(_get_current_user),
):
try:
path = await result_delivery_service.resolve_item_path(
db,
delivery_id=delivery_id,
item_id=item_id,
user=current_user,
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
media_type = mimetypes.guess_type(path)[0] or "application/octet-stream"
return FileResponse(path, filename=os.path.basename(path), media_type=media_type)