feat(provenance): /api/provenance image + post endpoints
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ def all_blueprints() -> list[Blueprint]:
|
||||
from .gallery import gallery_bp
|
||||
from .import_admin import import_admin_bp
|
||||
from .ml_admin import ml_admin_bp
|
||||
from .provenance import provenance_bp
|
||||
from .settings import settings_bp
|
||||
from .showcase import showcase_bp
|
||||
from .suggestions import suggestions_bp
|
||||
@@ -27,6 +28,7 @@ def all_blueprints() -> list[Blueprint]:
|
||||
return [
|
||||
api_bp,
|
||||
gallery_bp,
|
||||
provenance_bp,
|
||||
tags_bp,
|
||||
artist_bp,
|
||||
showcase_bp,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
"""Provenance API: image -> posts, post -> header payload.
|
||||
|
||||
Separate from the gallery/tag APIs by design (provenance is its own
|
||||
system). Read-only.
|
||||
"""
|
||||
|
||||
from quart import Blueprint, jsonify
|
||||
|
||||
from ..extensions import get_session
|
||||
from ..services.provenance_service import ProvenanceService
|
||||
|
||||
provenance_bp = Blueprint("provenance", __name__,
|
||||
url_prefix="/api/provenance")
|
||||
|
||||
|
||||
@provenance_bp.route("/image/<int:image_id>", methods=["GET"])
|
||||
async def image_provenance(image_id: int):
|
||||
async with get_session() as session:
|
||||
svc = ProvenanceService(session)
|
||||
payload = await svc.for_image(image_id)
|
||||
if payload is None:
|
||||
return jsonify({"error": "not found"}), 404
|
||||
return jsonify(payload)
|
||||
|
||||
|
||||
@provenance_bp.route("/post/<int:post_id>", methods=["GET"])
|
||||
async def post_provenance(post_id: int):
|
||||
async with get_session() as session:
|
||||
svc = ProvenanceService(session)
|
||||
payload = await svc.for_post(post_id)
|
||||
if payload is None:
|
||||
return jsonify({"error": "not found"}), 404
|
||||
return jsonify(payload)
|
||||
@@ -0,0 +1,84 @@
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from backend.app import create_app
|
||||
from backend.app.models import (
|
||||
Artist,
|
||||
ImageProvenance,
|
||||
ImageRecord,
|
||||
Post,
|
||||
Source,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.integration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def app():
|
||||
return create_app()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client(app):
|
||||
async with app.test_client() as c:
|
||||
yield c
|
||||
|
||||
|
||||
async def _seed_full(db):
|
||||
rec = ImageRecord(
|
||||
path="/images/p/1.jpg", sha256="p" + "0" * 63,
|
||||
size_bytes=1, mime="image/jpeg", width=1, height=1,
|
||||
origin="imported_filesystem", integrity_status="unknown",
|
||||
)
|
||||
artist = Artist(name="Alice", slug="alice")
|
||||
db.add_all([rec, artist])
|
||||
await db.flush()
|
||||
source = Source(artist_id=artist.id, platform="patreon",
|
||||
url="https://patreon.test/alice")
|
||||
db.add(source)
|
||||
await db.flush()
|
||||
post = Post(source_id=source.id, external_post_id="555",
|
||||
post_url="https://patreon.test/p/555", post_title="Set 1",
|
||||
post_date=datetime(2023, 8, 1, tzinfo=UTC),
|
||||
description="<p>hi</p>", attachment_count=2)
|
||||
db.add(post)
|
||||
await db.flush()
|
||||
db.add(ImageProvenance(image_record_id=rec.id, post_id=post.id,
|
||||
source_id=source.id))
|
||||
await db.flush()
|
||||
await db.commit()
|
||||
return rec.id, post.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_image_provenance_endpoint(client, db):
|
||||
image_id, post_id = await _seed_full(db)
|
||||
resp = await client.get(f"/api/provenance/image/{image_id}")
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert body["image_id"] == image_id
|
||||
assert len(body["provenance"]) == 1
|
||||
assert body["provenance"][0]["post"]["id"] == post_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_image_provenance_404(client):
|
||||
resp = await client.get("/api/provenance/image/999999")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_provenance_endpoint(client, db):
|
||||
_, post_id = await _seed_full(db)
|
||||
resp = await client.get(f"/api/provenance/post/{post_id}")
|
||||
assert resp.status_code == 200
|
||||
body = await resp.get_json()
|
||||
assert body["post"]["id"] == post_id
|
||||
assert body["artist"]["slug"] == "alice"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_provenance_404(client):
|
||||
resp = await client.get("/api/provenance/post/999999")
|
||||
assert resp.status_code == 404
|
||||
Reference in New Issue
Block a user