feat: manage saved thumbnail plans and select plans for tasks
This commit is contained in:
@@ -141,6 +141,9 @@ def create_app(settings=None, start_worker=True):
|
||||
install_extensions(app, service)
|
||||
install_accounts(app, db)
|
||||
install_history(app, service)
|
||||
from pigway_frame_flow.api.thumbnails import install_thumbnails
|
||||
|
||||
install_thumbnails(app, service)
|
||||
app.state.service = service
|
||||
app.state.worker_enabled = start_worker
|
||||
app.state.scheduler = scheduler
|
||||
@@ -986,7 +989,8 @@ def create_app(settings=None, start_worker=True):
|
||||
dependencies=[Depends(require("jobs:write"))],
|
||||
)
|
||||
def create_job(payload: JobInput):
|
||||
return scheduler.save_job(payload)
|
||||
with service.lock:
|
||||
return scheduler.save_job(payload)
|
||||
|
||||
@app.put(
|
||||
"/api/v1/jobs/{job_id}",
|
||||
@@ -997,7 +1001,8 @@ def create_app(settings=None, start_worker=True):
|
||||
def update_job(job_id: str, payload: JobInput):
|
||||
if not db.one("SELECT id FROM jobs WHERE id=?", (job_id,)):
|
||||
raise FlowError(E.NOT_FOUND, "Job not found", 404)
|
||||
return scheduler.save_job(payload, job_id)
|
||||
with service.lock:
|
||||
return scheduler.save_job(payload, job_id)
|
||||
|
||||
@app.delete(
|
||||
"/api/v1/jobs/{job_id}",
|
||||
@@ -1366,6 +1371,7 @@ def create_app(settings=None, start_worker=True):
|
||||
def static(filename: str):
|
||||
if filename not in [
|
||||
"app.js",
|
||||
"thumbnails.js",
|
||||
"features.js",
|
||||
"extensions.js",
|
||||
"style.css",
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user