From aa755a03332e37de1a859d4f5322e0165ef30fc9 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Tue, 11 Nov 2025 23:24:31 +0000 Subject: [PATCH] Artist verification API endpoints cgen-79bdd75d2bc741f2b26acc1636d8122d --- api/ethos/verification.ts | 275 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 api/ethos/verification.ts diff --git a/api/ethos/verification.ts b/api/ethos/verification.ts new file mode 100644 index 00000000..648d8cae --- /dev/null +++ b/api/ethos/verification.ts @@ -0,0 +1,275 @@ +import { createClient } from "@supabase/supabase-js"; +import { sendEmail } from "@/server/email"; + +const supabase = createClient( + process.env.VITE_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +interface VerificationRequest { + id: string; + user_id: string; + artist_profile_id: string; + status: "pending" | "approved" | "rejected"; + submitted_at: string; + reviewed_at?: string; + reviewed_by?: string; + rejection_reason?: string; + submission_notes?: string; + portfolio_links?: string[]; + user_profiles?: { + full_name: string; + email: string; + avatar_url?: string; + }; + ethos_artist_profiles?: { + bio: string; + skills: string[]; + for_hire: boolean; + sample_price_track?: number; + }; +} + +export default async function handler(req: any, res: any) { + const { method, query, body } = req; + + try { + if (method === "GET") { + // Get verification requests (admin only) + const { status = "pending", limit = 20, offset = 0 } = query; + const authUser = req.headers["x-user-id"]; + + if (!authUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + + // Check if user is admin + const { data: adminCheck } = await supabase + .from("user_profiles") + .select("is_admin") + .eq("id", authUser) + .single(); + + if (!adminCheck?.is_admin) { + return res.status(403).json({ error: "Only admins can view verification requests" }); + } + + const query_builder = supabase + .from("ethos_verification_requests") + .select( + ` + id, + user_id, + artist_profile_id, + status, + submitted_at, + reviewed_at, + reviewed_by, + rejection_reason, + submission_notes, + portfolio_links, + user_profiles:user_id(full_name, email, avatar_url), + ethos_artist_profiles:artist_profile_id(bio, skills, for_hire, sample_price_track) + `, + { count: "exact" }, + ) + .eq("status", status) + .order("submitted_at", { ascending: false }) + .range(offset, offset + limit - 1); + + const { data, count, error } = await query_builder; + + if (error) throw error; + + return res.status(200).json({ data, total: count }); + } + + if (method === "POST") { + const { action, request_id, rejection_reason, submission_notes, portfolio_links } = body; + const authUser = req.headers["x-user-id"]; + + if (!authUser) { + return res.status(401).json({ error: "Unauthorized" }); + } + + if (action === "submit") { + // Artist submits for verification + const { data: existingRequest } = await supabase + .from("ethos_verification_requests") + .select("id") + .eq("user_id", authUser) + .eq("status", "pending") + .single(); + + if (existingRequest) { + return res.status(400).json({ error: "You already have a pending verification request" }); + } + + // Create verification request + const { data: request, error: requestError } = await supabase + .from("ethos_verification_requests") + .insert({ + user_id: authUser, + artist_profile_id: authUser, + status: "pending", + submission_notes, + portfolio_links, + }) + .select() + .single(); + + if (requestError) throw requestError; + + // Log the submission + await supabase + .from("ethos_verification_audit_log") + .insert({ + request_id: request.id, + action: "submitted", + actor_id: authUser, + notes: "Artist submitted verification request", + }); + + return res.status(201).json({ data: request }); + } + + if (action === "approve") { + // Admin approves artist + const { data: adminCheck } = await supabase + .from("user_profiles") + .select("is_admin") + .eq("id", authUser) + .single(); + + if (!adminCheck?.is_admin) { + return res.status(403).json({ error: "Only admins can approve verification" }); + } + + const { data: request, error: updateError } = await supabase + .from("ethos_verification_requests") + .update({ + status: "approved", + reviewed_at: new Date().toISOString(), + reviewed_by: authUser, + }) + .eq("id", request_id) + .select() + .single(); + + if (updateError) throw updateError; + + // Update artist profile to verified + await supabase + .from("ethos_artist_profiles") + .update({ verified: true }) + .eq("user_id", request.user_id); + + // Log the approval + await supabase + .from("ethos_verification_audit_log") + .insert({ + request_id, + action: "approved", + actor_id: authUser, + notes: "Artist verified by admin", + }); + + // Send verification email + const { data: userData } = await supabase + .from("user_profiles") + .select("email, full_name") + .eq("id", request.user_id) + .single(); + + if (userData?.email) { + await sendEmail({ + to: userData.email, + subject: "Your Ethos Guild Artist Verification - Approved! 🎵", + html: ` +

Welcome to the Ethos Guild, ${userData.full_name}!

+

Congratulations! Your artist verification has been approved.

+

You can now:

+ +

Go to your artist settings to start uploading.

+ `, + }); + } + + return res.status(200).json({ data: request }); + } + + if (action === "reject") { + // Admin rejects artist + const { data: adminCheck } = await supabase + .from("user_profiles") + .select("is_admin") + .eq("id", authUser) + .single(); + + if (!adminCheck?.is_admin) { + return res.status(403).json({ error: "Only admins can reject verification" }); + } + + const { data: request, error: updateError } = await supabase + .from("ethos_verification_requests") + .update({ + status: "rejected", + reviewed_at: new Date().toISOString(), + reviewed_by: authUser, + rejection_reason, + }) + .eq("id", request_id) + .select() + .single(); + + if (updateError) throw updateError; + + // Log the rejection + await supabase + .from("ethos_verification_audit_log") + .insert({ + request_id, + action: "rejected", + actor_id: authUser, + notes: rejection_reason, + }); + + // Send rejection email + const { data: userData } = await supabase + .from("user_profiles") + .select("email, full_name") + .eq("id", request.user_id) + .single(); + + if (userData?.email) { + await sendEmail({ + to: userData.email, + subject: "Ethos Guild Artist Verification - Application Decision", + html: ` +

Ethos Guild Artist Verification

+

Thank you for your interest in the Ethos Guild.

+

Unfortunately, your application was not approved at this time.

+ ${rejection_reason ? `

Feedback: ${rejection_reason}

` : ""} +

You're welcome to reapply with updates to your portfolio or qualifications.

+

Learn more about the Ethos Guild

+ `, + }); + } + + return res.status(200).json({ data: request }); + } + + return res.status(400).json({ error: "Invalid action" }); + } + + return res.status(405).json({ error: "Method not allowed" }); + } catch (error: any) { + console.error("Verification error:", error); + return res.status(500).json({ error: error.message }); + } +}