AeThex-OS/temp-forge-extract/aethex-forge-main/api/creators.ts
MrPiglr b3c308b2c8 Add functional marketplace modules, bottom nav bar, root terminal, arcade games
- ModuleManager: Central tracking for installed marketplace modules
- DataAnalyzerWidget: Real-time CPU/RAM/Battery/Storage widget (unlocked by Data Analyzer module)
- BottomNavBar: Navigation bar for Projects/Chat/Marketplace/Settings
- RootShell: Real root command execution utility
- TerminalActivity: Full root shell with neofetch, sysinfo, real Linux commands
- Terminal Pro module: Adds aliases (ll, la, h), command history
- ArcadeActivity + SnakeGame: Pixel Arcade module unlocks retro games
- fade_in/fade_out animations for smooth transitions
2026-02-18 22:03:50 -07:00

385 lines
10 KiB
TypeScript

import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.VITE_SUPABASE_URL || "";
const supabaseServiceRole = process.env.SUPABASE_SERVICE_ROLE || "";
const supabase = createClient(supabaseUrl, supabaseServiceRole);
export async function getCreators(req: Request) {
const url = new URL(req.url);
const arm = url.searchParams.get("arm");
const page = parseInt(url.searchParams.get("page") || "1");
const limit = parseInt(url.searchParams.get("limit") || "20");
const search = url.searchParams.get("search");
try {
let query = supabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
spotify_profile_url,
created_at,
aethex_projects(count)
`,
{ count: "exact" },
)
.eq("is_discoverable", true)
.order("created_at", { ascending: false });
if (arm) {
query = query.contains("arm_affiliations", [arm]);
}
if (search) {
query = query.or(`username.ilike.%${search}%,bio.ilike.%${search}%`);
}
const start = (page - 1) * limit;
query = query.range(start, start + limit - 1);
const { data, error, count } = await query;
if (error) throw error;
return new Response(
JSON.stringify({
data,
pagination: {
page,
limit,
total: count || 0,
pages: Math.ceil((count || 0) / limit),
},
}),
{
status: 200,
headers: { "Content-Type": "application/json" },
},
);
} catch (error) {
console.error("Error fetching creators:", error);
return new Response(JSON.stringify({ error: "Failed to fetch creators" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
export async function getCreatorByUsername(username: string) {
try {
const { data: creator, error } = await supabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
spotify_profile_url,
created_at,
updated_at,
aethex_projects(id, title, description, url, image_url, tags, is_featured),
aethex_skill_endorsements(skill, count)
`,
)
.eq("username", username)
.eq("is_discoverable", true)
.single();
if (error) throw error;
// Get DevConnect link if exists
const { data: devConnectLink } = await supabase
.from("aethex_devconnect_links")
.select("devconnect_username, devconnect_profile_url")
.eq("aethex_creator_id", creator.id)
.single();
return {
...creator,
devconnect_link: devConnectLink,
};
} catch (error) {
console.error("Error fetching creator:", error);
return null;
}
}
export async function createCreatorProfile(req: Request, userId: string) {
try {
const body = (await req.json()) as any;
const {
username,
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
spotify_profile_url,
} = body;
// Validate required fields
if (
!username ||
typeof username !== "string" ||
username.trim().length === 0
) {
return new Response(
JSON.stringify({
error: "Username is required and must be a non-empty string",
}),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
if (!experience_level) {
return new Response(
JSON.stringify({ error: "Experience level is required" }),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
if (!primary_arm) {
return new Response(
JSON.stringify({ error: "Primary arm is required" }),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
const normalizedUsername = username.trim().toLowerCase();
// Check if username already exists
const { data: existingCreator } = await supabase
.from("aethex_creators")
.select("id")
.eq("username", normalizedUsername)
.single();
if (existingCreator) {
return new Response(
JSON.stringify({ error: "Username is already taken" }),
{ status: 409, headers: { "Content-Type": "application/json" } },
);
}
const { data, error } = await supabase
.from("aethex_creators")
.insert({
user_id: userId,
username: normalizedUsername,
bio,
skills: skills || [],
avatar_url,
experience_level,
primary_arm,
arm_affiliations: arm_affiliations || [],
spotify_profile_url,
})
.select()
.single();
if (error) throw error;
return new Response(JSON.stringify(data), {
status: 201,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error creating creator profile:", error);
return new Response(
JSON.stringify({ error: "Failed to create creator profile" }),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function updateCreatorProfile(req: Request, userId: string) {
try {
const body = (await req.json()) as any;
const {
username,
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
is_discoverable,
allow_recommendations,
spotify_profile_url,
} = body;
// Validate required fields if provided
if (username !== undefined && username !== null) {
if (typeof username !== "string" || username.trim().length === 0) {
return new Response(
JSON.stringify({ error: "Username must be a non-empty string" }),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
}
// Check if username is being changed to something that already exists
const { data: currentCreator } = await supabase
.from("aethex_creators")
.select("username")
.eq("user_id", userId)
.single();
const normalizedUsername = username.trim().toLowerCase();
if (currentCreator && currentCreator.username !== normalizedUsername) {
// Username is being changed, check if new username exists
const { data: existingCreator } = await supabase
.from("aethex_creators")
.select("id")
.eq("username", normalizedUsername)
.single();
if (existingCreator) {
return new Response(
JSON.stringify({ error: "Username is already taken" }),
{ status: 409, headers: { "Content-Type": "application/json" } },
);
}
}
}
const updateData: any = {
updated_at: new Date().toISOString(),
};
// Only update fields that are provided
if (username !== undefined && username !== null) {
updateData.username = username.trim().toLowerCase();
}
if (bio !== undefined) updateData.bio = bio;
if (skills !== undefined) updateData.skills = skills;
if (avatar_url !== undefined) updateData.avatar_url = avatar_url;
if (experience_level !== undefined)
updateData.experience_level = experience_level;
if (primary_arm !== undefined) updateData.primary_arm = primary_arm;
if (arm_affiliations !== undefined)
updateData.arm_affiliations = arm_affiliations;
if (is_discoverable !== undefined)
updateData.is_discoverable = is_discoverable;
if (allow_recommendations !== undefined)
updateData.allow_recommendations = allow_recommendations;
if (spotify_profile_url !== undefined)
updateData.spotify_profile_url = spotify_profile_url;
const { data, error } = await supabase
.from("aethex_creators")
.update(updateData)
.eq("user_id", userId)
.select()
.single();
if (error) throw error;
return new Response(JSON.stringify(data), {
status: 200,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error updating creator profile:", error);
return new Response(
JSON.stringify({ error: "Failed to update creator profile" }),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
}
export async function addProjectToCreator(req: Request, creatorId: string) {
try {
const body = await req.json();
const { title, description, url, image_url, tags, is_featured } = body;
const { data, error } = await supabase
.from("aethex_projects")
.insert({
creator_id: creatorId,
title,
description,
url,
image_url,
tags: tags || [],
is_featured: is_featured || false,
})
.select()
.single();
if (error) throw error;
return new Response(JSON.stringify(data), {
status: 201,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error adding project:", error);
return new Response(JSON.stringify({ error: "Failed to add project" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
export async function endorseSkill(
req: Request,
creatorId: string,
endorsedByUserId: string,
) {
try {
const body = await req.json();
const { skill } = body;
// Get the endorsing user's creator ID
const { data: endorsingCreator } = await supabase
.from("aethex_creators")
.select("id")
.eq("user_id", endorsedByUserId)
.single();
if (!endorsingCreator) {
return new Response(
JSON.stringify({ error: "Endorsing user not found" }),
{ status: 404, headers: { "Content-Type": "application/json" } },
);
}
const { data, error } = await supabase
.from("aethex_skill_endorsements")
.insert({
creator_id: creatorId,
endorsed_by_id: endorsingCreator.id,
skill,
})
.select()
.single();
if (error) throw error;
return new Response(JSON.stringify(data), {
status: 201,
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Error endorsing skill:", error);
return new Response(JSON.stringify({ error: "Failed to endorse skill" }), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}