diff --git a/src/app/api/referrals/route.ts b/src/app/api/referrals/route.ts index b4bffb23..6faa2340 100644 --- a/src/app/api/referrals/route.ts +++ b/src/app/api/referrals/route.ts @@ -1,88 +1,8 @@ -import { NextRequest, NextResponse } from "next/server"; -import { getAuthContext } from "@/lib/auth/get-user"; -import { referralInviteEmail, sendEmail } from "@/lib/email"; -import { createServiceClient } from "@/lib/supabase/service"; - -type AnySupabase = any; - -// GET /api/referrals - List my referrals -export async function GET(request: NextRequest) { - try { - const auth = await getAuthContext(request); - if (!auth) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - const { user, supabase } = auth; - - const { data: referrals, error } = await (supabase as AnySupabase) - .from("referrals") - .select("*") - .eq("referrer_id", user.id) - .order("created_at", { ascending: false }); - - if (error) { - return NextResponse.json({ error: error.message }, { status: 400 }); - } - - const total = referrals?.length || 0; - const registered = referrals?.filter((r: any) => r.status !== "pending").length || 0; - - return NextResponse.json({ - data: referrals, - stats: { - total_invited: total, - total_registered: registered, - conversion_rate: total > 0 ? Math.round((registered / total) * 100) : 0, - }, - }); - } catch { - return NextResponse.json( - { error: "An unexpected error occurred" }, - { status: 500 } - ); - } -} - -// POST /api/referrals - Send invites -export async function POST(request: NextRequest) { - try { - const auth = await getAuthContext(request); - if (!auth) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - const { user, supabase } = auth; - - const body = await request.json(); - const { emails } = body; - - if (!emails || !Array.isArray(emails) || emails.length === 0) { - return NextResponse.json( - { error: "Please provide an array of emails" }, - { status: 400 } - ); - } - - if (!emails.every((email: unknown) => typeof email === "string")) { - return NextResponse.json( - { error: "All email entries must be strings" }, - { status: 400 } - ); - } - - if (emails.length > 20) { - return NextResponse.json( - { error: "Maximum 20 invites at a time" }, - { status: 400 } - ); - } - - // Validate email syntax BEFORE rate-limit checks (#143) - // Only valid emails should count toward throttle limits - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - const normalizedEmails = emails.map((e: string) => e.trim().toLowerCase()); - const validEmails = normalizedEmails.filter((e: string) => emailRegex.test(e)); +const validEmails = normalizedEmails.filter((e: string) => emailRegex.test(e)); + const userEmail = user.email?.toLowerCase(); + const filteredEmails = validEmails.filter((e: string) => e !== userEmail); - if (validEmails.length === 0) { + if (filteredEmails.length === 0) { return NextResponse.json( { error: "No valid email addresses provided" }, { status: 400 } @@ -101,7 +21,7 @@ export async function POST(request: NextRequest) { .eq("referrer_id", user.id) .gte("created_at", oneHourAgo); - if ((hourlyCount ?? 0) + validEmails.length > 10) { + if ((hourlyCount ?? 0) + filteredEmails.length > 10) { return NextResponse.json( { error: "Too many invites. Max 10 per hour." }, { status: 429 } @@ -114,7 +34,7 @@ export async function POST(request: NextRequest) { .eq("referrer_id", user.id) .gte("created_at", oneDayAgo); - if ((dailyCount ?? 0) + validEmails.length > 50) { + if ((dailyCount ?? 0) + filteredEmails.length > 50) { return NextResponse.json( { error: "Daily invite limit reached. Max 50 per day." }, { status: 429 } @@ -126,7 +46,7 @@ export async function POST(request: NextRequest) { .from("referrals") .select("referred_email") .eq("referrer_id", user.id) - .in("referred_email", validEmails); + .in("referred_email", filteredEmails); const alreadyInvited = new Set((existingInvites || []).map((r: any) => r.referred_email)); @@ -145,7 +65,7 @@ export async function POST(request: NextRequest) { const inviterName = profile.full_name || profile.username || "Someone"; // Filter valid emails that aren't already invited (#143) - const newValidEmails = validEmails.filter((e: string) => !alreadyInvited.has(e)); + const newValidEmails = filteredEmails.filter((e: string) => !alreadyInvited.has(e)); if (newValidEmails.length === 0) { return NextResponse.json(