Skip to content

RED-107: Subreddit monitoring with RSS + AI qualification#183

Merged
aegloist merged 3 commits into
mainfrom
feature/RED-107-subreddit-monitoring
Mar 18, 2026
Merged

RED-107: Subreddit monitoring with RSS + AI qualification#183
aegloist merged 3 commits into
mainfrom
feature/RED-107-subreddit-monitoring

Conversation

@aegloist
Copy link
Copy Markdown
Owner

Summary

New feature: monitor subreddits for relevant posts and draft AI reply comments.

How it works:

  1. User adds subreddits to monitor (linked to a project)
  2. Cloudflare Worker cron fetches RSS every 15 min
  3. New posts stored in DB, deduped by Reddit post ID
  4. AI (gpt-4.1-mini) scores each post for relevance to user's product
  5. User sees qualified posts in dashboard, clicks "Draft reply"
  6. AI (gpt-5.3) generates a natural comment, user copies to Reddit

No Reddit API needed — uses RSS feeds which are free and unblocked.

Changes

  • prisma/schema.prisma: 2 new tables (MonitoredSubreddit, MonitoredPost)
  • src/lib/subreddit/rssFeed.ts: RSS feed parser
  • src/lib/monitor/qualifyPost.ts: AI relevance scorer
  • src/app/api/monitor/run/route.ts: Cron endpoint
  • src/app/api/monitor/subreddits/route.ts: CRUD for monitored subs
  • src/app/api/monitor/posts/route.ts: List qualified posts
  • src/app/api/monitor/posts/[id]/draft-reply/route.ts: AI comment drafter
  • src/app/(app)/monitor/page.tsx: Dashboard UI
  • src/components/app/navConfig.ts: Added Monitor to sidebar

Deploy steps

  1. Push DB migration: prisma db push on production
  2. Deploy Cloudflare Worker with cron trigger
  3. Add MONITOR_CRON_SECRET env var to both Vercel and Cloudflare

Test Plan

  • All 468 unit tests pass
  • TypeScript compiles cleanly

🤖 Generated with Claude Code

- Add MonitoredSubreddit + MonitoredPost tables (Prisma schema)
- RSS feed parser for subreddit new posts (no API needed)
- AI post qualifier using gpt-4.1-mini (scores relevance 0-100)
- Cron endpoint POST /api/monitor/run (called by Cloudflare Worker every 15min)
- API: GET/POST /api/monitor/subreddits (manage monitored subreddits)
- API: GET /api/monitor/posts (list qualified posts)
- API: POST /api/monitor/posts/[id]/draft-reply (AI comment generation)
- Dashboard page at /monitor with post list + draft reply UI
- Added Monitor to sidebar navigation
- Cloudflare Worker updated with cron trigger (not yet deployed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 18, 2026 08:39
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openfast Ready Ready Preview, Comment Mar 18, 2026 5:46pm

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements subreddit monitoring via Reddit RSS feeds, storing discovered posts and using OpenAI to qualify relevance and generate draft reply comments, surfaced through a new “Monitor” dashboard section.

Changes:

  • Adds Prisma models for monitored subreddits and monitored posts, including relevance scoring and draft reply persistence.
  • Introduces RSS fetch/parsing, cron-driven ingestion + AI qualification, and workspace-scoped APIs to list/add subreddits and list/draft posts.
  • Adds a new Monitor UI page and navigation entry.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
prisma/schema.prisma Adds MonitoredSubreddit + MonitoredPost tables and relations/indexes for monitoring storage.
src/lib/subreddit/rssFeed.ts Fetches and parses subreddit RSS (Atom) into normalized post objects.
src/lib/monitor/qualifyPost.ts Calls OpenAI to score post relevance (0–100) with a short rationale.
src/app/api/monitor/run/route.ts Cron endpoint: fetch RSS, dedupe/store posts, and qualify unscored posts with AI.
src/app/api/monitor/subreddits/route.ts Workspace-scoped list/add (reactivate) monitored subreddits.
src/app/api/monitor/posts/route.ts Workspace-scoped list of qualified monitored posts (minScore filter).
src/app/api/monitor/posts/[id]/draft-reply/route.ts Generates and persists an AI draft reply for a monitored post.
src/app/(app)/monitor/page.tsx Dashboard UI for viewing qualified posts, adding subreddits, and drafting replies.
src/components/app/navConfig.ts Adds “Monitor” entry to the app sidebar nav.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +13 to +20
export async function GET(req: Request) {
const ctx = await requireWorkspaceSession();
const { searchParams } = new URL(req.url);
const minScore = Math.max(0, Number(searchParams.get("minScore") ?? 40));

const posts = await prisma.monitoredPost.findMany({
where: {
monitoredSubreddit: { workspaceId: ctx.workspaceId },
Comment on lines +92 to +101
// Qualify unscored posts with AI (batch limit to control cost)
const unscored = await prisma.monitoredPost.findMany({
where: { relevanceScore: null },
take: MAX_QUALIFY_PER_RUN,
include: {
monitoredSubreddit: {
include: {
project: { select: { name: true, description: true } },
},
},
Comment thread src/app/(app)/monitor/page.tsx Outdated
Comment on lines +70 to +76
// Need a project — use the first one for now
// In a real UI we'd let user pick
const res = await fetch("/api/monitor/subreddits", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ subreddit: newSub.trim() }),
});
Comment on lines +15 to +22
const { searchParams } = new URL(req.url);
const minScore = Math.max(0, Number(searchParams.get("minScore") ?? 40));

const posts = await prisma.monitoredPost.findMany({
where: {
monitoredSubreddit: { workspaceId: ctx.workspaceId },
relevanceScore: { gte: minScore },
},
Comment on lines +29 to +37
// Get all active monitored subreddits with their project info
const monitored = await prisma.monitoredSubreddit.findMany({
where: { isActive: true },
take: MAX_SUBREDDITS_PER_RUN,
include: {
project: {
select: { name: true, description: true },
},
},
Comment on lines +21 to +31
export async function POST(req: Request) {
// Auth: only allow calls with the correct secret
const authHeader = req.headers.get("authorization") ?? "";
const secret = process.env.MONITOR_CRON_SECRET;
if (!secret || authHeader !== `Bearer ${secret}`) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Get all active monitored subreddits with their project info
const monitored = await prisma.monitoredSubreddit.findMany({
where: { isActive: true },
Comment on lines +22 to +31
export async function GET() {
const ctx = await requireWorkspaceSession();

const subs = await prisma.monitoredSubreddit.findMany({
where: { workspaceId: ctx.workspaceId },
include: {
project: { select: { name: true } },
_count: { select: { posts: true } },
},
orderBy: { createdAt: "desc" },
Comment on lines +47 to +67
const raw = await generateChatText({
systemPrompt: SYSTEM_PROMPT,
userPrompt,
feature: "risk-scoring", // uses gpt-4.1-mini
maxTokens: 150,
});

if (!raw) return { score: 0, reason: "AI unavailable" };

try {
const parsed = JSON.parse(raw) as { score?: number; reason?: string };
return {
score:
typeof parsed.score === "number" && Number.isFinite(parsed.score)
? Math.max(0, Math.min(100, Math.round(parsed.score)))
: 0,
reason:
typeof parsed.reason === "string" ? parsed.reason.slice(0, 200) : "",
};
} catch {
return { score: 0, reason: "Failed to parse AI response" };
Comment on lines +25 to +44
export async function POST(
req: Request,
{ params }: { params: { id: string } },
) {
const ctx = await requireWorkspaceSession();
const postId = params.id;

// Get the post + its project context
const post = await prisma.monitoredPost.findUnique({
where: { id: postId },
include: {
monitoredSubreddit: {
include: {
project: {
select: { name: true, description: true, workspaceId: true },
},
},
},
},
});
},
},
},
orderBy: { discoveredAt: "desc" }, // newest first
- Remove Approvals and Scheduling from sidebar nav
- Onboarding: 3 steps → 2 steps (removed Connect Reddit)
- Create project now redirects to roadmap (skips connect-reddit)
- Roadmap no longer requires Reddit account to proceed
- All OAuth/scheduling code kept but unreachable (not deleted)
- Updated onboarding guidance hints to reference Monitor instead of scheduling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix params pattern on draft-reply route (Promise<{id}> with await)
- Frontend: add project selector, send projectId when adding subreddit
- Frontend: add loading state on Add button to prevent double-click
- Cron: parallel RSS fetches in batches of 5 (was sequential, risked Vercel timeout)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aegloist aegloist merged commit 92d847a into main Mar 18, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants