Move favorites to local library and keep confirmed task heads in cloud
This commit is contained in:
+117
-10
@@ -28,7 +28,9 @@ from pigway_frame_flow.integrations.kokonna_official.connection import (
|
||||
from pigway_frame_flow.models import (
|
||||
Accepted,
|
||||
Asset,
|
||||
AssetBatch,
|
||||
CurrentDisplay,
|
||||
DeleteRequest,
|
||||
Device,
|
||||
DisplayRequest,
|
||||
ErrorResponse,
|
||||
@@ -311,10 +313,24 @@ def create_app(settings=None, start_worker=True):
|
||||
dependencies=[Depends(require("images:read"))],
|
||||
)
|
||||
def images(device_id: str):
|
||||
return [
|
||||
image.model_copy(update=service.retention.info(device_id, image.image_id))
|
||||
for image in registry.get(device_id).images()
|
||||
]
|
||||
result = []
|
||||
for image in registry.get(device_id).images():
|
||||
source = db.one(
|
||||
"SELECT asset_id FROM image_sources WHERE device_id=? AND image_id=?",
|
||||
(device_id, image.image_id),
|
||||
)
|
||||
meta = (
|
||||
db.one(
|
||||
"SELECT starred FROM asset_metadata WHERE asset_id=?",
|
||||
(source["asset_id"],),
|
||||
)
|
||||
if source
|
||||
else None
|
||||
)
|
||||
result.append(
|
||||
image.model_copy(update={"starred": bool(meta and meta["starred"])})
|
||||
)
|
||||
return result
|
||||
|
||||
@app.put(
|
||||
"/api/v1/devices/{device_id}/images/{image_id}/star",
|
||||
@@ -323,7 +339,41 @@ def create_app(settings=None, start_worker=True):
|
||||
)
|
||||
def star_image(device_id: str, image_id: int, payload: ImageStar):
|
||||
registry.get(device_id)
|
||||
return service.retention.star(device_id, image_id, payload.starred)
|
||||
row = db.one(
|
||||
"SELECT asset_id FROM image_sources WHERE device_id=? AND image_id=?",
|
||||
(device_id, image_id),
|
||||
)
|
||||
if not row or not row["asset_id"]:
|
||||
raise FlowError(
|
||||
E.UNSUPPORTED_CAPABILITY, "Star images in the local library", 409
|
||||
)
|
||||
return star_asset(row["asset_id"], payload)
|
||||
|
||||
@app.post(
|
||||
"/api/v1/devices/{device_id}/images/clear",
|
||||
tags=["Images"],
|
||||
dependencies=[Depends(require("admin"))],
|
||||
)
|
||||
def clear_cloud(device_id: str, payload: DeleteRequest, request: Request):
|
||||
if not payload.confirm_current:
|
||||
raise FlowError(
|
||||
E.INVALID_REQUEST,
|
||||
"Explicit current-image deletion confirmation required",
|
||||
)
|
||||
write_guard(device_id)
|
||||
ids = [image.image_id for image in registry.get(device_id).images()]
|
||||
return {
|
||||
"operations": [
|
||||
submit(
|
||||
request,
|
||||
"delete",
|
||||
{"image_id": id, "confirm_current": True, "purge": True},
|
||||
device_id,
|
||||
request.state.request_id + "-" + str(id),
|
||||
)
|
||||
for id in ids
|
||||
]
|
||||
}
|
||||
|
||||
@app.get(
|
||||
"/api/v1/devices/{device_id}/images/{image_id}/file",
|
||||
@@ -496,7 +546,11 @@ def create_app(settings=None, start_worker=True):
|
||||
settings.sanitize(name),
|
||||
kind="cache",
|
||||
)
|
||||
return asset
|
||||
db.execute(
|
||||
"INSERT INTO asset_metadata(asset_id,origin) VALUES(?,?)",
|
||||
(asset["asset_id"], "manual"),
|
||||
)
|
||||
return assets.info(asset["asset_id"])
|
||||
|
||||
@app.get(
|
||||
"/api/v1/assets",
|
||||
@@ -507,14 +561,20 @@ def create_app(settings=None, start_worker=True):
|
||||
def list_assets(
|
||||
limit: int = Query(default=100, ge=1, le=200),
|
||||
source: Literal["all", "uploaded", "generated"] = "all",
|
||||
origin: Literal[
|
||||
"all", "manual", "task", "external", "template", "unknown"
|
||||
] = "all",
|
||||
job_id: str | None = None,
|
||||
offset: int = Query(default=0, ge=0),
|
||||
):
|
||||
return [
|
||||
assets.info(r["id"])
|
||||
for r in db.all(
|
||||
"SELECT id FROM assets WHERE kind!='cache' AND "
|
||||
"SELECT assets.id FROM assets LEFT JOIN asset_metadata ON asset_metadata.asset_id=assets.id WHERE kind!='cache' AND "
|
||||
"(?='all' OR (?='uploaded' AND kind='images') OR (?='generated' AND kind='generated')) "
|
||||
"ORDER BY created_at DESC LIMIT ?",
|
||||
(source, source, source, limit),
|
||||
"AND (?='all' OR COALESCE(origin,'unknown')=?) AND (? IS NULL OR job_id=?) "
|
||||
"ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
||||
(source, source, source, origin, origin, job_id, job_id, limit, offset),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -544,9 +604,51 @@ def create_app(settings=None, start_worker=True):
|
||||
409,
|
||||
)
|
||||
path.unlink(missing_ok=True)
|
||||
db.execute("DELETE FROM asset_metadata WHERE asset_id=?", (asset_id,))
|
||||
db.execute("DELETE FROM asset_sources WHERE asset_id=?", (asset_id,))
|
||||
db.execute("DELETE FROM assets WHERE id=?", (asset_id,))
|
||||
return Response(status_code=204)
|
||||
|
||||
@app.put(
|
||||
"/api/v1/assets/{asset_id}/star",
|
||||
response_model=Asset,
|
||||
tags=["Images"],
|
||||
dependencies=[Depends(require("images:write"))],
|
||||
)
|
||||
def star_asset(asset_id: str, payload: ImageStar):
|
||||
with service.lock:
|
||||
assets.info(asset_id)
|
||||
db.execute(
|
||||
"INSERT INTO asset_metadata(asset_id,starred) VALUES(?,?) ON CONFLICT(asset_id) DO UPDATE SET starred=excluded.starred",
|
||||
(asset_id, int(payload.starred)),
|
||||
)
|
||||
db.execute(
|
||||
"UPDATE image_retention SET starred=? WHERE (device_id,image_id) IN (SELECT device_id,image_id FROM image_sources WHERE asset_id=?)",
|
||||
(int(payload.starred), asset_id),
|
||||
)
|
||||
return assets.info(asset_id)
|
||||
|
||||
@app.post(
|
||||
"/api/v1/assets/batch",
|
||||
tags=["Images"],
|
||||
dependencies=[Depends(require("images:delete"))],
|
||||
)
|
||||
def batch_assets(payload: AssetBatch):
|
||||
results = []
|
||||
with service.lock:
|
||||
for id in dict.fromkeys(payload.asset_ids):
|
||||
try:
|
||||
if payload.action == "delete":
|
||||
delete_asset(id)
|
||||
else:
|
||||
star_asset(id, ImageStar(starred=payload.action == "star"))
|
||||
results.append({"asset_id": id, "status": "completed"})
|
||||
except FlowError as exc:
|
||||
results.append(
|
||||
{"asset_id": id, "status": "failed", "code": exc.code}
|
||||
)
|
||||
return {"results": results}
|
||||
|
||||
@app.get(
|
||||
"/api/v1/assets/{asset_id}",
|
||||
tags=["Images"],
|
||||
@@ -1031,13 +1133,18 @@ def create_app(settings=None, start_worker=True):
|
||||
),
|
||||
)
|
||||
service.validate(payload)
|
||||
return submit(
|
||||
accepted = submit(
|
||||
request,
|
||||
"display",
|
||||
payload.model_dump(mode="json"),
|
||||
job.device_id,
|
||||
idempotency_key,
|
||||
)
|
||||
db.execute(
|
||||
"INSERT OR IGNORE INTO job_runs VALUES(?,?,?)",
|
||||
(job_id, "manual-" + accepted.operation_id, accepted.operation_id),
|
||||
)
|
||||
return accepted
|
||||
|
||||
@app.post(
|
||||
"/api/v1/jobs/{job_id}/preview",
|
||||
|
||||
Reference in New Issue
Block a user