"""FC-3e: /api/posts — cursor-paginated unified posts feed.""" from quart import Blueprint, jsonify, request from ..extensions import get_session from ..services.post_feed_service import PostFeedService from ..services.source_service import KNOWN_PLATFORMS from ._responses import error_response as _bad posts_bp = Blueprint("posts", __name__, url_prefix="/api/posts") @posts_bp.route("", methods=["GET"]) async def list_posts(): args = request.args cursor = args.get("cursor") or None artist_id_raw = args.get("artist_id") platform = args.get("platform") or None limit_raw = args.get("limit", "24") try: limit = int(limit_raw) except ValueError: return _bad("invalid_limit", detail="limit must be an integer") if limit < 1 or limit > 100: return _bad("invalid_limit", detail="limit must be between 1 and 100") artist_id = None if artist_id_raw is not None: try: artist_id = int(artist_id_raw) except ValueError: return _bad("invalid_artist_id", detail="artist_id must be an integer") if platform is not None and platform not in KNOWN_PLATFORMS: return _bad( "unknown_platform", detail=f"platform must be one of {sorted(KNOWN_PLATFORMS)}", ) async with get_session() as session: try: page = await PostFeedService(session).scroll( cursor=cursor, artist_id=artist_id, platform=platform, limit=limit, ) except ValueError as exc: # Service raises ValueError for malformed cursors only; # limit bounds are validated above. return _bad("invalid_cursor", detail=str(exc)) return jsonify(page) @posts_bp.route("/", methods=["GET"]) async def get_post(post_id: int): async with get_session() as session: item = await PostFeedService(session).get_post(post_id) if item is None: return _bad("not_found", status=404, detail=f"post id={post_id}") return jsonify(item)