diff --git a/api/ethos/artists.ts b/api/ethos/artists.ts new file mode 100644 index 00000000..f67ee5a8 --- /dev/null +++ b/api/ethos/artists.ts @@ -0,0 +1,134 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient( + process.env.VITE_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +export default async function handler(req: any, res: any) { + const { method, query, body, headers } = req; + const userId = headers["x-user-id"]; + + try { + if (method === "GET") { + const artistId = query.id; + + if (artistId) { + const { data: artist, error: artistError } = await supabase + .from("ethos_artist_profiles") + .select( + ` + user_id, + skills, + for_hire, + bio, + portfolio_url, + sample_price_track, + sample_price_sfx, + sample_price_score, + turnaround_days, + verified, + total_downloads, + created_at, + user_profiles(id, full_name, avatar_url, email) + `, + ) + .eq("user_id", artistId) + .single(); + + if (artistError && artistError.code !== "PGRST116") throw artistError; + + if (!artist) { + return res.status(404).json({ error: "Artist not found" }); + } + + const { data: tracks } = await supabase + .from("ethos_tracks") + .select("*") + .eq("user_id", artistId) + .eq("is_published", true) + .order("created_at", { ascending: false }); + + res.json({ + ...artist, + tracks: tracks || [], + }); + } else { + const { limit = 50, offset = 0, verified, forHire } = query; + + let dbQuery = supabase.from("ethos_artist_profiles").select( + ` + user_id, + skills, + for_hire, + bio, + portfolio_url, + sample_price_track, + sample_price_sfx, + sample_price_score, + turnaround_days, + verified, + total_downloads, + created_at, + user_profiles(id, full_name, avatar_url) + `, + { count: "exact" }, + ); + + if (verified === "true") dbQuery = dbQuery.eq("verified", true); + if (forHire === "true") dbQuery = dbQuery.eq("for_hire", true); + + const { data, error, count } = await dbQuery + .order("verified", { ascending: false }) + .order("total_downloads", { ascending: false }) + .range(Number(offset), Number(offset) + Number(limit) - 1); + + if (error) throw error; + + res.json({ + data, + total: count, + limit: Number(limit), + offset: Number(offset), + }); + } + } else if (method === "PUT") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { + skills, + for_hire, + bio, + portfolio_url, + sample_price_track, + sample_price_sfx, + sample_price_score, + turnaround_days, + } = body; + + const { data, error } = await supabase + .from("ethos_artist_profiles") + .upsert( + { + user_id: userId, + skills: skills || [], + for_hire: for_hire !== false, + bio, + portfolio_url, + sample_price_track, + sample_price_sfx, + sample_price_score, + turnaround_days, + }, + { onConflict: "user_id" }, + ) + .select(); + + if (error) throw error; + res.json(data[0]); + } + } catch (err: any) { + console.error("[Ethos Artists]", err); + res.status(500).json({ error: err.message }); + } +} diff --git a/api/ethos/licensing-agreements.ts b/api/ethos/licensing-agreements.ts new file mode 100644 index 00000000..d31f612d --- /dev/null +++ b/api/ethos/licensing-agreements.ts @@ -0,0 +1,114 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient( + process.env.VITE_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +export default async function handler(req: any, res: any) { + const { method, query, body, headers } = req; + const userId = headers["x-user-id"]; + + try { + if (method === "GET") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { trackId, status = "pending" } = query; + + let dbQuery = supabase + .from("ethos_licensing_agreements") + .select( + ` + id, + track_id, + licensee_id, + license_type, + agreement_url, + approved, + created_at, + expires_at, + ethos_tracks(title, user_id), + user_profiles(full_name, avatar_url) + `, + { count: "exact" }, + ); + + if (trackId) { + dbQuery = dbQuery.eq("track_id", trackId); + } + + if (status === "pending") { + dbQuery = dbQuery.eq("approved", false); + } else if (status === "approved") { + dbQuery = dbQuery.eq("approved", true); + } + + const { data, error, count } = await dbQuery.order("created_at", { + ascending: false, + }); + + if (error) throw error; + + res.json({ + data, + total: count, + }); + } else if (method === "POST") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { track_id, license_type, agreement_url, expires_at } = body; + + if (!track_id || !license_type) { + return res.status(400).json({ + error: "Missing required fields: track_id, license_type", + }); + } + + const { data, error } = await supabase + .from("ethos_licensing_agreements") + .insert([ + { + track_id, + licensee_id: userId, + license_type, + agreement_url, + expires_at, + approved: false, + }, + ]) + .select(); + + if (error) throw error; + res.status(201).json(data[0]); + } else if (method === "PUT") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { id } = query; + const { approved } = body; + + const { data, error } = await supabase + .from("ethos_licensing_agreements") + .update({ approved }) + .eq("id", id) + .select(); + + if (error) throw error; + res.json(data[0]); + } else if (method === "DELETE") { + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { id } = query; + + const { error } = await supabase + .from("ethos_licensing_agreements") + .delete() + .eq("id", id); + + if (error) throw error; + res.json({ ok: true }); + } + } catch (err: any) { + console.error("[Ethos Licensing]", err); + res.status(500).json({ error: err.message }); + } +} diff --git a/api/ethos/tracks.ts b/api/ethos/tracks.ts new file mode 100644 index 00000000..1b3f7946 --- /dev/null +++ b/api/ethos/tracks.ts @@ -0,0 +1,114 @@ +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient( + process.env.VITE_SUPABASE_URL || "", + process.env.SUPABASE_SERVICE_ROLE || "", +); + +interface TrackFilters { + genre?: string; + licenseType?: string; + artist?: string; + search?: string; + published?: boolean; +} + +export default async function handler(req: any, res: any) { + const { method, query, body } = req; + + try { + if (method === "GET") { + const { + limit = 50, + offset = 0, + genre, + licenseType, + artist, + search, + } = query; + + let dbQuery = supabase + .from("ethos_tracks") + .select( + ` + id, + user_id, + title, + description, + file_url, + duration_seconds, + genre, + license_type, + bpm, + is_published, + download_count, + created_at, + updated_at, + user_profiles(id, full_name, avatar_url) + `, + { count: "exact" }, + ) + .eq("is_published", true) + .order("created_at", { ascending: false }); + + if (genre) dbQuery = dbQuery.contains("genre", [genre]); + if (licenseType) dbQuery = dbQuery.eq("license_type", licenseType); + if (search) dbQuery = dbQuery.or(`title.ilike.%${search}%,description.ilike.%${search}%`); + + const { data, error, count } = await dbQuery + .range(Number(offset), Number(offset) + Number(limit) - 1); + + if (error) throw error; + + res.json({ + data, + total: count, + limit: Number(limit), + offset: Number(offset), + }); + } else if (method === "POST") { + const userId = req.headers["x-user-id"]; + if (!userId) return res.status(401).json({ error: "Unauthorized" }); + + const { + title, + description, + file_url, + duration_seconds, + genre, + license_type, + bpm, + is_published, + } = body; + + if (!title || !file_url || !license_type) { + return res.status(400).json({ + error: "Missing required fields: title, file_url, license_type", + }); + } + + const { data, error } = await supabase + .from("ethos_tracks") + .insert([ + { + user_id: userId, + title, + description, + file_url, + duration_seconds, + genre: genre || [], + license_type, + bpm, + is_published: is_published !== false, + }, + ]) + .select(); + + if (error) throw error; + res.status(201).json(data[0]); + } + } catch (err: any) { + console.error("[Ethos Tracks]", err); + res.status(500).json({ error: err.message }); + } +}