"""Gallery API: cursor scroll, timeline, jump, image detail.""" from quart import Blueprint, jsonify, request from ..extensions import get_session from ..services.gallery_service import GalleryService gallery_bp = Blueprint("gallery", __name__, url_prefix="/api/gallery") @gallery_bp.route("/scroll", methods=["GET"]) async def scroll(): cursor = request.args.get("cursor") or None try: limit = int(request.args.get("limit", "50")) except ValueError: return jsonify({"error": "limit must be an integer"}), 400 tag_id_raw = request.args.get("tag_id") tag_id = int(tag_id_raw) if tag_id_raw else None post_id_raw = request.args.get("post_id") post_id = int(post_id_raw) if post_id_raw else None artist_id_raw = request.args.get("artist_id") artist_id = int(artist_id_raw) if artist_id_raw else None async with get_session() as session: svc = GalleryService(session) try: page = await svc.scroll( cursor=cursor, limit=limit, tag_id=tag_id, post_id=post_id, artist_id=artist_id, ) except ValueError as exc: return jsonify({"error": str(exc)}), 400 return jsonify( { "images": [ { "id": i.id, "sha256": i.sha256, "mime": i.mime, "width": i.width, "height": i.height, "created_at": i.created_at.isoformat(), "posted_at": i.posted_at.isoformat() if i.posted_at else None, "effective_date": i.effective_date.isoformat(), "thumbnail_url": i.thumbnail_url, "artist": i.artist, } for i in page.images ], "next_cursor": page.next_cursor, "date_groups": [ {"year": y, "month": m, "image_ids": ids} for y, m, ids in page.date_groups ], } ) @gallery_bp.route("/timeline", methods=["GET"]) async def timeline(): tag_id_raw = request.args.get("tag_id") tag_id = int(tag_id_raw) if tag_id_raw else None post_id_raw = request.args.get("post_id") post_id = int(post_id_raw) if post_id_raw else None artist_id_raw = request.args.get("artist_id") artist_id = int(artist_id_raw) if artist_id_raw else None async with get_session() as session: svc = GalleryService(session) try: buckets = await svc.timeline( tag_id=tag_id, post_id=post_id, artist_id=artist_id ) except ValueError as exc: return jsonify({"error": str(exc)}), 400 return jsonify( [{"year": b.year, "month": b.month, "count": b.count} for b in buckets] ) @gallery_bp.route("/jump", methods=["GET"]) async def jump(): try: year = int(request.args["year"]) month = int(request.args["month"]) except (KeyError, ValueError): return jsonify({"error": "year and month query params required"}), 400 tag_id_raw = request.args.get("tag_id") tag_id = int(tag_id_raw) if tag_id_raw else None post_id_raw = request.args.get("post_id") post_id = int(post_id_raw) if post_id_raw else None artist_id_raw = request.args.get("artist_id") artist_id = int(artist_id_raw) if artist_id_raw else None async with get_session() as session: svc = GalleryService(session) try: cursor = await svc.jump_cursor( year=year, month=month, tag_id=tag_id, post_id=post_id, artist_id=artist_id, ) except ValueError as exc: return jsonify({"error": str(exc)}), 400 return jsonify({"cursor": cursor}) @gallery_bp.route("/image/", methods=["GET"]) async def image_detail(image_id: int): async with get_session() as session: svc = GalleryService(session) payload = await svc.get_image_with_tags(image_id) if payload is None: return jsonify({"error": "not found"}), 404 return jsonify(payload)