magpie/app/api/endpoints/posts.py
Alan Weinstock dfdde692b8 feat(engagement): poll Reddit post metrics after posting (#6)
- Add RedditClient.fetch_stats() — fetches score/upvotes/comments/awards via by_id API
- Add Store.list_posts_needing_poll() — selects successful Reddit posts not checked within recheck window
- Add Store.list_posts() LEFT JOIN latest engagement snapshot (avoids N+1 on frontend)
- Add app/services/engagement.py — poll_recent_posts() async service with unauthenticated fallback
- Register hourly engagement poll job in APScheduler at startup
- Add POST /posts/poll-engagement for manual triggers
- Update Post interface with engagement fields (score, comment_count, awards, engagement_checked_at)
- Add Score/Comments columns and poll button to PostsView

Closes: #6
2026-06-13 22:02:07 -07:00

58 lines
1.7 KiB
Python

from __future__ import annotations
import asyncio
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from app.core.config import get_settings
from app.db.store import Store
from app.services.poster import post_campaign_to_sub
router = APIRouter(prefix="/posts", tags=["posts"])
def _in_thread(fn):
store = Store(get_settings().db_path)
try:
return fn(store)
finally:
store.close()
class PostToSub(BaseModel):
campaign_id: int
sub: str
@router.get("")
async def list_posts(campaign_id: int | None = None, target: str | None = None, limit: int = 50):
return await asyncio.to_thread(
_in_thread, lambda s: s.list_posts(campaign_id, target, limit)
)
@router.post("/trigger")
async def trigger_single_post(body: PostToSub):
"""Manually trigger a single post to one sub."""
campaign = await asyncio.to_thread(_in_thread, lambda s: s.get_campaign(body.campaign_id))
if campaign is None:
raise HTTPException(404, "Campaign not found")
return await post_campaign_to_sub(body.campaign_id, body.sub, triggered_by="manual")
@router.get("/{post_id}/engagement")
async def get_engagement(post_id: int):
result = await asyncio.to_thread(_in_thread, lambda s: s.get_latest_engagement(post_id))
if result is None:
raise HTTPException(404, "No engagement data for this post")
return result
@router.post("/poll-engagement")
async def poll_engagement():
"""Manually trigger an engagement poll for all recent posts."""
from app.services.engagement import poll_recent_posts
settings = get_settings()
result = await poll_recent_posts(settings.db_path)
return result