aethex-forge/server/index.ts
Builder.io 1e5e648180 completionId: cgen-7ed7b0861f9b41baa706d20d52c93f7e
cgen-7ed7b0861f9b41baa706d20d52c93f7e
2025-11-09 07:10:07 +00:00

4091 lines
130 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import "dotenv/config";
import "dotenv/config";
import express from "express";
import cors from "cors";
import { adminSupabase } from "./supabase";
import { emailService } from "./email";
import { randomUUID, createHash, createVerify } from "crypto";
// Discord Interactions Handler
const handleDiscordInteractions = async (
req: express.Request,
res: express.Response,
) => {
try {
const signature = req.get("x-signature-ed25519");
const timestamp = req.get("x-signature-timestamp");
const rawBody =
req.body instanceof Buffer
? req.body
: Buffer.from(JSON.stringify(req.body), "utf8");
const bodyString = rawBody.toString("utf8");
const publicKey = process.env.DISCORD_PUBLIC_KEY;
console.log("[Discord] Interaction received at", new Date().toISOString());
if (!publicKey) {
console.error("[Discord] DISCORD_PUBLIC_KEY not set");
return res.status(401).json({ error: "Server not configured" });
}
if (!signature || !timestamp) {
console.error(
"[Discord] Missing headers - signature:",
!!signature,
"timestamp:",
!!timestamp,
);
return res.status(401).json({ error: "Invalid request" });
}
// Verify signature
const message = `${timestamp}${bodyString}`;
const signatureBuffer = Buffer.from(signature, "hex");
const verifier = createVerify("ed25519");
verifier.update(message);
const isValid = verifier.verify(publicKey, signatureBuffer);
if (!isValid) {
console.error("[Discord] Signature verification failed");
return res.status(401).json({ error: "Invalid signature" });
}
const interaction = JSON.parse(bodyString);
console.log("[Discord] Valid interaction type:", interaction.type);
// Discord sends a PING to verify the endpoint
if (interaction.type === 1) {
console.log("[Discord] ✓ PING verified");
return res.json({ type: 1 });
}
// Handle APPLICATION_COMMAND (slash commands)
if (interaction.type === 2) {
const commandName = interaction.data.name;
console.log("[Discord] Slash command received:", commandName);
// /creators command
if (commandName === "creators") {
const arm = interaction.data.options?.[0]?.value;
const armFilter = arm ? ` (${arm})` : " (all arms)";
return res.json({
type: 4,
data: {
content: `🔍 Browse AeThex Creators${armFilter}\n\n👉 [Open Creator Directory](https://aethex.dev/creators${arm ? `?arm=${arm}` : ""})`,
flags: 0,
},
});
}
// /opportunities command
if (commandName === "opportunities") {
const arm = interaction.data.options?.[0]?.value;
const armFilter = arm ? ` (${arm})` : " (all arms)";
return res.json({
type: 4,
data: {
content: `💼 Find Opportunities on Nexus${armFilter}\n\n👉 [Browse Opportunities](https://aethex.dev/opportunities${arm ? `?arm=${arm}` : ""})`,
flags: 0,
},
});
}
// /nexus command
if (commandName === "nexus") {
return res.json({
type: 4,
data: {
content: `✨ **AeThex Nexus** - The Talent Marketplace\n\n🔗 [Open Nexus](https://aethex.dev/nexus)\n\n**Quick Links:**\n• 🔍 [Browse Creators](https://aethex.dev/creators)\n• 💼 [Find Opportunities](https://aethex.dev/opportunities)\n• 📊 [View Metrics](https://aethex.dev/admin)`,
flags: 0,
},
});
}
// /verify command - Generate verification code and link
if (commandName === "verify") {
try {
const discordId = interaction.member?.user?.id;
if (!discordId) {
return res.json({
type: 4,
data: {
content: "❌ Could not get your Discord ID",
flags: 64,
},
});
}
// Generate verification code (random 6-digit)
const verificationCode = Math.random()
.toString(36)
.substring(2, 8)
.toUpperCase();
const expiresAt = new Date(Date.now() + 15 * 60 * 1000).toISOString(); // 15 min
// Store verification code in Supabase
const { error } = await adminSupabase
.from("discord_verifications")
.insert([
{
discord_id: discordId,
verification_code: verificationCode,
expires_at: expiresAt,
},
]);
if (error) {
console.error("Error storing verification code:", error);
return res.json({
type: 4,
data: {
content:
"❌ Error generating verification code. Please try again.",
flags: 64,
},
});
}
const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`;
return res.json({
type: 4,
data: {
content: `✅ **Verification Code: \`${verificationCode}\`**\n\n🔗 [Click here to verify your account](${verifyUrl})\n\n⏱ This code expires in 15 minutes.`,
flags: 0,
},
});
} catch (error) {
console.error("Error in /verify command:", error);
return res.json({
type: 4,
data: {
content: "❌ An error occurred. Please try again later.",
flags: 64,
},
});
}
}
// /set-realm command - Choose primary arm
if (commandName === "set-realm") {
const realmChoice = interaction.data.options?.[0]?.value;
if (!realmChoice) {
return res.json({
type: 4,
data: {
content: "❌ Please select a realm",
flags: 64,
},
});
}
const realmMap: any = {
labs: "🔬 Labs",
gameforge: "🎮 GameForge",
corp: "💼 Corp",
foundation: "🤝 Foundation",
devlink: "🔗 Dev-Link",
};
return res.json({
type: 4,
data: {
content: `✅ You've selected **${realmMap[realmChoice] || realmChoice}** as your primary realm!\n\n📝 Your role will be assigned based on your selection.`,
flags: 0,
},
});
}
// /profile command - Show user profile
if (commandName === "profile") {
const discordId = interaction.member?.user?.id;
const username = interaction.member?.user?.username;
return res.json({
type: 4,
data: {
content: `👤 **Your AeThex Profile**\n\n**Discord:** ${username} (\`${discordId}\`)\n\n🔗 [View Full Profile](https://aethex.dev/profile)\n\n**Quick Actions:**\n• \`/set-realm\` - Choose your primary arm\n• \`/verify\` - Link your account\n• \`/verify-role\` - Check your assigned roles`,
flags: 0,
},
});
}
// /unlink command - Disconnect Discord
if (commandName === "unlink") {
const discordId = interaction.member?.user?.id;
return res.json({
type: 4,
data: {
content: `🔓 **Account Unlinked**\n\nYour Discord account (\`${discordId}\`) has been disconnected from AeThex.\n\nTo link again, use \`/verify\``,
flags: 0,
},
});
}
// /verify-role command - Check assigned roles
if (commandName === "verify-role") {
return res.json({
type: 4,
data: {
content: `✅ **Discord Roles**\n\nYour assigned AeThex roles are shown below.\n\n<EFBFBD><EFBFBD><EFBFBD><EFBFBD> [View Full Profile](https://aethex.dev/profile)`,
flags: 0,
},
});
}
// Default command response
return res.json({
type: 4,
data: {
content: `✨ AeThex - Advanced Development Platform\n\n**Available Commands:**\n• \`/creators [arm]\` - Browse creators across AeThex arms\n• \`/opportunities [arm]\` - Find job opportunities and collaborations\n• \`/nexus\` - Explore the Talent Marketplace\n• \`/verify\` - Link your Discord account\n• \`/set-realm\` - Choose your primary realm\n• \`/profile\` - View your AeThex profile\n• \`/unlink\` - Disconnect your account\n• \`/verify-role\` - Check your assigned Discord roles`,
flags: 0,
},
});
}
// For MESSAGE_COMPONENT interactions (buttons, etc.)
if (interaction.type === 3) {
console.log(
"[Discord] Message component interaction:",
interaction.data.custom_id,
);
return res.json({
type: 4,
data: { content: "Button clicked - feature coming soon!" },
});
}
// Acknowledge all other interactions
return res.json({
type: 4,
data: { content: "Interaction acknowledged" },
});
} catch (err: any) {
console.error("[Discord] Error:", err?.message);
return res.status(500).json({ error: "Server error" });
}
};
export function createServer() {
const app = express();
// Discord endpoint MUST be defined BEFORE any body parsing middleware
// and needs raw body for signature verification
app.post(
"/api/discord/interactions",
express.raw({ type: "application/json" }),
handleDiscordInteractions,
);
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Allow Discord to embed the activity in iframes
app.use((req, res, next) => {
// Allow embedding in iframes (Discord Activities need this)
res.setHeader("X-Frame-Options", "ALLOWALL");
// Allow Discord to access the iframe
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
res.setHeader(
"Access-Control-Allow-Headers",
"Content-Type, Authorization, x-signature-ed25519, x-signature-timestamp",
);
next();
});
// Example API routes
app.get("/api/ping", (_req, res) => {
const ping = process.env.PING_MESSAGE ?? "ping";
res.json({ message: ping });
});
// DevConnect REST proxy (GET only)
app.get("/api/devconnect/rest/:table", async (req, res) => {
try {
const base = process.env.DEVCONNECT_URL;
const key = process.env.DEVCONNECT_ANON_KEY;
if (!base || !key)
return res.status(500).json({ error: "DevConnect env not set" });
const table = String(req.params.table || "").replace(
/[^a-zA-Z0-9_]/g,
"",
);
const qs = req.url.includes("?")
? req.url.substring(req.url.indexOf("?"))
: "";
const url = `${base}/rest/v1/${table}${qs}`;
const r = await fetch(url, {
headers: {
apikey: key,
Authorization: `Bearer ${key}`,
Accept: "application/json",
},
});
const text = await r.text();
if (!r.ok) return res.status(r.status).send(text);
res.setHeader(
"content-type",
r.headers.get("content-type") || "application/json",
);
return res.status(200).send(text);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/auth/send-verification-email", async (req, res) => {
const { email, redirectTo, fullName } = (req.body || {}) as {
email?: string;
redirectTo?: string;
fullName?: string | null;
};
if (!email) {
return res.status(400).json({ error: "email is required" });
}
if (!adminSupabase?.auth?.admin) {
return res
.status(500)
.json({ error: "Supabase admin client unavailable" });
}
try {
const fallbackRedirect =
process.env.EMAIL_VERIFY_REDIRECT ??
process.env.PUBLIC_BASE_URL ??
process.env.SITE_URL ??
"https://aethex.biz/login";
const redirectUrl =
typeof redirectTo === "string" && redirectTo.startsWith("http")
? redirectTo
: fallbackRedirect;
const { data, error } = await adminSupabase.auth.admin.generateLink({
type: "signup",
email,
options: {
redirectTo: redirectUrl,
},
} as any);
if (error) {
console.error("[API] generateLink error:", {
message: error?.message || String(error),
status: (error as any)?.status || null,
code: (error as any)?.code || null,
details: (error as any)?.details || null,
});
const errMsg =
typeof error === "string"
? error
: error?.message || JSON.stringify(error);
return res
.status((error as any)?.status ?? 500)
.json({ error: errMsg });
}
const actionLink =
(data as any)?.properties?.action_link ??
(data as any)?.properties?.verification_link ??
null;
if (!actionLink) {
return res
.status(500)
.json({ error: "Failed to generate verification link" });
}
const displayName =
(data as any)?.user?.user_metadata?.full_name ?? fullName ?? null;
if (!emailService.isConfigured) {
console.warn(
"[API] Email service not configured. SMTP env vars missing:",
{
hasHost: Boolean(process.env.SMTP_HOST),
hasUser: Boolean(process.env.SMTP_USER),
hasPassword: Boolean(process.env.SMTP_PASSWORD),
},
);
return res.json({
sent: false,
verificationUrl: actionLink,
message:
"Email service not configured. Please set SMTP_HOST, SMTP_USER, and SMTP_PASSWORD to enable email sending.",
});
}
try {
await emailService.sendVerificationEmail({
to: email,
verificationUrl: actionLink,
fullName: displayName,
});
console.log("[API] Verification email sent successfully to:", email);
return res.json({ sent: true, verificationUrl: actionLink });
} catch (emailError: any) {
console.error("[API] sendVerificationEmail threw error:", {
message: emailError?.message || String(emailError),
code: emailError?.code || null,
response: emailError?.response || null,
});
// Return with manual link as fallback even if email fails
return res.status(200).json({
sent: false,
verificationUrl: actionLink,
message: `Email delivery failed: ${emailError?.message || "SMTP error"}. Use manual link to verify.`,
});
}
} catch (error: any) {
console.error("[API] send verification email failed", error);
return res
.status(500)
.json({ error: error?.message || "Unexpected error" });
}
});
// Org domain magic-link sender (Aethex)
app.post("/api/auth/send-org-link", async (req, res) => {
try {
const { email, redirectTo } = (req.body || {}) as {
email?: string;
redirectTo?: string;
};
const target = String(email || "")
.trim()
.toLowerCase();
if (!target) return res.status(400).json({ error: "email is required" });
const allowed = /@aethex\.dev$/i.test(target);
if (!allowed)
return res.status(403).json({ error: "domain not allowed" });
if (!adminSupabase?.auth?.admin) {
return res.status(500).json({ error: "Supabase admin unavailable" });
}
const fallbackRedirect =
process.env.EMAIL_VERIFY_REDIRECT ??
process.env.PUBLIC_BASE_URL ??
process.env.SITE_URL ??
"https://aethex.dev";
const toUrl =
typeof redirectTo === "string" && redirectTo.startsWith("http")
? redirectTo
: fallbackRedirect;
const { data, error } = await adminSupabase.auth.admin.generateLink({
type: "magiclink" as any,
email: target,
options: { redirectTo: toUrl },
} as any);
if (error) {
return res.status(500).json({ error: error.message || String(error) });
}
const actionLink =
(data as any)?.properties?.action_link ??
(data as any)?.properties?.verification_link ??
null;
if (!actionLink) {
return res.status(500).json({ error: "Failed to generate magic link" });
}
if (!emailService.isConfigured) {
return res.json({ sent: false, verificationUrl: actionLink });
}
await emailService.sendVerificationEmail({
to: target,
verificationUrl: actionLink,
fullName: null,
});
return res.json({ sent: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/auth/check-verification", async (req, res) => {
const { email } = (req.body || {}) as { email?: string };
if (!email) {
return res.status(400).json({ error: "email is required" });
}
if (!adminSupabase) {
return res
.status(500)
.json({ error: "Supabase admin client unavailable" });
}
try {
const targetEmail = String(email).trim().toLowerCase();
// Prefer GoTrue Admin listUsers; fall back to pagination if email filter unsupported
const admin = (adminSupabase as any)?.auth?.admin;
if (!admin) {
return res.status(500).json({ error: "Auth admin unavailable" });
}
let user: any = null;
let listResp: any = null;
try {
listResp = await admin.listUsers({
page: 1,
perPage: 200,
email: targetEmail,
} as any);
} catch (e) {
listResp = null;
}
const initialUsers: any[] = (listResp?.data?.users as any[]) || [];
user =
initialUsers.find(
(u: any) => String(u?.email || "").toLowerCase() === targetEmail,
) || null;
if (!user) {
// Pagination fallback (limited scan)
for (let page = 1; page <= 5 && !user; page++) {
const resp = await admin
.listUsers({ page, perPage: 200 } as any)
.catch(() => null);
const users = (resp?.data?.users as any[]) || [];
user =
users.find(
(u: any) => String(u?.email || "").toLowerCase() === targetEmail,
) || null;
}
}
if (!user) {
return res.json({ verified: false, user: null, reason: "not_found" });
}
const verified = Boolean(user?.email_confirmed_at || user?.confirmed_at);
if (verified) {
try {
const { data: ach, error: aErr } = await adminSupabase
.from("achievements")
.select("id")
.eq("name", "Founding Member")
.maybeSingle();
if (!aErr && ach?.id) {
const { error: uaErr } = await adminSupabase
.from("user_achievements")
.upsert(
{ user_id: user.id, achievement_id: ach.id },
{ onConflict: "user_id,achievement_id" as any },
);
if (uaErr) {
console.warn("Failed to award Founding Member:", uaErr);
}
}
} catch (awardErr) {
console.warn("Awarding achievement on verification failed", awardErr);
}
}
return res.json({ verified, user });
} catch (e: any) {
console.error("[API] check verification exception", e);
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Storage administration endpoints (service role)
app.post("/api/storage/ensure-buckets", async (_req, res) => {
if (!adminSupabase) {
return res
.status(500)
.json({ error: "Supabase admin client unavailable" });
}
try {
const targets = [
{ name: "avatars", public: true },
{ name: "banners", public: true },
{ name: "post_media", public: true },
];
const { data: buckets } = await (
adminSupabase as any
).storage.listBuckets();
const existing = new Set((buckets || []).map((b: any) => b.name));
const created: string[] = [];
for (const t of targets) {
if (!existing.has(t.name)) {
const { error } = await (adminSupabase as any).storage.createBucket(
t.name,
{ public: t.public },
);
if (error) {
console.warn("Failed to create bucket", t.name, error);
} else {
created.push(t.name);
}
}
}
return res.json({ ok: true, created });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Admin-backed API (service role)
try {
const ownerEmail = (
process.env.AETHEX_OWNER_EMAIL || "mrpiglr@gmail.com"
).toLowerCase();
const isTableMissing = (err: any) => {
const code = err?.code;
const message = String(err?.message || err?.hint || err?.details || "");
return (
code === "42P01" ||
message.includes("relation") ||
message.includes("does not exist")
);
};
// Roblox OAuth: start (build authorize URL with PKCE and redirect)
app.get("/api/roblox/oauth/start", (req, res) => {
try {
const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID;
if (!clientId)
return res.status(500).json({ error: "Roblox OAuth not configured" });
const baseSite =
process.env.PUBLIC_BASE_URL ||
process.env.SITE_URL ||
"https://aethex.dev";
const redirectUri =
typeof req.query.redirect_uri === "string" &&
req.query.redirect_uri.startsWith("http")
? String(req.query.redirect_uri)
: process.env.ROBLOX_OAUTH_REDIRECT_URI ||
`${baseSite}/roblox-callback`;
const scope = String(
req.query.scope || process.env.ROBLOX_OAUTH_SCOPE || "openid",
);
const state = String(req.query.state || randomUUID());
// PKCE
const codeVerifier = Buffer.from(randomUUID() + randomUUID())
.toString("base64url")
.slice(0, 64);
const codeChallenge = createHash("sha256")
.update(codeVerifier)
.digest("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/g, "");
const params = new URLSearchParams({
client_id: clientId,
response_type: "code",
redirect_uri: redirectUri,
scope,
state,
code_challenge: codeChallenge,
code_challenge_method: "S256",
});
const authorizeUrl = `https://apis.roblox.com/oauth/authorize?${params.toString()}`;
// set short-lived cookies for verifier/state (for callback validation)
const secure =
req.secure ||
req.get("x-forwarded-proto") === "https" ||
process.env.NODE_ENV === "production";
res.cookie("roblox_oauth_state", state, {
httpOnly: true,
sameSite: "lax",
secure,
maxAge: 10 * 60 * 1000,
path: "/",
});
res.cookie("roblox_oauth_code_verifier", codeVerifier, {
httpOnly: true,
sameSite: "lax",
secure,
maxAge: 10 * 60 * 1000,
path: "/",
});
if (String(req.query.json || "").toLowerCase() === "true") {
return res.json({ authorizeUrl, state });
}
return res.redirect(302, authorizeUrl);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Discord OAuth: callback handler
app.post("/api/discord/oauth/callback", async (req, res) => {
const { code, state } = (req.body || {}) as {
code?: string;
state?: string;
};
if (!code) {
return res
.status(400)
.json({ error: "Authorization code is required" });
}
try {
const clientId =
process.env.VITE_DISCORD_CLIENT_ID || "578971245454950421";
const clientSecret = process.env.DISCORD_CLIENT_SECRET;
const redirectUri =
process.env.DISCORD_REDIRECT_URI ||
`${process.env.PUBLIC_BASE_URL || process.env.SITE_URL || "http://localhost:5173"}/discord/callback`;
if (!clientSecret) {
console.warn(
"[Discord OAuth] DISCORD_CLIENT_SECRET not configured, skipping token exchange",
);
return res.json({
ok: true,
access_token: null,
message: "Discord auth configured for Activity context only",
});
}
// Exchange authorization code for access token
const tokenResponse = await fetch(
"https://discord.com/api/oauth2/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
code,
grant_type: "authorization_code",
redirect_uri: redirectUri,
}),
},
);
if (!tokenResponse.ok) {
const errorData = await tokenResponse.text();
console.error("[Discord OAuth] Token exchange failed:", {
status: tokenResponse.status,
error: errorData,
});
return res.status(400).json({
error: "Failed to exchange authorization code",
});
}
const tokenData = await tokenResponse.json();
// Get Discord user information
const userResponse = await fetch("https://discord.com/api/users/@me", {
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
});
if (!userResponse.ok) {
return res.status(400).json({
error: "Failed to retrieve Discord user information",
});
}
const discordUser = await userResponse.json();
// Optionally: create or update Supabase user linked to Discord account
if (adminSupabase && discordUser.id) {
try {
// Check if user with Discord ID exists
const { data: existingUser } = await adminSupabase
.from("user_profiles")
.select("id")
.eq("discord_id", discordUser.id)
.maybeSingle();
if (!existingUser && discordUser.email) {
// Attempt to find by email
const { data: userByEmail } = await adminSupabase
.from("user_profiles")
.select("id")
.eq("email", discordUser.email)
.maybeSingle();
if (userByEmail) {
// Update existing user with Discord ID
await adminSupabase
.from("user_profiles")
.update({ discord_id: discordUser.id })
.eq("id", userByEmail.id);
}
}
} catch (err) {
console.warn("[Discord OAuth] Failed to link Discord ID:", err);
}
}
return res.json({
ok: true,
access_token: tokenData.access_token,
discord_user: {
id: discordUser.id,
username: discordUser.username,
avatar: discordUser.avatar,
email: discordUser.email,
},
});
} catch (e: any) {
console.error("[Discord OAuth] Error:", e);
return res.status(500).json({
error: e?.message || "Internal server error during Discord OAuth",
});
}
});
// Discord Role Mappings CRUD
app.get("/api/discord/role-mappings", async (req, res) => {
try {
const { data, error } = await adminSupabase
.from("discord_role_mappings")
.select("*")
.order("created_at", { ascending: false });
if (error) {
console.error("[Discord] Error fetching role mappings:", error);
return res.status(500).json({
error: `Failed to fetch role mappings: ${error.message}`,
});
}
res.setHeader("Content-Type", "application/json");
return res.json(data || []);
} catch (e: any) {
console.error("[Discord] Exception fetching role mappings:", e);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: e?.message || "Failed to fetch role mappings",
});
}
});
app.post("/api/discord/role-mappings", async (req, res) => {
try {
const { arm, discord_role, discord_role_name, server_id, user_type } =
req.body;
const roleName = discord_role_name || discord_role;
if (!arm || !roleName) {
return res.status(400).json({
error: "arm and discord_role (or discord_role_name) are required",
});
}
const { data, error } = await adminSupabase
.from("discord_role_mappings")
.insert({
arm,
user_type: user_type || "community_member",
discord_role_name: roleName,
server_id: server_id || null,
})
.select()
.single();
if (error) {
console.error("[Discord] Error creating role mapping:", error);
return res.status(500).json({
error: `Failed to create mapping: ${error.message}`,
});
}
res.setHeader("Content-Type", "application/json");
return res.status(201).json(data);
} catch (e: any) {
console.error("[Discord] Exception creating role mapping:", e);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: e?.message || "Failed to create mapping",
});
}
});
app.put("/api/discord/role-mappings", async (req, res) => {
try {
const {
id,
arm,
discord_role,
discord_role_name,
server_id,
user_type,
} = req.body;
if (!id) {
return res.status(400).json({ error: "id is required" });
}
const updateData: any = {};
if (arm) updateData.arm = arm;
const roleName = discord_role_name || discord_role;
if (roleName) updateData.discord_role_name = roleName;
if (server_id !== undefined) updateData.server_id = server_id;
if (user_type) updateData.user_type = user_type;
const { data, error } = await adminSupabase
.from("discord_role_mappings")
.update(updateData)
.eq("id", id)
.select()
.single();
if (error) {
console.error("[Discord] Error updating role mapping:", error);
return res.status(500).json({
error: `Failed to update mapping: ${error.message}`,
});
}
res.setHeader("Content-Type", "application/json");
return res.json(data);
} catch (e: any) {
console.error("[Discord] Exception updating role mapping:", e);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: e?.message || "Failed to update mapping",
});
}
});
app.delete("/api/discord/role-mappings", async (req, res) => {
try {
const { id } = req.query;
if (!id) {
return res.status(400).json({
error: "id query parameter is required",
});
}
const { error } = await adminSupabase
.from("discord_role_mappings")
.delete()
.eq("id", id);
if (error) {
console.error("[Discord] Error deleting role mapping:", error);
return res.status(500).json({
error: `Failed to delete mapping: ${error.message}`,
});
}
res.setHeader("Content-Type", "application/json");
return res.json({ success: true });
} catch (e: any) {
console.error("[Discord] Exception deleting role mapping:", e);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: e?.message || "Failed to delete mapping",
});
}
});
// Discord Admin Register Commands
app.get("/api/discord/admin-register-commands", async (req, res) => {
// GET: Show HTML form for browser access
res.setHeader("Content-Type", "text/html; charset=utf-8");
return res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Register Discord Commands</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
text-align: center;
max-width: 500px;
width: 90%;
}
h1 { color: #333; margin-bottom: 10px; font-size: 28px; }
p { color: #666; margin-bottom: 30px; line-height: 1.6; }
.input-group {
margin-bottom: 20px;
text-align: left;
}
label {
display: block;
color: #333;
font-weight: bold;
margin-bottom: 8px;
font-size: 14px;
}
input {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.3s;
}
input:focus {
outline: none;
border-color: #667eea;
}
button {
background: #667eea;
color: white;
border: none;
padding: 14px 40px;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
transition: all 0.3s;
width: 100%;
}
button:hover {
background: #764ba2;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
button:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
#result {
margin-top: 30px;
padding: 20px;
border-radius: 6px;
display: none;
font-size: 14px;
word-break: break-all;
}
#result.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
display: block;
}
#result.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
display: block;
}
#loading {
display: none;
color: #667eea;
font-weight: bold;
margin-top: 20px;
}
.commands-list {
background: #f0f0f0;
padding: 15px;
border-radius: 6px;
margin: 20px 0;
font-size: 13px;
text-align: left;
color: #333;
}
.commands-list li { margin: 5px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🤖 Discord Commands Registration</h1>
<p>Register all Discord slash commands for AeThex</p>
<div class="commands-list">
<strong>Commands to be registered:</strong>
<ul>
<li>✅ /verify - Link your Discord account</li>
<li>✅ /set-realm - Choose your primary arm</li>
<li>✅ /profile - View your AeThex profile</li>
<li>✅ /unlink - Disconnect your account</li>
<li>✅ /verify-role - Check your Discord roles</li>
</ul>
</div>
<div class="input-group">
<label for="token">Admin Token:</label>
<input type="password" id="token" placeholder="Enter admin token" value="aethex-link" />
</div>
<button id="registerBtn" onclick="registerCommands()">🚀 Register Commands Now</button>
<div id="loading">⏳ Registering commands... please wait...</div>
<div id="result"></div>
</div>
<script>
async function registerCommands() {
const btn = document.getElementById('registerBtn');
const loading = document.getElementById('loading');
const result = document.getElementById('result');
const token = document.getElementById('token').value.trim();
console.log('[Form] Token length:', token.length);
console.log('[Form] Token value:', 'Bearer ' + token);
if (!token) {
result.className = 'error';
result.innerHTML = '<h3>❌ Error</h3><p>Please enter the admin token</p>';
result.style.display = 'block';
return;
}
btn.disabled = true;
loading.style.display = 'block';
result.style.display = 'none';
try {
const authHeader = 'Bearer ' + token;
console.log('[Form] Sending auth header:', authHeader);
const response = await fetch('/api/discord/admin-register-commands', {
method: 'POST',
headers: {
'Authorization': authHeader,
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: token })
});
let data;
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
data = await response.json();
} else {
const text = await response.text();
data = { error: text };
}
loading.style.display = 'none';
result.style.display = 'block';
if (response.ok && data.ok) {
result.className = 'success';
result.innerHTML = \`
<h3>✅ Success!</h3>
<p><strong>Registered \${data.commands.length} commands:</strong></p>
<ul style="text-align: left;">
\${data.commands.map(cmd => \`<li>✓ /\${cmd.name}</li>\`).join('')}
</ul>
<p style="margin-top: 15px; color: #155724;">You can now use these commands in Discord!</p>
\`;
} else {
result.className = 'error';
result.innerHTML = \`
<h3>❌ Registration Failed (\${response.status})</h3>
<p><strong>Error:</strong> \${data.error || 'Unknown error'}</p>
\${data.details ? \`<p><strong>Details:</strong> \${data.details}</p>\` : ''}
\`;
}
} catch (error) {
loading.style.display = 'none';
result.style.display = 'block';
result.className = 'error';
result.innerHTML = \`
<h3>❌ Request Failed</h3>
<p>\${error.message}</p>
\`;
} finally {
btn.disabled = false;
}
}
// Auto-focus token field on page load
window.addEventListener('load', () => {
document.getElementById('token').focus();
});
</script>
</body>
</html>
`);
});
app.post("/api/discord/admin-register-commands", async (req, res) => {
try {
// Skip auth for localhost/development
const isLocalhost =
req.hostname === "localhost" || req.hostname === "127.0.0.1";
if (!isLocalhost) {
const authHeader = req.headers.authorization;
const tokenFromBody = req.body?.token as string;
// Extract token from Bearer header
let token = null;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
} else if (tokenFromBody) {
token = tokenFromBody;
}
const adminToken = process.env.DISCORD_ADMIN_REGISTER_TOKEN;
if (!adminToken || !token || token !== adminToken) {
console.error(
"[Discord] Authorization failed - token mismatch or missing",
);
return res.status(401).json({
error: "Unauthorized - invalid or missing admin token",
});
}
}
const botToken = process.env.DISCORD_BOT_TOKEN?.trim();
const clientId = process.env.DISCORD_CLIENT_ID?.trim();
console.log(
"[Discord] Config check - botToken set:",
!!botToken,
"clientId set:",
!!clientId,
"botToken length:",
botToken?.length,
);
// Log first and last few chars of token for debugging (safe logging)
if (botToken) {
const first = botToken.substring(0, 5);
const last = botToken.substring(botToken.length - 5);
console.log(
"[Discord] Token preview: " + first + "..." + last,
"Total length:",
botToken.length,
);
}
if (!botToken || !clientId) {
return res.status(500).json({
error: "Discord bot token or client ID not configured",
});
}
// Validate token format
if (botToken.length < 20) {
console.warn(
"[Discord] Bot token appears invalid - length:",
botToken.length,
);
return res.status(500).json({
error:
"Discord bot token appears invalid (check DISCORD_BOT_TOKEN in environment)",
tokenLength: botToken.length,
});
}
// Register slash commands
const commands = [
{
name: "verify",
description: "Link your Discord account to AeThex",
type: 1,
},
{
name: "set-realm",
description: "Choose your primary arm/realm",
type: 1,
options: [
{
name: "realm",
description: "Select your primary realm",
type: 3,
required: true,
choices: [
{ name: "Labs", value: "labs" },
{ name: "GameForge", value: "gameforge" },
{ name: "Corp", value: "corp" },
{ name: "Foundation", value: "foundation" },
{ name: "Nexus", value: "nexus" },
],
},
],
},
{
name: "profile",
description: "View your AeThex profile",
type: 1,
},
{
name: "unlink",
description: "Disconnect your Discord account from AeThex",
type: 1,
},
{
name: "verify-role",
description: "Check your assigned Discord roles",
type: 1,
},
];
const registerUrl = `https://discord.com/api/v10/applications/${clientId}/commands`;
console.log("[Discord] Calling Discord API:", registerUrl);
console.log("[Discord] Bot token length:", botToken.length);
console.log("[Discord] Sending", commands.length, "commands");
const response = await fetch(registerUrl, {
method: "PUT",
headers: {
Authorization: `Bot ${botToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(commands),
});
console.log("[Discord] Discord API response status:", response.status);
if (!response.ok) {
const errorData = await response.text();
let errorJson = {};
try {
errorJson = JSON.parse(errorData);
} catch {}
console.error(
"[Discord] Command registration failed:",
response.status,
errorData,
);
return res.status(500).json({
error: `Discord API error (${response.status}): ${errorData}`,
discordError: errorJson,
});
}
const result = await response.json();
console.log(
"[Discord] Commands registered successfully:",
result.length,
);
res.setHeader("Content-Type", "application/json");
return res.json({
ok: true,
message: `Registered ${result.length} commands`,
commands: result,
});
} catch (e: any) {
console.error(
"[Discord] Exception registering commands:",
e.message,
e.stack,
);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: e?.message || "Failed to register commands",
details:
process.env.NODE_ENV === "development" ? e?.stack : undefined,
});
}
});
// Discord Bot Health Check (proxy to avoid CSP issues)
app.get("/api/discord/bot-health", async (req, res) => {
try {
const botHealthUrl = "https://aethex.railway.internal:8044/health";
const response = await fetch(botHealthUrl, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
return res.status(response.status).json({
status: "offline",
error: `HTTP ${response.status}: Failed to reach bot`,
});
}
const data = await response.json();
res.setHeader("Content-Type", "application/json");
return res.json({
status: data.status || "online",
guilds: data.guilds || 0,
commands: data.commands || 0,
uptime: data.uptime || 0,
timestamp: data.timestamp || new Date().toISOString(),
});
} catch (error: any) {
console.error("[Discord Bot Health] Error:", error);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
status: "offline",
error:
error instanceof Error
? error.message
: "Failed to reach bot health endpoint",
});
}
});
// Site settings (admin-managed)
app.get("/api/site-settings", async (req, res) => {
try {
const key = String(req.query.key || "").trim();
if (key) {
try {
const { data, error } = await adminSupabase
.from("site_settings")
.select("value")
.eq("key", key)
.maybeSingle();
if (error) {
if (isTableMissing(error)) return res.json({});
return res.status(500).json({ error: error.message });
}
return res.json((data as any)?.value || {});
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
}
const { data, error } = await adminSupabase
.from("site_settings")
.select("key, value");
if (error) {
if (isTableMissing(error)) return res.json({});
return res.status(500).json({ error: error.message });
}
const map: Record<string, any> = {};
for (const row of data || [])
map[(row as any).key] = (row as any).value;
return res.json(map);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/site-settings", async (req, res) => {
try {
const { key, value } = (req.body || {}) as {
key?: string;
value?: any;
};
if (!key || typeof key !== "string") {
return res.status(400).json({ error: "key required" });
}
const payload = { key, value: value ?? {} } as any;
const { error } = await adminSupabase
.from("site_settings")
.upsert(payload, { onConflict: "key" as any });
if (error) {
if (isTableMissing(error))
return res
.status(400)
.json({ error: "site_settings table missing" });
return res.status(500).json({ error: error.message });
}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/health", async (_req, res) => {
try {
const { error } = await adminSupabase
.from("user_profiles")
.select("count", { count: "exact", head: true });
if (error)
return res.status(500).json({ ok: false, error: error.message });
return res.json({ ok: true });
} catch (e: any) {
return res
.status(500)
.json({ ok: false, error: e?.message || String(e) });
}
});
app.get("/api/posts", async (req, res) => {
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 10));
try {
const { data, error } = await adminSupabase
.from("community_posts")
.select(`*, user_profiles ( username, full_name, avatar_url )`)
.eq("is_published", true)
.order("created_at", { ascending: false })
.limit(limit);
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/user/:id/posts", async (req, res) => {
const userId = req.params.id;
try {
const { data, error } = await adminSupabase
.from("community_posts")
.select("*")
.eq("author_id", userId)
.order("created_at", { ascending: false });
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/posts", async (req, res) => {
const payload = req.body || {};
// Validation
if (!payload.author_id) {
return res.status(400).json({ error: "author_id is required" });
}
if (
!payload.title ||
typeof payload.title !== "string" ||
!payload.title.trim()
) {
return res
.status(400)
.json({ error: "title is required and must be a non-empty string" });
}
if (
!payload.content ||
typeof payload.content !== "string" ||
!payload.content.trim()
) {
return res.status(400).json({
error: "content is required and must be a non-empty string",
});
}
// Validate author_id is a valid UUID format
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(String(payload.author_id))) {
return res
.status(400)
.json({ error: "author_id must be a valid UUID" });
}
try {
// Verify author exists
const { data: author, error: authorError } = await adminSupabase
.from("user_profiles")
.select("id")
.eq("id", payload.author_id)
.single();
if (authorError || !author) {
return res.status(404).json({ error: "Author not found" });
}
const { data, error } = await adminSupabase
.from("community_posts")
.insert({
author_id: payload.author_id,
title: String(payload.title).trim(),
content: String(payload.content).trim(),
category: payload.category ? String(payload.category).trim() : null,
tags: Array.isArray(payload.tags)
? payload.tags.map((t: any) => String(t).trim())
: [],
is_published: payload.is_published ?? true,
})
.select()
.single();
if (error) {
console.error("[API] /api/posts insert error:", {
code: error.code,
message: error.message,
details: (error as any).details,
});
return res
.status(500)
.json({ error: error.message || "Failed to create post" });
}
res.json(data);
} catch (e: any) {
console.error("[API] /api/posts exception:", e?.message || String(e));
res.status(500).json({ error: e?.message || "Failed to create post" });
}
});
app.post("/api/profile/ensure", async (req, res) => {
const { id, profile } = req.body || {};
console.log("[API] /api/profile/ensure called", { id, profile });
if (!id) return res.status(400).json({ error: "missing id" });
const tryUpsert = async (payload: any) => {
const resp = await adminSupabase
.from("user_profiles")
.upsert(payload, { onConflict: "id" })
.select()
.single();
return resp as any;
};
try {
let username = profile?.username;
let attempt = await tryUpsert({ id, ...profile, username });
const normalizeError = (err: any) => {
if (!err) return null;
if (typeof err === "string") return { message: err };
if (typeof err === "object" && Object.keys(err).length === 0)
return null; // treat empty object as no error
return err;
};
let error = normalizeError(attempt.error);
if (error) {
console.error("[API] ensure upsert error:", {
message: (error as any).message,
code: (error as any).code,
details: (error as any).details,
hint: (error as any).hint,
});
const message: string = (error as any).message || "";
const code: string = (error as any).code || "";
// Handle duplicate username
if (
code === "23505" ||
message.includes("duplicate key") ||
message.includes("username")
) {
const suffix = Math.random().toString(36).slice(2, 6);
const newUsername = `${String(username || "user").slice(0, 20)}_${suffix}`;
console.log("[API] retrying with unique username", newUsername);
attempt = await tryUpsert({
id,
...profile,
username: newUsername,
});
error = normalizeError(attempt.error);
}
}
if (error) {
// Possible foreign key violation: auth.users missing
if (
(error as any).code === "23503" ||
(error as any).message?.includes("foreign key")
) {
return res.status(400).json({
error:
"User does not exist in authentication system. Please sign out and sign back in, then retry onboarding.",
});
}
return res.status(500).json({
error:
(error as any).message ||
JSON.stringify(error) ||
"Unknown error",
});
}
return res.json(attempt.data || {});
} catch (e: any) {
console.error("[API] /api/profile/ensure exception:", e);
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/interests", async (req, res) => {
const { user_id, interests } = req.body || {};
if (!user_id || !Array.isArray(interests))
return res.status(400).json({ error: "invalid payload" });
try {
await adminSupabase
.from("user_interests")
.delete()
.eq("user_id", user_id);
if (interests.length) {
const rows = interests.map((interest: string) => ({
user_id,
interest,
}));
const { error } = await adminSupabase
.from("user_interests")
.insert(rows);
if (error) return res.status(500).json({ error: error.message });
}
res.json({ ok: true });
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/featured-studios", async (_req, res) => {
try {
const { data, error } = await adminSupabase
.from("featured_studios")
.select("*")
.order("rank", { ascending: true, nullsFirst: true } as any)
.order("name", { ascending: true });
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/featured-studios", async (req, res) => {
const studios = (req.body?.studios || []) as any[];
if (!Array.isArray(studios))
return res.status(400).json({ error: "studios must be an array" });
try {
const rows = studios.map((s: any, idx: number) => ({
id: s.id,
name: String(s.name || "").trim(),
tagline: s.tagline || null,
metrics: s.metrics || null,
specialties: Array.isArray(s.specialties) ? s.specialties : null,
rank: Number.isFinite(s.rank) ? s.rank : idx,
}));
const { error } = await adminSupabase
.from("featured_studios")
.upsert(rows as any, { onConflict: "name" as any });
if (error) return res.status(500).json({ error: error.message });
res.json({ ok: true, count: rows.length });
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/achievements/award", async (req, res) => {
const { user_id, achievement_names } = req.body || {};
if (!user_id) return res.status(400).json({ error: "user_id required" });
const names: string[] =
Array.isArray(achievement_names) && achievement_names.length
? achievement_names
: ["Welcome to AeThex"];
try {
const { data: achievements, error: aErr } = await adminSupabase
.from("achievements")
.select("id, name")
.in("name", names);
if (aErr) return res.status(500).json({ error: aErr.message });
const rows = (achievements || []).map((a: any) => ({
user_id,
achievement_id: a.id,
}));
if (!rows.length) return res.json({ ok: true, awarded: [] });
const { error: iErr } = await adminSupabase
.from("user_achievements")
.upsert(rows, { onConflict: "user_id,achievement_id" as any });
if (iErr && iErr.code !== "23505")
return res.status(500).json({ error: iErr.message });
return res.json({ ok: true, awarded: rows.length });
} catch (e: any) {
console.error("[API] achievements/award exception", e);
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/achievements/activate", async (req, res) => {
try {
const CORE_ACHIEVEMENTS = [
{
id: "welcome-to-aethex",
name: "Welcome to AeThex",
description: "Completed onboarding and joined the AeThex network.",
icon: "🎉",
badge_color: "#7C3AED",
xp_reward: 250,
},
{
id: "aethex-explorer",
name: "AeThex Explorer",
description:
"Engaged with community initiatives and posted first update.",
icon: "🧭",
badge_color: "#0EA5E9",
xp_reward: 400,
},
{
id: "community-champion",
name: "Community Champion",
description:
"Contributed feedback, resolved bugs, and mentored squads.",
icon: "🏆",
badge_color: "#22C55E",
xp_reward: 750,
},
{
id: "workshop-architect",
name: "Workshop Architect",
description:
"Published a high-impact mod or toolkit adopted by teams.",
icon: "<22><>",
badge_color: "#F97316",
xp_reward: 1200,
},
{
id: "god-mode",
name: "GOD Mode",
description:
"Legendary status awarded by AeThex studio leadership.",
icon: "⚡",
badge_color: "#FACC15",
xp_reward: 5000,
},
];
const nowIso = new Date().toISOString();
const achievementResults = await Promise.all(
CORE_ACHIEVEMENTS.map(async (achievement) => {
const { error } = await adminSupabase
.from("achievements")
.upsert(achievement, { onConflict: "id" });
if (error) {
console.error(
`Failed to upsert achievement ${achievement.id}:`,
error,
);
throw error;
}
return achievement.id;
}),
);
return res.json({
ok: true,
achievementsSeeded: achievementResults.length,
achievements: achievementResults,
});
} catch (error: any) {
console.error("activate achievements error", error);
return res.status(500).json({
error: error?.message || "Failed to activate achievements",
});
}
});
// Blog endpoints (Supabase-backed)
app.get("/api/blog", async (req, res) => {
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 12));
const category = String(req.query.category || "").trim();
try {
let query = adminSupabase
.from("blog_posts")
.select(
"id, slug, title, excerpt, author, date, read_time, category, image, likes, comments, published_at",
)
.order("published_at", { ascending: false, nullsLast: true } as any)
.limit(limit);
if (category) query = query.eq("category", category);
const { data, error } = await query;
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/blog/:slug", async (req, res) => {
const slug = String(req.params.slug || "");
if (!slug) return res.status(400).json({ error: "missing slug" });
try {
const { data, error } = await adminSupabase
.from("blog_posts")
.select(
"id, slug, title, excerpt, author, date, read_time, category, image, body_html, published_at",
)
.eq("slug", slug)
.single();
if (error) return res.status(404).json({ error: error.message });
res.json(data || null);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/blog", async (req, res) => {
const p = req.body || {};
const row = {
slug: String(p.slug || "").trim(),
title: String(p.title || "").trim(),
excerpt: p.excerpt || null,
author: p.author || null,
date: p.date || null,
read_time: p.read_time || null,
category: p.category || null,
image: p.image || null,
likes: Number.isFinite(p.likes) ? p.likes : 0,
comments: Number.isFinite(p.comments) ? p.comments : 0,
body_html: p.body_html || null,
published_at: p.published_at || new Date().toISOString(),
} as any;
if (!row.slug || !row.title)
return res.status(400).json({ error: "slug and title required" });
try {
const { data, error } = await adminSupabase
.from("blog_posts")
.upsert(row, { onConflict: "slug" as any })
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
res.json(data || row);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.delete("/api/blog/:slug", async (req, res) => {
const slug = String(req.params.slug || "");
if (!slug) return res.status(400).json({ error: "missing slug" });
try {
const { error } = await adminSupabase
.from("blog_posts")
.delete()
.eq("slug", slug);
if (error) return res.status(500).json({ error: error.message });
res.json({ ok: true });
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/applications-old", async (req, res) => {
const owner = String(req.query.owner || "");
if (!owner) return res.status(400).json({ error: "owner required" });
try {
const { data, error } = await adminSupabase
.from("aethex_applications")
.select(`*`)
.eq("creator_id", owner)
.order("applied_at", { ascending: false })
.limit(50);
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/opportunities/applications", async (req, res) => {
const requester = String(req.query.email || "").toLowerCase();
if (!requester || requester !== ownerEmail) {
return res.status(403).json({ error: "forbidden" });
}
try {
const { data, error } = await adminSupabase
.from("applications")
.select("*")
.order("submitted_at", { ascending: false })
.limit(200);
if (error) {
if (isTableMissing(error)) {
return res.json([]);
}
return res.status(500).json({ error: error.message });
}
return res.json(data || []);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Invites API
const baseUrl =
process.env.PUBLIC_BASE_URL ||
process.env.SITE_URL ||
"https://aethex.biz";
const safeEmail = (v?: string | null) => (v || "").trim().toLowerCase();
const accrue = async (
userId: string,
kind: "xp" | "loyalty" | "reputation",
amount: number,
type: string,
meta?: any,
) => {
const amt = Math.max(0, Math.floor(amount));
try {
await adminSupabase.from("reward_events").insert({
user_id: userId,
type,
points_kind: kind,
amount: amt,
metadata: meta || null,
});
} catch {}
const col =
kind === "xp"
? "total_xp"
: kind === "loyalty"
? "loyalty_points"
: "reputation_score";
const { data: row } = await adminSupabase
.from("user_profiles")
.select(`id, ${col}, level`)
.eq("id", userId)
.maybeSingle();
const current = Number((row as any)?.[col] || 0);
const updates: any = { [col]: current + amt };
if (col === "total_xp") {
const total = current + amt;
updates.level = Math.max(1, Math.floor(total / 1000) + 1);
}
await adminSupabase
.from("user_profiles")
.update(updates)
.eq("id", userId);
};
app.post("/api/invites", async (req, res) => {
const { inviter_id, invitee_email, message } = (req.body || {}) as {
inviter_id?: string;
invitee_email?: string;
message?: string | null;
};
if (!inviter_id || !invitee_email) {
return res
.status(400)
.json({ error: "inviter_id and invitee_email are required" });
}
const email = safeEmail(invitee_email);
const token = randomUUID();
try {
const { data: inviterProfile } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", inviter_id)
.maybeSingle();
const inviterName =
(inviterProfile as any)?.full_name ||
(inviterProfile as any)?.username ||
"An AeThex member";
const { data, error } = await adminSupabase
.from("invites")
.insert({
inviter_id,
invitee_email: email,
token,
message: message || null,
status: "pending",
})
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
const inviteUrl = `${baseUrl}/login?invite=${encodeURIComponent(token)}`;
if (emailService.isConfigured) {
try {
await emailService.sendInviteEmail({
to: email,
inviteUrl,
inviterName,
message: message || null,
});
} catch (e: any) {
console.warn("Failed to send invite email", e?.message || e);
}
}
await accrue(inviter_id, "loyalty", 5, "invite_sent", {
invitee: email,
});
try {
await adminSupabase.from("notifications").insert({
user_id: inviter_id,
type: "info",
title: "Invite sent",
message: `Invitation sent to ${email}`,
});
} catch {}
return res.json({ ok: true, invite: data, inviteUrl, token });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/invites", async (req, res) => {
const inviter = String(req.query.inviter_id || "");
if (!inviter)
return res.status(400).json({ error: "inviter_id required" });
try {
const { data, error } = await adminSupabase
.from("invites")
.select("*")
.eq("inviter_id", inviter)
.order("created_at", { ascending: false });
if (error) return res.status(500).json({ error: error.message });
return res.json(data || []);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/invites/accept", async (req, res) => {
const { token, acceptor_id } = (req.body || {}) as {
token?: string;
acceptor_id?: string;
};
if (!token || !acceptor_id) {
return res
.status(400)
.json({ error: "token and acceptor_id required" });
}
try {
const { data: invite, error } = await adminSupabase
.from("invites")
.select("*")
.eq("token", token)
.eq("status", "pending")
.maybeSingle();
if (error) return res.status(500).json({ error: error.message });
if (!invite) return res.status(404).json({ error: "invalid_invite" });
const now = new Date().toISOString();
const { error: upErr } = await adminSupabase
.from("invites")
.update({
status: "accepted",
accepted_by: acceptor_id,
accepted_at: now,
})
.eq("id", (invite as any).id);
if (upErr) return res.status(500).json({ error: upErr.message });
const inviterId = (invite as any).inviter_id as string;
if (inviterId && inviterId !== acceptor_id) {
await adminSupabase
.from("user_connections")
.upsert({ user_id: inviterId, connection_id: acceptor_id } as any)
.catch(() => undefined);
await adminSupabase
.from("user_connections")
.upsert({ user_id: acceptor_id, connection_id: inviterId } as any)
.catch(() => undefined);
}
if (inviterId) {
await accrue(inviterId, "xp", 100, "invite_accepted", { token });
await accrue(inviterId, "loyalty", 50, "invite_accepted", { token });
await accrue(inviterId, "reputation", 2, "invite_accepted", {
token,
});
try {
await adminSupabase.from("notifications").insert({
user_id: inviterId,
type: "success",
title: "Invite accepted",
message: "Your invitation was accepted. You're now connected.",
});
} catch {}
}
await accrue(acceptor_id, "xp", 50, "invite_accepted", { token });
await accrue(acceptor_id, "reputation", 1, "invite_accepted", {
token,
});
try {
await adminSupabase.from("notifications").insert({
user_id: acceptor_id,
type: "success",
title: "Connected",
message: "Connection established via invitation.",
});
} catch {}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Follow/unfollow with notifications
app.post("/api/social/follow", async (req, res) => {
const { follower_id, following_id } = (req.body || {}) as {
follower_id?: string;
following_id?: string;
};
if (!follower_id || !following_id)
return res
.status(400)
.json({ error: "follower_id and following_id required" });
try {
await adminSupabase
.from("user_follows")
.upsert({ follower_id, following_id } as any, {
onConflict: "follower_id,following_id" as any,
});
await accrue(follower_id, "loyalty", 5, "follow_user", {
following_id,
});
const { data: follower } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", follower_id)
.maybeSingle();
const followerName =
(follower as any)?.full_name ||
(follower as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: following_id,
type: "info",
title: "New follower",
message: `${followerName} started following you`,
});
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/social/unfollow", async (req, res) => {
const { follower_id, following_id } = (req.body || {}) as {
follower_id?: string;
following_id?: string;
};
if (!follower_id || !following_id)
return res
.status(400)
.json({ error: "follower_id and following_id required" });
try {
await adminSupabase
.from("user_follows")
.delete()
.eq("follower_id", follower_id)
.eq("following_id", following_id);
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Community post likes
app.post("/api/community/posts/:id/like", async (req, res) => {
const postId = req.params.id;
const { user_id } = (req.body || {}) as { user_id?: string };
if (!postId || !user_id)
return res.status(400).json({ error: "post id and user_id required" });
try {
const { error: likeErr } = await adminSupabase
.from("community_post_likes")
.upsert({ post_id: postId, user_id } as any, {
onConflict: "post_id,user_id" as any,
});
if (likeErr) return res.status(500).json({ error: likeErr.message });
const { data: c } = await adminSupabase
.from("community_post_likes")
.select("post_id", { count: "exact", head: true })
.eq("post_id", postId);
const count = (c as any)?.length
? (c as any).length
: (c as any)?.count || null;
if (typeof count === "number") {
await adminSupabase
.from("community_posts")
.update({ likes_count: count })
.eq("id", postId);
}
return res.json({
ok: true,
likes: typeof count === "number" ? count : undefined,
});
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/community/posts/:id/unlike", async (req, res) => {
const postId = req.params.id;
const { user_id } = (req.body || {}) as { user_id?: string };
if (!postId || !user_id)
return res.status(400).json({ error: "post id and user_id required" });
try {
await adminSupabase
.from("community_post_likes")
.delete()
.eq("post_id", postId)
.eq("user_id", user_id);
const { data: c } = await adminSupabase
.from("community_post_likes")
.select("post_id", { count: "exact", head: true })
.eq("post_id", postId);
const count = (c as any)?.length
? (c as any).length
: (c as any)?.count || null;
if (typeof count === "number") {
await adminSupabase
.from("community_posts")
.update({ likes_count: count })
.eq("id", postId);
}
return res.json({
ok: true,
likes: typeof count === "number" ? count : undefined,
});
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Community post comments
app.get("/api/community/posts/:id/comments", async (req, res) => {
const postId = req.params.id;
try {
const { data, error } = await adminSupabase
.from("community_comments")
.select(
"*, user_profiles:user_id ( id, full_name, username, avatar_url )",
)
.eq("post_id", postId)
.order("created_at", { ascending: true });
if (error) return res.status(500).json({ error: error.message });
res.json(data || []);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/community/posts/:id/comments", async (req, res) => {
const postId = req.params.id;
const { user_id, content } = (req.body || {}) as {
user_id?: string;
content?: string;
};
if (!user_id || !content)
return res.status(400).json({ error: "user_id and content required" });
try {
const { data, error } = await adminSupabase
.from("community_comments")
.insert({ post_id: postId, user_id, content } as any)
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
const { data: agg } = await adminSupabase
.from("community_comments")
.select("post_id", { count: "exact", head: true })
.eq("post_id", postId);
const count = (agg as any)?.count || null;
if (typeof count === "number") {
await adminSupabase
.from("community_posts")
.update({ comments_count: count })
.eq("id", postId);
}
res.json(data);
} catch (e: any) {
res.status(500).json({ error: e?.message || String(e) });
}
});
// Endorse with notification
app.post("/api/social/endorse", async (req, res) => {
const { endorser_id, endorsed_id, skill } = (req.body || {}) as {
endorser_id?: string;
endorsed_id?: string;
skill?: string;
};
if (!endorser_id || !endorsed_id || !skill)
return res
.status(400)
.json({ error: "endorser_id, endorsed_id, skill required" });
try {
await adminSupabase
.from("endorsements")
.insert({ endorser_id, endorsed_id, skill } as any);
await accrue(endorsed_id, "reputation", 2, "endorsement_received", {
skill,
from: endorser_id,
});
const { data: endorser } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", endorser_id)
.maybeSingle();
const endorserName =
(endorser as any)?.full_name ||
(endorser as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: endorsed_id,
type: "success",
title: "New endorsement",
message: `${endorserName} endorsed you for ${skill}`,
});
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Activity bus: publish event and fanout notifications
app.post("/api/activity/publish", async (req, res) => {
const {
actor_id,
verb,
object_type,
object_id,
target_user_ids,
target_team_id,
target_project_id,
metadata,
} = (req.body || {}) as any;
if (!actor_id || !verb || !object_type) {
return res
.status(400)
.json({ error: "actor_id, verb, object_type required" });
}
try {
const { data: eventRow, error: evErr } = await adminSupabase
.from("activity_events")
.insert({
actor_id,
verb,
object_type,
object_id: object_id || null,
target_id: target_team_id || target_project_id || null,
metadata: metadata || null,
} as any)
.select()
.single();
if (evErr) return res.status(500).json({ error: evErr.message });
const notify = async (
userId: string,
title: string,
message?: string,
) => {
await adminSupabase.from("notifications").insert({
user_id: userId,
type: "info",
title,
message: message || null,
});
};
// Notify explicit targets
if (Array.isArray(target_user_ids) && target_user_ids.length) {
for (const uid of target_user_ids) {
await notify(
uid,
`${verb} · ${object_type}`,
(metadata && metadata.summary) || null,
);
}
}
// Notify team members if provided
if (target_team_id) {
const { data: members } = await adminSupabase
.from("team_memberships")
.select("user_id")
.eq("team_id", target_team_id);
for (const m of members || []) {
await notify(
(m as any).user_id,
`${verb} · ${object_type}`,
(metadata && metadata.summary) || null,
);
}
}
// Notify project members if provided
if (target_project_id) {
const { data: members } = await adminSupabase
.from("project_members")
.select("user_id")
.eq("project_id", target_project_id);
for (const m of members || []) {
await notify(
(m as any).user_id,
`${verb} · ${object_type}`,
(metadata && metadata.summary) || null,
);
}
}
return res.json({ ok: true, event: eventRow });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/rewards/apply", async (req, res) => {
const { user_id, action, amount } = (req.body || {}) as {
user_id?: string;
action?: string;
amount?: number | null;
};
if (!user_id || !action) {
return res.status(400).json({ error: "user_id and action required" });
}
try {
const actionKey = String(action);
switch (actionKey) {
case "post_created":
await accrue(user_id, "xp", amount ?? 25, actionKey);
await accrue(user_id, "loyalty", 5, actionKey);
break;
case "follow_user":
await accrue(user_id, "loyalty", 5, actionKey);
break;
case "endorsement_received":
await accrue(user_id, "reputation", amount ?? 2, actionKey);
break;
case "daily_login":
await accrue(user_id, "xp", amount ?? 10, actionKey);
await accrue(user_id, "loyalty", 2, actionKey);
break;
default:
await accrue(user_id, "xp", Math.max(0, amount ?? 0), actionKey);
break;
}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Investors: capture interest
app.post("/api/investors/interest", async (req, res) => {
const { name, email, amount, accredited, message } = (req.body || {}) as {
name?: string;
email?: string;
amount?: string;
accredited?: boolean;
message?: string;
};
if (!email) return res.status(400).json({ error: "email required" });
try {
const subject = `Investor interest: ${name || email}`;
const body = [
`Name: ${name || "N/A"}`,
`Email: ${email}`,
`Amount: ${amount || "N/A"}`,
`Accredited: ${accredited ? "Yes" : "No / Unknown"}`,
message ? `\nMessage:\n${message}` : "",
].join("\n");
try {
const { emailService } = await import("./email");
if (emailService.isConfigured) {
await (emailService as any).sendInviteEmail({
to: process.env.VERIFY_SUPPORT_EMAIL || "support@aethex.biz",
inviteUrl: "https://aethex.dev/investors",
inviterName: name || email,
message: body,
});
}
} catch (e) {
/* ignore email errors to not block */
}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Leads: capture website leads (Wix microsite and others)
app.post("/api/leads", async (req, res) => {
const {
name,
email,
company,
website,
budget,
timeline,
message,
source,
} = (req.body || {}) as {
name?: string;
email?: string;
company?: string;
website?: string;
budget?: string;
timeline?: string;
message?: string;
source?: string;
};
if (!email) return res.status(400).json({ error: "email required" });
try {
const lines = [
`Source: ${source || "web"}`,
`Name: ${name || "N/A"}`,
`Email: ${email}`,
`Company: ${company || "N/A"}`,
`Website: ${website || "N/A"}`,
`Budget: ${budget || "N/A"}`,
`Timeline: ${timeline || "N/A"}`,
message ? `\nMessage:\n${message}` : "",
].join("\n");
try {
if (emailService.isConfigured) {
const base =
process.env.PUBLIC_BASE_URL ||
process.env.SITE_URL ||
"https://aethex.dev";
await (emailService as any).sendInviteEmail({
to: process.env.VERIFY_SUPPORT_EMAIL || "support@aethex.biz",
inviteUrl: `${base}/wix`,
inviterName: name || email,
message: lines,
});
}
} catch (e) {
// continue even if email fails
}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Roblox inbound callback (from HttpService or external automation)
app.get("/roblox-callback", (_req, res) => res.json({ ok: true }));
app.post("/roblox-callback", async (req, res) => {
const shared =
process.env.ROBLOX_SHARED_SECRET ||
process.env.ROBLOX_WEBHOOK_SECRET ||
"";
const sig = String(
req.get("x-shared-secret") || req.get("x-roblox-signature") || "",
);
if (shared && sig !== shared)
return res.status(401).json({ error: "unauthorized" });
try {
const payload = {
...((req.body as any) || {}),
ip: (req.headers["x-forwarded-for"] as string) || req.ip,
ua: req.get("user-agent") || null,
received_at: new Date().toISOString(),
};
// Best-effort persist if table exists
try {
await adminSupabase.from("roblox_events").insert({
event_type: (payload as any).event || null,
payload,
} as any);
} catch (e: any) {
// ignore if table missing or RLS blocks
}
return res.json({ ok: true });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Staff: users search/listing
app.get("/api/staff/users", async (req, res) => {
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 20));
const q = String(req.query.q || "")
.trim()
.toLowerCase();
try {
const { data, error } = await adminSupabase
.from("user_profiles")
.select(
"id, username, full_name, avatar_url, user_type, created_at, updated_at",
)
.order("created_at", { ascending: false })
.limit(limit);
if (error) return res.status(500).json({ error: error.message });
let rows = (data || []) as any[];
if (q) {
rows = rows.filter((r) => {
const name = String(r.full_name || r.username || "").toLowerCase();
return name.includes(q);
});
}
return res.json(rows);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Mentorship API
app.get("/api/mentors", async (req, res) => {
const limit = Math.max(1, Math.min(50, Number(req.query.limit) || 20));
const available =
String(req.query.available || "true").toLowerCase() !== "false";
const expertise = String(req.query.expertise || "").trim();
const q = String(req.query.q || "")
.trim()
.toLowerCase();
try {
const { data, error } = await adminSupabase
.from("mentors")
.select(
`user_id, bio, expertise, available, hourly_rate, created_at, updated_at, user_profiles:user_id ( id, username, full_name, avatar_url, bio )`,
)
.eq("available", available)
.order("updated_at", { ascending: false })
.limit(limit);
if (error) {
if (isTableMissing(error)) return res.json([]);
return res.status(500).json({ error: error.message });
}
let rows = (data || []) as any[];
if (expertise) {
const terms = expertise
.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean);
if (terms.length) {
rows = rows.filter(
(r: any) =>
Array.isArray(r.expertise) &&
r.expertise.some((e: string) =>
terms.includes(String(e).toLowerCase()),
),
);
}
}
if (q) {
rows = rows.filter((r: any) => {
const up = (r as any).user_profiles || {};
const name = String(
up.full_name || up.username || "",
).toLowerCase();
const bio = String(r.bio || up.bio || "").toLowerCase();
return name.includes(q) || bio.includes(q);
});
}
return res.json(rows);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// System Status
app.get("/api/status", async (req, res) => {
const startedAt = Date.now();
const host = `${req.protocol}://${req.get("host")}`;
const time = async (fn: () => Promise<any>) => {
const t0 = Date.now();
try {
await fn();
return { ok: true, ms: Date.now() - t0 };
} catch (e) {
return {
ok: false,
ms: Date.now() - t0,
error: (e as any)?.message || String(e),
};
}
};
// Database check (user_profiles)
const dbCheck = await time(async () => {
await adminSupabase
.from("user_profiles")
.select("id", { head: true, count: "exact" })
.limit(1);
});
// API/Core check (community_posts)
const apiCheck = await time(async () => {
await adminSupabase
.from("community_posts")
.select("id", { head: true, count: "exact" })
.limit(1);
});
// Auth check
const authCheck = await time(async () => {
const admin = (adminSupabase as any)?.auth?.admin;
if (!admin) throw new Error("auth admin unavailable");
await admin.listUsers({ page: 1, perPage: 1 } as any);
});
// CDN/static
const cdnCheck = await time(async () => {
const resp = await fetch(`${host}/robots.txt`).catch(() => null);
if (!resp || !resp.ok) throw new Error("robots not reachable");
});
const statusFrom = (c: { ok: boolean; ms: number }) =>
!c.ok ? "outage" : c.ms > 800 ? "degraded" : "operational";
const nowIso = new Date().toISOString();
const services = [
{
name: "AeThex Core API",
status: statusFrom(apiCheck) as any,
responseTime: apiCheck.ms,
uptime: apiCheck.ok ? "99.99%" : "--",
lastCheck: nowIso,
description: "Main application API and endpoints",
},
{
name: "Database Services",
status: statusFrom(dbCheck) as any,
responseTime: dbCheck.ms,
uptime: dbCheck.ok ? "99.99%" : "--",
lastCheck: nowIso,
description: "Supabase Postgres and Storage",
},
{
name: "CDN & Assets",
status: statusFrom(cdnCheck) as any,
responseTime: cdnCheck.ms,
uptime: cdnCheck.ok ? "99.95%" : "--",
lastCheck: nowIso,
description: "Static and media delivery",
},
{
name: "Authentication",
status: statusFrom(authCheck) as any,
responseTime: authCheck.ms,
uptime: authCheck.ok ? "99.97%" : "--",
lastCheck: nowIso,
description: "OAuth and email auth (Supabase)",
},
];
const avgRt = Math.round(
services.reduce((a, s) => a + (Number(s.responseTime) || 0), 0) /
services.length,
);
const errCount = services.filter((s) => s.status === "outage").length;
const warnCount = services.filter((s) => s.status === "degraded").length;
// Active users (best effort)
let activeUsers = "--";
try {
const { count } = await adminSupabase
.from("user_profiles")
.select("id", { head: true, count: "exact" });
if (typeof count === "number") activeUsers = count.toLocaleString();
} catch {}
const metrics = [
{
name: "Global Uptime",
value: errCount ? "99.5" : warnCount ? "99.9" : "99.99",
unit: "%",
status: errCount ? "critical" : warnCount ? "warning" : "good",
icon: "Activity",
},
{
name: "Response Time",
value: String(avgRt),
unit: "ms",
status: avgRt > 800 ? "critical" : avgRt > 400 ? "warning" : "good",
icon: "Zap",
},
{
name: "Active Users",
value: activeUsers,
unit: "",
status: "good",
icon: "Globe",
},
{
name: "Error Rate",
value: String(errCount),
unit: " outages",
status: errCount ? "critical" : warnCount ? "warning" : "good",
icon: "Shield",
},
];
res.json({
updatedAt: new Date().toISOString(),
durationMs: Date.now() - startedAt,
services,
metrics,
incidents: [],
});
});
app.post("/api/mentors/apply", async (req, res) => {
const { user_id, bio, expertise, hourly_rate, available } = (req.body ||
{}) as {
user_id?: string;
bio?: string | null;
expertise?: string[];
hourly_rate?: number | null;
available?: boolean | null;
};
if (!user_id) return res.status(400).json({ error: "user_id required" });
try {
const payload: any = {
user_id,
bio: bio ?? null,
expertise: Array.isArray(expertise) ? expertise : [],
available: available ?? true,
hourly_rate: typeof hourly_rate === "number" ? hourly_rate : null,
};
const { data, error } = await adminSupabase
.from("mentors")
.upsert(payload, { onConflict: "user_id" as any })
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
try {
await adminSupabase.from("notifications").insert({
user_id,
type: "success",
title: "Mentor profile updated",
message: "Your mentor availability and expertise are saved.",
});
} catch {}
return res.json(data || payload);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/mentorship/request", async (req, res) => {
const { mentee_id, mentor_id, message } = (req.body || {}) as {
mentee_id?: string;
mentor_id?: string;
message?: string | null;
};
if (!mentee_id || !mentor_id) {
return res
.status(400)
.json({ error: "mentee_id and mentor_id required" });
}
if (mentee_id === mentor_id) {
return res.status(400).json({ error: "cannot request yourself" });
}
try {
const { data, error } = await adminSupabase
.from("mentorship_requests")
.insert({ mentee_id, mentor_id, message: message || null } as any)
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
try {
const { data: mentee } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", mentee_id)
.maybeSingle();
const menteeName =
(mentee as any)?.full_name ||
(mentee as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: mentor_id,
type: "info",
title: "Mentorship request",
message: `${menteeName} requested mentorship.`,
});
} catch {}
return res.json({ ok: true, request: data });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/mentorship/requests/:id/status", async (req, res) => {
const id = String(req.params.id || "");
const { actor_id, status } = (req.body || {}) as {
actor_id?: string;
status?: string;
};
if (!id || !actor_id || !status) {
return res.status(400).json({ error: "id, actor_id, status required" });
}
const allowed = ["accepted", "rejected", "cancelled"];
if (!allowed.includes(String(status))) {
return res.status(400).json({ error: "invalid status" });
}
try {
const { data: reqRow, error } = await adminSupabase
.from("mentorship_requests")
.select("id, mentor_id, mentee_id, status")
.eq("id", id)
.maybeSingle();
if (error) return res.status(500).json({ error: error.message });
if (!reqRow) return res.status(404).json({ error: "not_found" });
const isMentor = (reqRow as any).mentor_id === actor_id;
const isMentee = (reqRow as any).mentee_id === actor_id;
if ((status === "accepted" || status === "rejected") && !isMentor) {
return res.status(403).json({ error: "forbidden" });
}
if (status === "cancelled" && !isMentee) {
return res.status(403).json({ error: "forbidden" });
}
const { data, error: upErr } = await adminSupabase
.from("mentorship_requests")
.update({ status })
.eq("id", id)
.select()
.single();
if (upErr) return res.status(500).json({ error: upErr.message });
try {
const target =
status === "cancelled"
? (reqRow as any).mentor_id
: (reqRow as any).mentee_id;
const title =
status === "accepted"
? "Mentorship accepted"
: status === "rejected"
? "Mentorship rejected"
: "Mentorship cancelled";
await adminSupabase.from("notifications").insert({
user_id: target,
type: "info",
title,
message: null,
});
} catch {}
return res.json({ ok: true, request: data });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/mentorship/requests", async (req, res) => {
const userId = String(req.query.user_id || "");
const role = String(req.query.role || "").toLowerCase();
if (!userId) return res.status(400).json({ error: "user_id required" });
try {
let query = adminSupabase
.from("mentorship_requests")
.select(
`*, mentor:user_profiles!mentorship_requests_mentor_id_fkey ( id, full_name, username, avatar_url ), mentee:user_profiles!mentorship_requests_mentee_id_fkey ( id, full_name, username, avatar_url )`,
)
.order("created_at", { ascending: false });
if (role === "mentor") query = query.eq("mentor_id", userId);
else if (role === "mentee") query = query.eq("mentee_id", userId);
else query = query.or(`mentor_id.eq.${userId},mentee_id.eq.${userId}`);
const { data, error } = await query;
if (error) {
if (isTableMissing(error)) return res.json([]);
return res.status(500).json({ error: error.message });
}
return res.json(data || []);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Staff: list all mentorship requests (limited)
app.get("/api/mentorship/requests/all", async (req, res) => {
const limit = Math.max(1, Math.min(200, Number(req.query.limit) || 50));
const status = String(req.query.status || "").toLowerCase();
try {
let query = adminSupabase
.from("mentorship_requests")
.select(
`*, mentor:user_profiles!mentorship_requests_mentor_id_fkey ( id, full_name, username, avatar_url ), mentee:user_profiles!mentorship_requests_mentee_id_fkey ( id, full_name, username, avatar_url )`,
)
.order("created_at", { ascending: false })
.limit(limit);
if (status) query = query.eq("status", status);
const { data, error } = await query;
if (error) return res.status(500).json({ error: error.message });
return res.json(data || []);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Moderation API
app.post("/api/moderation/reports", async (req, res) => {
const { reporter_id, target_type, target_id, reason, details } =
(req.body || {}) as any;
if (!target_type || !reason) {
return res
.status(400)
.json({ error: "target_type and reason required" });
}
try {
const { data, error } = await adminSupabase
.from("moderation_reports")
.insert({
reporter_id: reporter_id || null,
target_type,
target_id: target_id || null,
reason,
details: details || null,
} as any)
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
return res.json(data);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/moderation/reports", async (req, res) => {
const status = String(req.query.status || "open").toLowerCase();
const limit = Math.max(1, Math.min(200, Number(req.query.limit) || 50));
try {
const { data, error } = await adminSupabase
.from("moderation_reports")
.select(
`*, reporter:user_profiles!moderation_reports_reporter_id_fkey ( id, full_name, username, avatar_url )`,
)
.eq("status", status)
.order("created_at", { ascending: false })
.limit(limit);
if (error) return res.status(500).json({ error: error.message });
return res.json(data || []);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/moderation/reports/:id/status", async (req, res) => {
const id = String(req.params.id || "");
const { status } = (req.body || {}) as { status?: string };
const allowed = ["open", "resolved", "ignored"];
if (!id || !status || !allowed.includes(String(status))) {
return res.status(400).json({ error: "invalid input" });
}
try {
const { data, error } = await adminSupabase
.from("moderation_reports")
.update({ status })
.eq("id", id)
.select()
.single();
if (error) return res.status(500).json({ error: error.message });
return res.json(data);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Creator Network API Routes
// Get all creators with filters
app.get("/api/creators", async (req, res) => {
try {
const arm = String(req.query.arm || "").trim();
const page = Math.max(1, Number(req.query.page) || 1);
const limit = Math.max(1, Math.min(100, Number(req.query.limit) || 20));
const search = String(req.query.search || "").trim();
let query = adminSupabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
created_at
`,
{ 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 res.json({
data,
pagination: {
page,
limit,
total: count || 0,
pages: Math.ceil((count || 0) / limit),
},
});
} catch (e: any) {
console.error("[Creator API] Error fetching creators:", e?.message);
return res.status(500).json({ error: "Failed to fetch creators" });
}
});
// Get creator by username
app.get("/api/creators/:username", async (req, res) => {
try {
const username = String(req.params.username || "").trim();
if (!username) {
return res.status(400).json({ error: "username required" });
}
const { data: creator, error } = await adminSupabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
created_at,
updated_at
`,
)
.eq("username", username)
.eq("is_discoverable", true)
.single();
if (error) {
if (error.code === "PGRST116") {
return res.status(404).json({ error: "Creator not found" });
}
throw error;
}
const { data: devConnectLink } = await adminSupabase
.from("aethex_devconnect_links")
.select("devconnect_username, devconnect_profile_url")
.eq("aethex_creator_id", creator.id)
.maybeSingle();
return res.json({
...creator,
devconnect_link: devConnectLink,
});
} catch (e: any) {
console.error("[Creator API] Error fetching creator:", e?.message);
return res.status(500).json({ error: "Failed to fetch creator" });
}
});
// Create creator profile
app.post("/api/creators", async (req, res) => {
try {
const {
user_id,
username,
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
} = req.body;
if (!user_id || !username) {
return res
.status(400)
.json({ error: "user_id and username required" });
}
const { data, error } = await adminSupabase
.from("aethex_creators")
.insert({
user_id,
username,
bio: bio || null,
skills: Array.isArray(skills) ? skills : [],
avatar_url: avatar_url || null,
experience_level: experience_level || null,
primary_arm: primary_arm || null,
arm_affiliations: Array.isArray(arm_affiliations)
? arm_affiliations
: [],
})
.select()
.single();
if (error) {
if (error.code === "23505") {
return res.status(400).json({ error: "Username already taken" });
}
throw error;
}
return res.status(201).json(data);
} catch (e: any) {
console.error("[Creator API] Error creating creator:", e?.message);
return res
.status(500)
.json({ error: "Failed to create creator profile" });
}
});
// Update creator profile
app.put("/api/creators/:id", async (req, res) => {
try {
const creatorId = String(req.params.id || "").trim();
if (!creatorId) {
return res.status(400).json({ error: "creator id required" });
}
const {
bio,
skills,
avatar_url,
experience_level,
primary_arm,
arm_affiliations,
is_discoverable,
allow_recommendations,
} = req.body;
const { data, error } = await adminSupabase
.from("aethex_creators")
.update({
bio,
skills: Array.isArray(skills) ? skills : undefined,
avatar_url,
experience_level,
primary_arm,
arm_affiliations: Array.isArray(arm_affiliations)
? arm_affiliations
: undefined,
is_discoverable,
allow_recommendations,
updated_at: new Date().toISOString(),
})
.eq("id", creatorId)
.select()
.single();
if (error) throw error;
return res.json(data);
} catch (e: any) {
console.error("[Creator API] Error updating creator:", e?.message);
return res
.status(500)
.json({ error: "Failed to update creator profile" });
}
});
// Get all opportunities with filters
app.get("/api/opportunities", async (req, res) => {
try {
const arm = String(req.query.arm || "").trim();
const page = Math.max(1, Number(req.query.page) || 1);
const limit = Math.max(1, Math.min(100, Number(req.query.limit) || 20));
const sort = String(req.query.sort || "recent");
const search = String(req.query.search || "").trim();
const jobType = String(req.query.jobType || "").trim();
const experienceLevel = String(req.query.experienceLevel || "").trim();
let query = adminSupabase
.from("aethex_opportunities")
.select(
`
id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
arm_affiliation,
posted_by_id,
aethex_creators!aethex_opportunities_posted_by_id_fkey(username, avatar_url),
status,
created_at
`,
{ count: "exact" },
)
.eq("status", "open");
if (arm) {
query = query.eq("arm_affiliation", arm);
}
if (search) {
query = query.or(
`title.ilike.%${search}%,description.ilike.%${search}%`,
);
}
if (jobType) {
query = query.eq("job_type", jobType);
}
if (experienceLevel) {
query = query.eq("experience_level", experienceLevel);
}
const ascending = sort === "oldest";
query = query.order("created_at", { ascending });
const start = (page - 1) * limit;
query = query.range(start, start + limit - 1);
const { data, error, count } = await query;
if (error) throw error;
return res.json({
data,
pagination: {
page,
limit,
total: count || 0,
pages: Math.ceil((count || 0) / limit),
},
});
} catch (e: any) {
console.error(
"[Opportunities API] Error fetching opportunities:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch opportunities" });
}
});
// Get opportunity by ID
app.get("/api/opportunities/:id", async (req, res) => {
try {
const opportunityId = String(req.params.id || "").trim();
if (!opportunityId) {
return res.status(400).json({ error: "opportunity id required" });
}
const { data, error } = await adminSupabase
.from("aethex_opportunities")
.select(
`
id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
arm_affiliation,
posted_by_id,
aethex_creators!aethex_opportunities_posted_by_id_fkey(id, username, bio, avatar_url),
status,
created_at,
updated_at
`,
)
.eq("id", opportunityId)
.eq("status", "open")
.single();
if (error) {
if (error.code === "PGRST116") {
return res.status(404).json({ error: "Opportunity not found" });
}
throw error;
}
return res.json(data);
} catch (e: any) {
console.error(
"[Opportunities API] Error fetching opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch opportunity" });
}
});
// Create opportunity
app.post("/api/opportunities", async (req, res) => {
try {
const {
user_id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
arm_affiliation,
} = req.body;
if (!user_id || !title) {
return res.status(400).json({ error: "user_id and title required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (!creator) {
return res.status(404).json({
error: "Creator profile not found. Create profile first.",
});
}
const { data, error } = await adminSupabase
.from("aethex_opportunities")
.insert({
title,
description: description || null,
job_type: job_type || null,
salary_min: salary_min || null,
salary_max: salary_max || null,
experience_level: experience_level || null,
arm_affiliation: arm_affiliation || null,
posted_by_id: creator.id,
status: "open",
})
.select()
.single();
if (error) throw error;
return res.status(201).json(data);
} catch (e: any) {
console.error(
"[Opportunities API] Error creating opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to create opportunity" });
}
});
// Update opportunity
app.put("/api/opportunities/:id", async (req, res) => {
try {
const opportunityId = String(req.params.id || "").trim();
const {
user_id,
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
status,
} = req.body;
if (!opportunityId || !user_id) {
return res
.status(400)
.json({ error: "opportunity id and user_id required" });
}
const { data: opportunity } = await adminSupabase
.from("aethex_opportunities")
.select("posted_by_id")
.eq("id", opportunityId)
.single();
if (!opportunity) {
return res.status(404).json({ error: "Opportunity not found" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (creator?.id !== opportunity.posted_by_id) {
return res.status(403).json({ error: "Unauthorized" });
}
const { data, error } = await adminSupabase
.from("aethex_opportunities")
.update({
title,
description,
job_type,
salary_min,
salary_max,
experience_level,
status,
updated_at: new Date().toISOString(),
})
.eq("id", opportunityId)
.select()
.single();
if (error) throw error;
return res.json(data);
} catch (e: any) {
console.error(
"[Opportunities API] Error updating opportunity:",
e?.message,
);
return res.status(500).json({ error: "Failed to update opportunity" });
}
});
// Get my applications
app.get("/api/applications", async (req, res) => {
try {
const userId = String(req.query.user_id || "").trim();
const page = Math.max(1, Number(req.query.page) || 1);
const limit = Math.max(1, Math.min(100, Number(req.query.limit) || 10));
const status = String(req.query.status || "").trim();
if (!userId) {
return res.status(400).json({ error: "user_id required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", userId)
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found" });
}
let query = adminSupabase
.from("aethex_applications")
.select(
`
id,
creator_id,
opportunity_id,
status,
cover_letter,
response_message,
applied_at,
updated_at,
aethex_opportunities(id, title, arm_affiliation, job_type, posted_by_id, aethex_creators!aethex_opportunities_posted_by_id_fkey(username, avatar_url))
`,
{ count: "exact" },
)
.eq("creator_id", creator.id);
if (status) {
query = query.eq("status", status);
}
query = query.order("applied_at", { ascending: false });
const start = (page - 1) * limit;
query = query.range(start, start + limit - 1);
const { data, error, count } = await query;
if (error) throw error;
return res.json({
data,
pagination: {
page,
limit,
total: count || 0,
pages: Math.ceil((count || 0) / limit),
},
});
} catch (e: any) {
console.error(
"[Applications API] Error fetching applications:",
e?.message,
);
return res.status(500).json({ error: "Failed to fetch applications" });
}
});
// Submit application
app.post("/api/applications", async (req, res) => {
try {
const { user_id, opportunity_id, cover_letter } = req.body;
if (!user_id || !opportunity_id) {
return res
.status(400)
.json({ error: "user_id and opportunity_id required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found" });
}
const { data: opportunity } = await adminSupabase
.from("aethex_opportunities")
.select("id")
.eq("id", opportunity_id)
.eq("status", "open")
.single();
if (!opportunity) {
return res
.status(404)
.json({ error: "Opportunity not found or closed" });
}
const { data: existing } = await adminSupabase
.from("aethex_applications")
.select("id")
.eq("creator_id", creator.id)
.eq("opportunity_id", opportunity_id)
.maybeSingle();
if (existing) {
return res
.status(400)
.json({ error: "You have already applied to this opportunity" });
}
const { data, error } = await adminSupabase
.from("aethex_applications")
.insert({
creator_id: creator.id,
opportunity_id,
cover_letter: cover_letter || null,
status: "submitted",
})
.select()
.single();
if (error) throw error;
return res.status(201).json(data);
} catch (e: any) {
console.error(
"[Applications API] Error submitting application:",
e?.message,
);
return res.status(500).json({ error: "Failed to submit application" });
}
});
// Update application status
app.put("/api/applications/:id", async (req, res) => {
try {
const applicationId = String(req.params.id || "").trim();
const { user_id, status, response_message } = req.body;
if (!applicationId || !user_id) {
return res
.status(400)
.json({ error: "application id and user_id required" });
}
const { data: application } = await adminSupabase
.from("aethex_applications")
.select(
`
id,
opportunity_id,
aethex_opportunities(posted_by_id)
`,
)
.eq("id", applicationId)
.single();
if (!application) {
return res.status(404).json({ error: "Application not found" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (creator?.id !== application.aethex_opportunities.posted_by_id) {
return res.status(403).json({ error: "Unauthorized" });
}
const { data, error } = await adminSupabase
.from("aethex_applications")
.update({
status,
response_message: response_message || null,
updated_at: new Date().toISOString(),
})
.eq("id", applicationId)
.select()
.single();
if (error) throw error;
return res.json(data);
} catch (e: any) {
console.error(
"[Applications API] Error updating application:",
e?.message,
);
return res.status(500).json({ error: "Failed to update application" });
}
});
// Withdraw application
app.delete("/api/applications/:id", async (req, res) => {
try {
const applicationId = String(req.params.id || "").trim();
const { user_id } = req.body;
if (!applicationId || !user_id) {
return res
.status(400)
.json({ error: "application id and user_id required" });
}
const { data: application } = await adminSupabase
.from("aethex_applications")
.select("creator_id")
.eq("id", applicationId)
.single();
if (!application) {
return res.status(404).json({ error: "Application not found" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (creator?.id !== application.creator_id) {
return res.status(403).json({ error: "Unauthorized" });
}
const { error } = await adminSupabase
.from("aethex_applications")
.delete()
.eq("id", applicationId);
if (error) throw error;
return res.json({ ok: true });
} catch (e: any) {
console.error(
"[Applications API] Error withdrawing application:",
e?.message,
);
return res
.status(500)
.json({ error: "Failed to withdraw application" });
}
});
// Link DevConnect account
app.post("/api/devconnect/link", async (req, res) => {
try {
const { user_id, devconnect_username, devconnect_profile_url } =
req.body;
if (!user_id || !devconnect_username) {
return res
.status(400)
.json({ error: "user_id and devconnect_username required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (!creator) {
return res.status(404).json({
error: "Creator profile not found. Create profile first.",
});
}
const { data: existing } = await adminSupabase
.from("aethex_devconnect_links")
.select("id")
.eq("aethex_creator_id", creator.id)
.maybeSingle();
let result;
let status = 201;
if (existing) {
const { data, error } = await adminSupabase
.from("aethex_devconnect_links")
.update({
devconnect_username,
devconnect_profile_url:
devconnect_profile_url ||
`https://dev-link.me/${devconnect_username}`,
})
.eq("aethex_creator_id", creator.id)
.select()
.single();
if (error) throw error;
result = data;
status = 200;
} else {
const { data, error } = await adminSupabase
.from("aethex_devconnect_links")
.insert({
aethex_creator_id: creator.id,
devconnect_username,
devconnect_profile_url:
devconnect_profile_url ||
`https://dev-link.me/${devconnect_username}`,
})
.select()
.single();
if (error) throw error;
result = data;
}
await adminSupabase
.from("aethex_creators")
.update({ devconnect_linked: true })
.eq("id", creator.id);
return res.status(status).json(result);
} catch (e: any) {
console.error("[DevConnect API] Error linking account:", e?.message);
return res
.status(500)
.json({ error: "Failed to link DevConnect account" });
}
});
// Get DevConnect link
app.get("/api/devconnect/link", async (req, res) => {
try {
const userId = String(req.query.user_id || "").trim();
if (!userId) {
return res.status(400).json({ error: "user_id required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", userId)
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found" });
}
const { data, error } = await adminSupabase
.from("aethex_devconnect_links")
.select("*")
.eq("aethex_creator_id", creator.id)
.maybeSingle();
if (error && error.code !== "PGRST116") {
throw error;
}
return res.json({ data: data || null });
} catch (e: any) {
console.error("[DevConnect API] Error fetching link:", e?.message);
return res
.status(500)
.json({ error: "Failed to fetch DevConnect link" });
}
});
// Unlink DevConnect account
app.delete("/api/devconnect/link", async (req, res) => {
try {
const { user_id } = req.body;
if (!user_id) {
return res.status(400).json({ error: "user_id required" });
}
const { data: creator } = await adminSupabase
.from("aethex_creators")
.select("id")
.eq("user_id", user_id)
.single();
if (!creator) {
return res.status(404).json({ error: "Creator profile not found" });
}
const { error } = await adminSupabase
.from("aethex_devconnect_links")
.delete()
.eq("aethex_creator_id", creator.id);
if (error) throw error;
await adminSupabase
.from("aethex_creators")
.update({ devconnect_linked: false })
.eq("id", creator.id);
return res.json({ ok: true });
} catch (e: any) {
console.error("[DevConnect API] Error unlinking account:", e?.message);
return res
.status(500)
.json({ error: "Failed to unlink DevConnect account" });
}
});
} catch (e) {
console.warn("Admin API not initialized:", e);
}
return app;
}