Spaces:
Running
Running
| 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; | |
| } | |