import { supabaseAdmin } from "../config/supabase"; import { HttpError } from "../utils/httpError"; interface Candidate { candidate_profile_id: string; compatibility_score: number; shared_traits: string[]; caution_notes: string[]; } function orderedPair(a: string, b: string): [string, string] { return a < b ? [a, b] : [b, a]; } export async function listMyMatches(userId: string) { const { data: profile, error: profileError } = await supabaseAdmin .from("profiles") .select("id") .eq("user_id", userId) .maybeSingle(); if (profileError) { throw profileError; } if (!profile) { return []; } const { data, error } = await supabaseAdmin .from("matches") .select(` id, compatibility_score, shared_traits, caution_notes, profile_a_accepted, profile_b_accepted, status, profile_a_id, profile_b_id, profile_a:profiles!matches_profile_a_id_fkey(id, display_name, age, location, narrative_summary), profile_b:profiles!matches_profile_b_id_fkey(id, display_name, age, location, narrative_summary) `) .or(`profile_a_id.eq.${profile.id},profile_b_id.eq.${profile.id}`) .order("compatibility_score", { ascending: false }); if (error) { throw error; } return (data ?? []).map((match) => { const isA = match.profile_a_id === profile.id; return { id: match.id, compatibility_score: match.compatibility_score, shared_traits: match.shared_traits, caution_notes: match.caution_notes, status: match.status, my_accepted: isA ? match.profile_a_accepted : match.profile_b_accepted, their_accepted: isA ? match.profile_b_accepted : match.profile_a_accepted, profile: isA ? match.profile_b : match.profile_a }; }); } export async function curateMatches(userId: string) { const { data: profile, error: profileError } = await supabaseAdmin .from("profiles") .select("id, profile_complete, paid_entry") .eq("user_id", userId) .maybeSingle(); if (profileError) { throw profileError; } if (!profile) { throw new HttpError(409, "profile_required", "Create and complete a profile before curating matches"); } if (!profile.profile_complete) { throw new HttpError(422, "profile_incomplete", "Complete the full questionnaire before curating matches"); } if (!profile.paid_entry) { throw new HttpError(402, "entry_fee_required", "The one-time entry fee is required before match curation"); } const { data: candidates, error: rpcError } = await supabaseAdmin.rpc("match_candidates", { source_profile_id: profile.id, match_count: 10 }); if (rpcError) { throw rpcError; } for (const candidate of (candidates ?? []) as Candidate[]) { const [profileAId, profileBId] = orderedPair(profile.id, candidate.candidate_profile_id); const { error } = await supabaseAdmin.from("matches").upsert( { profile_a_id: profileAId, profile_b_id: profileBId, compatibility_score: candidate.compatibility_score, shared_traits: candidate.shared_traits, caution_notes: candidate.caution_notes }, { onConflict: "profile_a_id,profile_b_id" } ); if (error) { throw error; } } return listMyMatches(userId); } export async function acceptMatch(userId: string, matchId: string) { const { data: profile, error: profileError } = await supabaseAdmin .from("profiles") .select("id") .eq("user_id", userId) .maybeSingle(); if (profileError) { throw profileError; } if (!profile) { throw new HttpError(404, "profile_not_found", "Profile not found"); } const { data: match, error: matchError } = await supabaseAdmin .from("matches") .select("*") .eq("id", matchId) .or(`profile_a_id.eq.${profile.id},profile_b_id.eq.${profile.id}`) .single(); if (matchError || !match) { throw new HttpError(404, "match_not_found", "Match not found"); } const isProfileA = match.profile_a_id === profile.id; const profileAAccepted = isProfileA ? true : match.profile_a_accepted; const profileBAccepted = isProfileA ? match.profile_b_accepted : true; const nextStatus = profileAAccepted && profileBAccepted ? "active" : "accepted"; const { data, error } = await supabaseAdmin .from("matches") .update({ profile_a_accepted: profileAAccepted, profile_b_accepted: profileBAccepted, status: nextStatus }) .eq("id", matchId) .select("*") .single(); if (error) { throw error; } if (nextStatus === "active") { const { error: conversationError } = await supabaseAdmin .from("conversations") .upsert({ match_id: matchId }, { onConflict: "match_id" }); if (conversationError) { throw conversationError; } } return data; }