From 46374d14c98f8d4dd32656557de3c364a3e5cd93 Mon Sep 17 00:00:00 2001 From: codecrafted1 Date: Sun, 28 Jun 2026 16:49:35 +0530 Subject: [PATCH 1/4] fix: use paginated repository fetch in repo explorer --- src/app/api/metrics/repo-explorer/route.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/app/api/metrics/repo-explorer/route.ts b/src/app/api/metrics/repo-explorer/route.ts index 31c34f1b1..5d9da2c5f 100644 --- a/src/app/api/metrics/repo-explorer/route.ts +++ b/src/app/api/metrics/repo-explorer/route.ts @@ -3,6 +3,7 @@ import { NextRequest } from "next/server"; import { authOptions } from "@/lib/auth"; import { isMetricsCacheBypassed, metricsCacheKey, withMetricsCache } from "@/lib/metrics-cache"; import { ExplorerRepoCardData } from "@/lib/repoAnalytics"; +import { fetchUserRepos } from "@/lib/github"; export const dynamic = "force-dynamic"; const GITHUB_API = "https://api.github.com"; @@ -16,16 +17,11 @@ export async function GET(req: NextRequest) { const bypass = isMetricsCacheBypassed(req); const key = metricsCacheKey(session.githubId ?? session.githubLogin, "repo-explorer-v2" as any, { days: 7 }); - try { - const data = await withMetricsCache({ bypass, key, ttlSeconds: 30 * 60 }, async () => { - // 1. Fetch user repos (up to 100 to show more repos) - const reposRes = await fetch(`${GITHUB_API}/user/repos?sort=pushed&per_page=100`, { - headers: { Authorization: `Bearer ${session.accessToken}`, Accept: "application/vnd.github+json" }, - cache: "no-store", - }); - - if (!reposRes.ok) throw new Error("API error fetching repos"); - const repos = await reposRes.json(); + try { + const data = await withMetricsCache( + { bypass, key, ttlSeconds: 30 * 60 }, + async () => { + const repos = await fetchUserRepos(session.accessToken); // 2. Fetch last 30 days of commits across all repos for the user const since = new Date(); @@ -94,7 +90,8 @@ export async function GET(req: NextRequest) { result.sort((a, b) => b.commitCount - a.commitCount || new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); return { repos: result }; - }); + } + ); return Response.json(data); } catch (error) { console.error(error); From 4706da5906289d73f7ca851d6a94315eaab44eae Mon Sep 17 00:00:00 2001 From: codecrafted1 Date: Thu, 2 Jul 2026 00:25:42 +0530 Subject: [PATCH 2/4] fix: resolve review issues in repo explorer pagination --- src/app/api/metrics/repo-explorer/route.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/app/api/metrics/repo-explorer/route.ts b/src/app/api/metrics/repo-explorer/route.ts index 6fb1bd120..276b0b19f 100644 --- a/src/app/api/metrics/repo-explorer/route.ts +++ b/src/app/api/metrics/repo-explorer/route.ts @@ -1,7 +1,6 @@ import { getSessionWithToken } from "@/lib/get-session-token"; import { NextRequest } from "next/server"; import { isMetricsCacheBypassed, metricsCacheKey, withMetricsCache } from "@/lib/metrics-cache"; -import { ExplorerRepoCardData } from "@/lib/repoAnalytics"; import { fetchUserRepos } from "@/lib/github"; import { ExplorerRepoCardData } from "@/lib/repo-analytics-types"; @@ -28,15 +27,7 @@ export async function GET(req: NextRequest) { async () => { const repos = await fetchUserRepos(session.accessToken); - try { - const data = await withMetricsCache({ bypass, key, ttlSeconds: 30 * 60 }, async () => { - const reposRes = await fetch(`${GITHUB_API}/user/repos?sort=pushed&per_page=100`, { - headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/vnd.github+json" }, - cache: "no-store", - }); - - if (!reposRes.ok) throw new Error("API error fetching repos"); - const repos = await reposRes.json(); + const since = new Date(); since.setDate(since.getDate() - 30); @@ -110,4 +101,4 @@ export async function GET(req: NextRequest) { console.error(error); return Response.json({ error: "GitHub API error" }, { status: 502 }); } -} \ No newline at end of file +} From 2324c98c1e6f9950831501a2195dddcae28b9b9f Mon Sep 17 00:00:00 2001 From: codecrafted1 Date: Sun, 26 Jul 2026 20:30:31 +0530 Subject: [PATCH 3/4] fix: create pending room invitations instead of direct room membership --- src/app/api/rooms/[roomId]/invite/route.ts | 30 +++++++++++++++--- src/lib/supabase-rooms.ts | 31 +++++++++++++++++-- .../20260726_create_room_invitations.sql | 13 ++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 supabase/migrations/20260726_create_room_invitations.sql diff --git a/src/app/api/rooms/[roomId]/invite/route.ts b/src/app/api/rooms/[roomId]/invite/route.ts index 4867887a9..03092b0ec 100644 --- a/src/app/api/rooms/[roomId]/invite/route.ts +++ b/src/app/api/rooms/[roomId]/invite/route.ts @@ -1,6 +1,11 @@ import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth'; -import { getRoomById, getRoomMembers, addRoomMember } from '@/lib/supabase-rooms'; +import { + getRoomById, + getRoomMembers, + createRoomInvitation, + getPendingInvitation +} from '@/lib/supabase-rooms'; import { NextResponse } from 'next/server'; export async function POST( @@ -36,6 +41,23 @@ export async function POST( const members = await getRoomMembers(roomId); if (members.some((m) => m.github_username === github_username)) return NextResponse.json({ error: 'User is already a member' }, { status: 409 }); - await addRoomMember(roomId, github_username); - return NextResponse.json({ success: true }); -} \ No newline at end of file + const existingInvite = await getPendingInvitation( + roomId, + github_username +); + +if (existingInvite) { + return NextResponse.json( + { error: "User already has a pending invitation." }, + { status: 409 } + ); +} +await createRoomInvitation( + roomId, + github_username, + session.user.name +); +return NextResponse.json({ + success: true, + message: "Invitation sent." +});} \ No newline at end of file diff --git a/src/lib/supabase-rooms.ts b/src/lib/supabase-rooms.ts index f4c4ff16f..657213608 100644 --- a/src/lib/supabase-rooms.ts +++ b/src/lib/supabase-rooms.ts @@ -35,9 +35,36 @@ export async function getRoomMembers(roomId: string): Promise { if (error) throw error; return data ?? []; } +export async function getPendingInvitation( + roomId: string, + githubUsername: string +) { + const { data, error } = await supabaseAdmin + .from("room_invitations") + .select("id") + .eq("room_id", roomId) + .eq("github_username", githubUsername) + .eq("status", "pending") + .maybeSingle(); + + if (error) throw error; + return data; +} + +export async function createRoomInvitation( + roomId: string, + githubUsername: string, + invitedBy: string +) { + const { error } = await supabaseAdmin + .from("room_invitations") + .insert({ + room_id: roomId, + github_username: githubUsername, + invited_by: invitedBy, + status: "pending", + }); -export async function addRoomMember(roomId: string, githubUsername: string) { - const { error } = await supabaseAdmin.from("room_members").insert({ room_id: roomId, github_username: githubUsername, role: "member" }); if (error) throw error; } diff --git a/supabase/migrations/20260726_create_room_invitations.sql b/supabase/migrations/20260726_create_room_invitations.sql new file mode 100644 index 000000000..6310718d4 --- /dev/null +++ b/supabase/migrations/20260726_create_room_invitations.sql @@ -0,0 +1,13 @@ +create table room_invitations ( + id uuid primary key default gen_random_uuid(), + room_id uuid references collaboration_rooms(id) on delete cascade, + github_username text not null, + invited_by text not null, + status text not null default 'pending', + created_at timestamptz default now(), + responded_at timestamptz +); + +create unique index room_invitation_unique +on room_invitations(room_id, github_username) +where status = 'pending'; \ No newline at end of file From b5f879bf1e37149fa9a6b412e877692a7b679ebe Mon Sep 17 00:00:00 2001 From: codecrafted1 Date: Sat, 1 Aug 2026 11:33:27 +0530 Subject: [PATCH 4/4] fix --- src/app/api/ai/roast/route.ts | 42 +++++++++++++++++++++++++- src/app/api/ai/weekly-summary/route.ts | 2 +- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/app/api/ai/roast/route.ts b/src/app/api/ai/roast/route.ts index 2eb468a8c..577b7b41c 100644 --- a/src/app/api/ai/roast/route.ts +++ b/src/app/api/ai/roast/route.ts @@ -1,11 +1,51 @@ import { NextResponse } from 'next/server'; import { GoogleGenerativeAI } from '@google/generative-ai'; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; +import { resolveAppUser } from "@/lib/resolve-user"; + +import { + upstashRateLimitFixedWindow, + getUpstashConfig, +} from "@/lib/upstash-rest"; + +import { createMemoryFixedWindowRateLimiter } from "@/lib/rate-limit"; // Initialize the Google Generative AI SDK const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY || ''); +const AI_ROAST_LIMIT = 5; +const AI_ROAST_WINDOW_SECONDS = 60 * 60; + +const memoryLimiter = createMemoryFixedWindowRateLimiter({ + windowMs: AI_ROAST_WINDOW_SECONDS * 1000, + pruneIntervalMs: AI_ROAST_WINDOW_SECONDS * 1000, + maxEntries: 10_000, +}); export async function POST(req: Request) { - try { + const session = await getServerSession(authOptions); +const user = await resolveAppUser( + session.githubId, + session.githubLogin +); + +if (!user) { + return NextResponse.json( + { error: "User not found" }, + { status: 404 } + ); +} + +const userId = user.id; + +if (!session?.githubId) { + return NextResponse.json( + { error: "Unauthorized" }, + { status: 401 } + ); +} + + try { const body = await req.json(); const { mode, stats } = body; diff --git a/src/app/api/ai/weekly-summary/route.ts b/src/app/api/ai/weekly-summary/route.ts index b467da6c8..4d43d4a93 100644 --- a/src/app/api/ai/weekly-summary/route.ts +++ b/src/app/api/ai/weekly-summary/route.ts @@ -10,7 +10,7 @@ * Security * -------- * - Session required — unauthenticated requests are rejected with 401. - * - ANTHROPIC_API_KEY is read only on the server; it never appears in any + * - ANTHROPIC_API_KEY isa read only on the server; it never appears in any * response or client-accessible bundle. * - Metrics are validated server-side before the prompt is built. * - String fields (topRepo) are truncated to prevent over-long inputs.