119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
import json
|
|||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
from fastapi import Depends
|
||
|
|
from fastapi.responses import Response
|
||
|
|
from pydantic import Field
|
||
|
|
|
||
|
|
from pigway_frame_flow.core.auth import require
|
||
|
|
from pigway_frame_flow.display.thumbnail_plans import (
|
||
|
|
ThumbnailPlan,
|
||
|
|
get_plan,
|
||
|
|
render,
|
||
|
|
save_plan,
|
||
|
|
)
|
||
|
|
from pigway_frame_flow.errors import ErrorCode as E
|
||
|
|
from pigway_frame_flow.errors import FlowError
|
||
|
|
from pigway_frame_flow.models import StrictModel
|
||
|
|
|
||
|
|
|
||
|
|
class ThumbnailRequest(StrictModel):
|
||
|
|
source_id: str = Field(min_length=1, max_length=120)
|
||
|
|
item_id: str = Field(min_length=1, max_length=2048)
|
||
|
|
plan: ThumbnailPlan
|
||
|
|
|
||
|
|
|
||
|
|
def install_thumbnails(app, service):
|
||
|
|
@app.get("/api/v1/thumbnail-plans", dependencies=[Depends(require("images:read"))])
|
||
|
|
def plans():
|
||
|
|
return [
|
||
|
|
dict(
|
||
|
|
id=r["key"].removeprefix("thumbnail-plan:"), **json.loads(r["payload"])
|
||
|
|
)
|
||
|
|
for r in service.db.all(
|
||
|
|
"SELECT key,payload FROM app_config WHERE key LIKE 'thumbnail-plan:%' ORDER BY key"
|
||
|
|
)
|
||
|
|
]
|
||
|
|
|
||
|
|
@app.post("/api/v1/thumbnail-plans", dependencies=[Depends(require("admin"))])
|
||
|
|
def create(plan: ThumbnailPlan):
|
||
|
|
plan_id = uuid4().hex
|
||
|
|
save_plan(service, plan_id, plan)
|
||
|
|
return dict(id=plan_id, **plan.model_dump())
|
||
|
|
|
||
|
|
@app.get(
|
||
|
|
"/api/v1/thumbnail-plans/{plan_id}",
|
||
|
|
dependencies=[Depends(require("images:read"))],
|
||
|
|
)
|
||
|
|
def detail(plan_id: str):
|
||
|
|
return dict(id=plan_id, **get_plan(service, plan_id).model_dump())
|
||
|
|
|
||
|
|
@app.put(
|
||
|
|
"/api/v1/thumbnail-plans/{plan_id}", dependencies=[Depends(require("admin"))]
|
||
|
|
)
|
||
|
|
def update(plan_id: str, plan: ThumbnailPlan):
|
||
|
|
with service.lock:
|
||
|
|
get_plan(service, plan_id)
|
||
|
|
save_plan(service, plan_id, plan)
|
||
|
|
return dict(id=plan_id, **plan.model_dump())
|
||
|
|
|
||
|
|
@app.delete(
|
||
|
|
"/api/v1/thumbnail-plans/{plan_id}",
|
||
|
|
dependencies=[Depends(require("admin"))],
|
||
|
|
status_code=204,
|
||
|
|
)
|
||
|
|
def delete(plan_id: str):
|
||
|
|
with service.lock:
|
||
|
|
get_plan(service, plan_id)
|
||
|
|
for row in service.db.all("SELECT payload FROM jobs"):
|
||
|
|
job = json.loads(row["payload"])
|
||
|
|
if job.get("thumbnail_plan_id") == plan_id:
|
||
|
|
raise FlowError(
|
||
|
|
E.CONFLICT,
|
||
|
|
"Thumbnail plan is used by a task",
|
||
|
|
409,
|
||
|
|
{"reason": "thumbnailPlanInUse"},
|
||
|
|
)
|
||
|
|
for row in service.db.all(
|
||
|
|
"SELECT payload FROM operations WHERE status IN ('QUEUED','RENDERING','UPLOADING','UNKNOWN')"
|
||
|
|
):
|
||
|
|
if (
|
||
|
|
json.loads(row["payload"])
|
||
|
|
.get("options", {})
|
||
|
|
.get("thumbnail_plan_id")
|
||
|
|
== plan_id
|
||
|
|
):
|
||
|
|
raise FlowError(
|
||
|
|
E.CONFLICT,
|
||
|
|
"Thumbnail plan is used by a pending operation",
|
||
|
|
409,
|
||
|
|
{"reason": "thumbnailPlanInUse"},
|
||
|
|
)
|
||
|
|
service.db.execute(
|
||
|
|
"DELETE FROM app_config WHERE key=?", ("thumbnail-plan:" + plan_id,)
|
||
|
|
)
|
||
|
|
|
||
|
|
def image(request):
|
||
|
|
data, _, _ = service.photo_sources.read(
|
||
|
|
request.source_id,
|
||
|
|
request.item_id,
|
||
|
|
target_size=(request.plan.width, request.plan.height),
|
||
|
|
)
|
||
|
|
return render(data, request.plan)
|
||
|
|
|
||
|
|
@app.post(
|
||
|
|
"/api/v1/thumbnails/preview", dependencies=[Depends(require("images:read"))]
|
||
|
|
)
|
||
|
|
def preview(request: ThumbnailRequest):
|
||
|
|
return Response(
|
||
|
|
image(request),
|
||
|
|
media_type="image/jpeg",
|
||
|
|
headers={"Cache-Control": "no-store"},
|
||
|
|
)
|
||
|
|
|
||
|
|
@app.post("/api/v1/thumbnails/generate", dependencies=[Depends(require("admin"))])
|
||
|
|
def generate(request: ThumbnailRequest):
|
||
|
|
return service.assets.put(
|
||
|
|
image(request), request.plan.name + ".jpg", "generated"
|
||
|
|
)
|