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, randomBytes } from "crypto";
import blogIndexHandler from "../api/blog/index";
import blogSlugHandler from "../api/blog/[slug]";
// 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���� [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();
});
// Subdomain detection middleware for aethex.me and aethex.space
app.use((req, res, next) => {
const host = (req.headers.host || "").toLowerCase();
const forwarded = (
(req.headers["x-forwarded-host"] as string) || ""
).toLowerCase();
const hostname = forwarded || host;
// Parse subdomain
let subdomain = "";
let domain = "";
if (hostname.includes("aethex.me")) {
const parts = hostname.split(".");
if (parts.length > 2) {
subdomain = parts[0];
domain = "aethex.me";
}
} else if (hostname.includes("aethex.space")) {
const parts = hostname.split(".");
if (parts.length > 2) {
subdomain = parts[0];
domain = "aethex.space";
}
}
// Attach subdomain info to request
(req as any).subdomainInfo = {
subdomain,
domain,
isCreatorPassport: domain === "aethex.me",
isProjectPassport: domain === "aethex.space",
};
if (subdomain) {
console.log("[Subdomain] Detected:", {
hostname,
subdomain,
domain,
isCreatorPassport: domain === "aethex.me",
isProjectPassport: domain === "aethex.space",
});
}
next();
});
// Subdomain Passport Data API - returns JSON for the React component to use
app.get("/api/passport/subdomain-data/:username", async (req, res) => {
try {
const username = String(req.params.username || "")
.toLowerCase()
.trim();
if (!username) {
return res.status(400).json({ error: "username required" });
}
console.log("[Passport Data API] Fetching creator:", username);
const { data: user, error } = await adminSupabase
.from("user_profiles")
.select(
"id, username, full_name, bio, avatar_url, banner_url, location, website_url, github_url, linkedin_url, twitter_url, role, level, total_xp, user_type, experience_level, current_streak, longest_streak, created_at, updated_at",
)
.eq("username", username)
.single();
if (error || !user) {
console.log("[Passport Data API] Creator not found:", username);
return res.status(404).json({ error: "Creator not found", username });
}
// Fetch achievements
const { data: achievements = [] } = await adminSupabase
.from("user_achievements")
.select(
`
achievement_id,
achievements(
id,
name,
description,
icon,
category,
badge_color
)
`,
)
.eq("user_id", user.id);
return res.json({
type: "creator",
user,
achievements: achievements
.map((a: any) => a.achievements)
.filter(Boolean),
domain: "aethex.me",
});
} catch (e: any) {
console.error("[Passport Data API] Error:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to fetch creator passport",
});
}
});
app.get("/api/passport/project-data/:projectSlug", async (req, res) => {
try {
const projectSlug = String(req.params.projectSlug || "")
.toLowerCase()
.trim();
if (!projectSlug) {
return res.status(400).json({ error: "projectSlug required" });
}
console.log("[Passport Data API] Fetching project:", projectSlug);
const { data: project, error } = await adminSupabase
.from("projects")
.select(
"id, slug, name, description, logo_url, banner_url, website_url, github_url, team_size, created_at, updated_at",
)
.eq("slug", projectSlug)
.single();
if (error || !project) {
console.log("[Passport Data API] Project not found:", projectSlug);
return res
.status(404)
.json({ error: "Project not found", projectSlug });
}
return res.json({
type: "group",
group: project,
domain: "aethex.space",
});
} catch (e: any) {
console.error("[Passport Data API] Error:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to fetch project passport",
});
}
});
// Example API routes
app.get("/api/ping", (_req, res) => {
const ping = process.env.PING_MESSAGE ?? "ping";
res.json({ message: ping });
});
// API: Creator passport lookup by subdomain (aethex.me)
app.get("/api/passport/subdomain/:username", async (req, res) => {
try {
const username = String(req.params.username || "")
.toLowerCase()
.trim();
if (!username) {
return res.status(400).json({ error: "username required" });
}
const { data, error } = await adminSupabase
.from("user_profiles")
.select(
"id, username, full_name, avatar_url, user_type, bio, created_at",
)
.eq("username", username)
.single();
if (error || !data) {
return res.status(404).json({
error: "Creator not found",
username,
});
}
return res.json({
type: "creator",
user: data,
domain: "aethex.me",
});
} catch (e: any) {
console.error("[Passport Subdomain] Error:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to fetch creator passport",
});
}
});
// API: Project passport lookup by subdomain (aethex.space)
app.get("/api/passport/project/:projectname", async (req, res) => {
try {
const projectname = String(req.params.projectname || "")
.toLowerCase()
.trim();
if (!projectname) {
return res.status(400).json({ error: "projectname required" });
}
// First try exact match by name
let query = adminSupabase
.from("projects")
.select(
"id, title, slug, description, user_id, created_at, updated_at, status, image_url, website",
)
.eq("slug", projectname);
let { data, error } = await query.single();
// If not found by slug, try by title (case-insensitive)
if (error && error.code === "PGRST116") {
query = adminSupabase
.from("projects")
.select(
"id, title, slug, description, user_id, created_at, updated_at, status, image_url, website",
)
.ilike("title", projectname);
const response = await query;
if (response.data && response.data.length > 0) {
data = response.data[0];
error = null;
}
}
if (error || !data) {
return res.status(404).json({
error: "Project not found",
projectname,
});
}
// Fetch project owner info
const { data: owner } = await adminSupabase
.from("user_profiles")
.select("id, username, full_name, avatar_url")
.eq("id", (data as any).user_id)
.maybeSingle();
return res.json({
type: "project",
project: data,
owner,
domain: "aethex.space",
});
} catch (e: any) {
console.error("[Project Subdomain] Error:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to fetch project passport",
});
}
});
// 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: start authorization flow
app.get("/api/discord/oauth/start", (req, res) => {
try {
const clientId = process.env.DISCORD_CLIENT_ID;
if (!clientId) {
return res
.status(500)
.json({ error: "Discord client ID not configured" });
}
// Use the API base URL (should match Discord Dev Portal redirect URI)
let apiBase =
process.env.VITE_API_BASE ||
process.env.API_BASE ||
process.env.PUBLIC_BASE_URL ||
process.env.SITE_URL;
if (!apiBase) {
// Fallback to request origin if no env var is set
const protocol =
req.headers["x-forwarded-proto"] || req.protocol || "https";
const host =
req.headers["x-forwarded-host"] || req.hostname || req.get("host");
apiBase = `${protocol}://${host}`;
}
const redirectUri = `${apiBase}/api/discord/oauth/callback`;
console.log(
"[Discord OAuth Start] Using redirect URI:",
redirectUri,
"from API base:",
apiBase,
);
// Get the state from query params (can be a JSON string with action and redirectTo)
let state = req.query.state || "/dashboard";
if (typeof state !== "string") {
state = "/dashboard";
}
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
response_type: "code",
scope: "identify email",
state: state,
});
const discordOAuthUrl = `https://discord.com/api/oauth2/authorize?${params.toString()}`;
console.log("[Discord OAuth Start] Redirecting to:", discordOAuthUrl);
return res.redirect(302, discordOAuthUrl);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Discord OAuth: callback handler
app.get("/api/discord/oauth/callback", async (req, res) => {
const code = req.query.code as string;
const state = req.query.state as string;
const error = req.query.error as string;
if (error) {
return res.redirect(`/login?error=${error}`);
}
if (!code) {
return res.redirect("/login?error=no_code");
}
// Parse state to determine if this is a linking or login flow
let isLinkingFlow = false;
let redirectTo = "/dashboard";
let authenticatedUserId: string | null = null;
if (state) {
try {
const stateData = JSON.parse(decodeURIComponent(state));
isLinkingFlow = stateData.action === "link";
redirectTo = stateData.redirectTo || redirectTo;
if (isLinkingFlow && stateData.sessionToken) {
// Look up the linking session to get the user ID
const { data: session, error: sessionError } = await adminSupabase
.from("discord_linking_sessions")
.select("user_id")
.eq("session_token", stateData.sessionToken)
.gt("expires_at", new Date().toISOString())
.single();
if (sessionError || !session) {
console.error(
"[Discord OAuth] Linking session not found or expired",
);
return res.redirect(
"/login?error=session_lost&message=Session%20expired.%20Please%20try%20linking%20Discord%20again.",
);
}
authenticatedUserId = session.user_id;
console.log(
"[Discord OAuth] Linking session found, user_id:",
authenticatedUserId,
);
// Clean up the temporary session
await adminSupabase
.from("discord_linking_sessions")
.delete()
.eq("session_token", stateData.sessionToken);
}
} catch (e) {
console.log("[Discord OAuth] Could not parse state:", e);
}
}
try {
const clientId = process.env.DISCORD_CLIENT_ID;
const clientSecret = process.env.DISCORD_CLIENT_SECRET;
// Use the same redirect URI as the start endpoint
let apiBase =
process.env.VITE_API_BASE ||
process.env.API_BASE ||
process.env.PUBLIC_BASE_URL ||
process.env.SITE_URL;
if (!apiBase) {
// Fallback to request origin if no env var is set
const protocol =
req.headers["x-forwarded-proto"] || req.protocol || "https";
const host =
req.headers["x-forwarded-host"] || req.hostname || req.get("host");
apiBase = `${protocol}://${host}`;
}
const redirectUri = `${apiBase}/api/discord/oauth/callback`;
console.log(
"[Discord OAuth Callback] Received callback, redirect URI:",
redirectUri,
);
if (!clientId || !clientSecret) {
console.error("[Discord OAuth] Missing client credentials");
return res.redirect("/login?error=config");
}
// 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,
}).toString(),
},
);
if (!tokenResponse.ok) {
const errorData = await tokenResponse.json();
console.error("[Discord OAuth] Token exchange failed:", errorData);
return res.redirect("/login?error=token_exchange");
}
const tokenData = await tokenResponse.json();
// Get Discord user information
const userResponse = await fetch(
"https://discord.com/api/v10/users/@me",
{
headers: {
Authorization: `Bearer ${tokenData.access_token}`,
},
},
);
if (!userResponse.ok) {
console.error(
"[Discord OAuth] User fetch failed:",
userResponse.status,
);
return res.redirect("/login?error=user_fetch");
}
const discordUser = await userResponse.json();
if (!discordUser.email) {
console.error("[Discord OAuth] Discord user has no email");
return res.redirect(
"/login?error=no_email&message=Please+enable+email+on+your+Discord+account",
);
}
// LINKING FLOW: Link Discord to authenticated user
if (isLinkingFlow && authenticatedUserId) {
console.log(
"[Discord OAuth] Linking Discord to user:",
authenticatedUserId,
);
// Check if Discord ID is already linked to someone else
const { data: existingLink } = await adminSupabase
.from("discord_links")
.select("user_id")
.eq("discord_id", discordUser.id)
.single();
if (existingLink && existingLink.user_id !== authenticatedUserId) {
console.error(
"[Discord OAuth] Discord ID already linked to different user",
);
return res.redirect(
`/dashboard?error=already_linked&message=${encodeURIComponent("This Discord account is already linked to another AeThex account")}`,
);
}
// Create or update Discord link
const { error: linkError } = await adminSupabase
.from("discord_links")
.upsert({
discord_id: discordUser.id,
user_id: authenticatedUserId,
linked_at: new Date().toISOString(),
});
if (linkError) {
console.error("[Discord OAuth] Link creation failed:", linkError);
return res.redirect(
`/dashboard?error=link_failed&message=${encodeURIComponent("Failed to link Discord account")}`,
);
}
console.log(
"[Discord OAuth] Successfully linked Discord:",
discordUser.id,
);
return res.redirect(redirectTo);
}
// LOGIN FLOW: Check if Discord user already exists
const { data: existingLink } = await adminSupabase
.from("discord_links")
.select("user_id")
.eq("discord_id", discordUser.id)
.single();
let userId: string;
if (existingLink) {
// Discord ID already linked - use existing user
userId = existingLink.user_id;
console.log(
"[Discord OAuth] Discord ID already linked to user:",
userId,
);
} else {
// Check if email matches existing account
const { data: existingUserProfile } = await adminSupabase
.from("user_profiles")
.select("id")
.eq("email", discordUser.email)
.single();
if (existingUserProfile) {
// Discord email matches existing user profile - link it
userId = existingUserProfile.id;
console.log(
"[Discord OAuth] Discord email matches existing user profile, linking Discord",
);
} else {
// Discord email doesn't match any existing account
// Don't auto-create - ask user to sign in with email first
console.log(
"[Discord OAuth] Discord email not found in existing accounts, redirecting to sign in",
);
return res.redirect(
`/login?error=discord_no_match&message=${encodeURIComponent(`Discord email (${discordUser.email}) not found. Please sign in with your email account first, then link Discord from settings.`)}`,
);
}
}
// Create Discord link
const { error: linkError } = await adminSupabase
.from("discord_links")
.upsert({
discord_id: discordUser.id,
user_id: userId,
linked_at: new Date().toISOString(),
});
if (linkError) {
console.error("[Discord OAuth] Link creation failed:", linkError);
return res.redirect("/login?error=link_create");
}
// Discord is now linked! Redirect to login for user to sign in
console.log(
"[Discord OAuth] Discord linked successfully, redirecting to login",
);
return res.redirect(
`/login?discord_linked=true&email=${encodeURIComponent(discordUser.email)}`,
);
} catch (e: any) {
console.error("[Discord OAuth] Callback error:", e);
return res.redirect("/login?error=unknown");
}
});
// Discord Create Linking Session: Creates temporary session for OAuth linking
app.post("/api/discord/create-linking-session", async (req, res) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: "Not authenticated" });
}
const token = authHeader.replace("Bearer ", "");
const {
data: { user },
error,
} = await adminSupabase.auth.getUser(token);
if (error || !user) {
return res.status(401).json({ error: "Invalid auth token" });
}
const sessionToken = randomBytes(32).toString("hex");
const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString();
const { error: insertError } = await adminSupabase
.from("discord_linking_sessions")
.insert({
user_id: user.id,
session_token: sessionToken,
expires_at: expiresAt,
});
if (insertError) {
console.error("[Discord] Session insert error:", {
code: insertError.code,
message: insertError.message,
details: insertError.details,
hint: insertError.hint,
});
return res.status(500).json({
error: insertError.message,
details: insertError.details,
});
}
res.json({ token: sessionToken });
} catch (error: any) {
console.error("[Discord] Create session error:", error);
res.status(500).json({ error: error.message });
}
});
// Discord Verify Code: Link account using verification code
app.post("/api/discord/verify-code", async (req, res) => {
const { verification_code, user_id } = req.body || {};
console.log("[Discord Verify] Request received:", {
verification_code: verification_code ? "***" : "missing",
user_id: user_id || "missing",
});
if (!verification_code || !user_id) {
console.error("[Discord Verify] Missing params:", {
hasCode: !!verification_code,
hasUserId: !!user_id,
});
return res.status(400).json({
message: "Missing verification code or user ID",
});
}
try {
// Find valid verification code
const { data: verification, error: verifyError } = await adminSupabase
.from("discord_verifications")
.select("*")
.eq("verification_code", verification_code.trim())
.gt("expires_at", new Date().toISOString())
.single();
if (verifyError) {
console.error(
"[Discord Verify] Error querying verification code:",
verifyError,
);
return res.status(400).json({
message:
"Invalid or expired verification code. Please try /verify again.",
});
}
if (!verification) {
console.warn(
"[Discord Verify] Verification code not found or expired:",
verification_code,
);
return res.status(400).json({
message:
"Invalid or expired verification code. Please try /verify again.",
});
}
const discordId = verification.discord_id;
console.log(
"[Discord Verify] Found verification code for Discord ID:",
{
discordId,
userId: user_id,
},
);
// Check if already linked
const { data: existingLink, error: linkCheckError } =
await adminSupabase
.from("discord_links")
.select("*")
.eq("discord_id", discordId)
.single();
if (linkCheckError && linkCheckError.code !== "PGRST116") {
console.error(
"[Discord Verify] Error checking existing link:",
linkCheckError,
);
}
if (existingLink && existingLink.user_id !== user_id) {
console.warn(
"[Discord Verify] Discord ID already linked to different user:",
{
discordId,
existingUserId: existingLink.user_id,
newUserId: user_id,
},
);
return res.status(400).json({
message:
"This Discord account is already linked to another AeThex account.",
});
}
// Create or update link
const { error: linkError } = await adminSupabase
.from("discord_links")
.upsert({
discord_id: discordId,
user_id: user_id,
linked_at: new Date().toISOString(),
});
if (linkError) {
console.error("[Discord Verify] Link creation failed:", linkError);
return res.status(500).json({
message: "Failed to link Discord account: " + linkError.message,
});
}
console.log("[Discord Verify] Link created successfully:", {
discordId,
userId: user_id,
});
// Delete used verification code
await adminSupabase
.from("discord_verifications")
.delete()
.eq("verification_code", verification_code.trim());
res.status(200).json({
success: true,
message: "Discord account linked successfully!",
discord_user: {
id: discordId,
username: verification.username || "Discord User",
},
});
} catch (error: any) {
console.error("[Discord Verify] Unexpected error:", error);
res.status(500).json({
message: "An error occurred. Please try again: " + error?.message,
});
}
});
// 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",
});
}
});
// Discord Activity Token Exchange Endpoint
app.post("/api/discord/token", async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: "Missing authorization code" });
}
const clientId = process.env.DISCORD_CLIENT_ID;
const clientSecret = process.env.DISCORD_CLIENT_SECRET;
if (!clientId || !clientSecret) {
console.error("[Discord Token] Missing CLIENT_ID or CLIENT_SECRET");
return res.status(500).json({ error: "Server not configured" });
}
// Exchange authorization code for access token
const tokenResponse = await fetch(
"https://discord.com/api/v10/oauth2/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
grant_type: "authorization_code",
code,
redirect_uri:
process.env.DISCORD_ACTIVITY_REDIRECT_URI ||
"https://aethex.dev/activity",
}).toString(),
},
);
if (!tokenResponse.ok) {
const error = await tokenResponse.json();
console.error("[Discord Token] Token exchange failed:", error);
return res.status(400).json({
error: "Failed to exchange code for token",
details: error,
});
}
const tokenData = await tokenResponse.json();
const accessToken = tokenData.access_token;
if (!accessToken) {
console.error("[Discord Token] No access token in response");
return res
.status(500)
.json({ error: "Failed to obtain access token" });
}
// Fetch Discord user info to ensure token is valid
const userResponse = await fetch(
"https://discord.com/api/v10/users/@me",
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
if (!userResponse.ok) {
console.error("[Discord Token] Failed to fetch user info");
return res.status(401).json({ error: "Invalid token" });
}
const discordUser = await userResponse.json();
console.log(
"[Discord Token] Token exchange successful for user:",
discordUser.id,
);
// Return access token to Activity
return res.status(200).json({
access_token: accessToken,
token_type: tokenData.token_type,
expires_in: tokenData.expires_in,
user_id: discordUser.id,
username: discordUser.username,
});
} catch (error) {
console.error("[Discord Token] Error:", error);
return res.status(500).json({
error: "Internal server error",
message: error instanceof Error ? error.message : String(error),
});
}
});
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(`
Register Discord Commands
��� Discord Commands Registration
Register all Discord slash commands for AeThex
Commands to be registered:
- ✅ /verify - Link your Discord account
- ✅ /set-realm - Choose your primary arm
- ✅ /profile - View your AeThex profile
- ✅ /unlink - Disconnect your account
- ✅ /verify-role - Check your Discord roles
⏳ Registering commands... please wait...
`);
});
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)
// Set DISCORD_BOT_HEALTH_URL env var to bot's actual health endpoint
// Examples:
// - Railway internal: http://aethex.railway.internal:8044/health
// - External URL: https://bot.example.com/health
// - Local: http://localhost:3000/health
app.get("/api/discord/bot-health", async (req, res) => {
try {
// Try multiple bot health URLs in order of preference
const botHealthUrls = [
process.env.DISCORD_BOT_HEALTH_URL,
"http://aethex.railway.internal:8044/health", // Railway internal network
"http://localhost:8044/health", // Local fallback
].filter(Boolean) as string[];
let lastError: Error | null = null;
let response: Response | null = null;
for (const url of botHealthUrls) {
try {
console.log(`[Discord Bot Health] Attempting to reach: ${url}`);
// Create AbortController with 5 second timeout for internal Railway, 3 seconds for localhost
const isInternal = url.includes("railway.internal");
const timeoutMs = isInternal ? 5000 : 3000;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
signal: controller.signal,
});
} finally {
clearTimeout(timeoutId);
}
const contentType = response.headers.get("content-type") || "";
const responseBody = await response.text();
console.log(
`[Discord Bot Health] Response from ${url}: Status ${response.status}, Content-Type: ${contentType}, Body: ${responseBody.substring(0, 200)}`,
);
if (response.ok && contentType.includes("application/json")) {
console.log(`[Discord Bot Health] Success from ${url}`);
const data = JSON.parse(responseBody);
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(),
});
}
if (response.ok && !contentType.includes("application/json")) {
lastError = new Error(
`Got non-JSON response (${contentType}): ${responseBody.substring(0, 100)}`,
);
continue;
}
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
console.warn(
`[Discord Bot Health] Failed to reach ${url}: ${lastError.message}`,
);
continue;
}
}
// Could not reach any health endpoint
res.setHeader("Content-Type", "application/json");
return res.status(503).json({
status: "offline",
error: `Could not reach bot health endpoint. Last error: ${lastError?.message || "Unknown error"}`,
});
} 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",
});
}
});
// Bot Management Proxy Endpoints (session-authenticated)
const BOT_ADMIN_TOKEN = process.env.DISCORD_ADMIN_TOKEN || "aethex-bot-admin";
const getBotApiUrl = () => {
const urls = [
process.env.DISCORD_BOT_HEALTH_URL?.replace("/health", ""),
"http://localhost:8044",
].filter(Boolean);
return urls[0] || "http://localhost:8044";
};
// Proxy to bot-status
app.get("/api/discord/bot-status", async (req, res) => {
try {
const botUrl = getBotApiUrl();
const response = await fetch(`${botUrl}/bot-status`, {
headers: { Authorization: `Bearer ${BOT_ADMIN_TOKEN}` },
});
if (!response.ok) throw new Error(`Bot returned ${response.status}`);
const data = await response.json();
res.json(data);
} catch (error: any) {
res.status(503).json({ error: error.message, status: "offline" });
}
});
// Proxy to linked-users
app.get("/api/discord/linked-users", async (req, res) => {
try {
const botUrl = getBotApiUrl();
const response = await fetch(`${botUrl}/linked-users`, {
headers: { Authorization: `Bearer ${BOT_ADMIN_TOKEN}` },
});
if (!response.ok) throw new Error(`Bot returned ${response.status}`);
const data = await response.json();
res.json(data);
} catch (error: any) {
res.status(503).json({ error: error.message, success: false });
}
});
// Proxy to command-stats
app.get("/api/discord/command-stats", async (req, res) => {
try {
const botUrl = getBotApiUrl();
const response = await fetch(`${botUrl}/command-stats`, {
headers: { Authorization: `Bearer ${BOT_ADMIN_TOKEN}` },
});
if (!response.ok) throw new Error(`Bot returned ${response.status}`);
const data = await response.json();
res.json(data);
} catch (error: any) {
res.status(503).json({ error: error.message, success: false });
}
});
// Proxy to feed-stats
app.get("/api/discord/feed-stats", async (req, res) => {
try {
const botUrl = getBotApiUrl();
const response = await fetch(`${botUrl}/feed-stats`, {
headers: { Authorization: `Bearer ${BOT_ADMIN_TOKEN}` },
});
if (!response.ok) throw new Error(`Bot returned ${response.status}`);
const data = await response.json();
res.json(data);
} catch (error: any) {
res.status(503).json({ error: error.message, success: false });
}
});
// Proxy to register-commands
app.post("/api/discord/bot-register-commands", async (req, res) => {
try {
const botUrl = getBotApiUrl();
const response = await fetch(`${botUrl}/register-commands`, {
method: "POST",
headers: {
Authorization: `Bearer ${BOT_ADMIN_TOKEN}`,
"Content-Type": "application/json",
},
});
if (!response.ok) throw new Error(`Bot returned ${response.status}`);
const data = await response.json();
res.json(data);
} catch (error: any) {
res.status(503).json({ error: error.message, success: false });
}
});
// Discord Token Diagnostic Endpoint
app.get("/api/discord/diagnostic", async (req, res) => {
try {
const botToken = process.env.DISCORD_BOT_TOKEN?.trim();
const clientId = process.env.DISCORD_CLIENT_ID?.trim();
const publicKey = process.env.DISCORD_PUBLIC_KEY?.trim();
const diagnostics = {
timestamp: new Date().toISOString(),
environment: {
botTokenSet: !!botToken,
clientIdSet: !!clientId,
publicKeySet: !!publicKey,
},
tokenValidation: {
length: botToken?.length || 0,
format: botToken ? "valid_format" : "missing",
preview: botToken
? `${botToken.substring(0, 15)}...${botToken.substring(botToken.length - 10)}`
: null,
minLengthMet: (botToken?.length || 0) >= 20,
},
clientIdValidation: {
value: clientId || null,
isNumeric: /^\d+$/.test(clientId || ""),
},
testRequest: {
url: `https://discord.com/api/v10/applications/${clientId}/commands`,
method: "PUT",
headerFormat: "Bot {token}",
status: "ready_to_test",
},
recommendations: [] as string[],
};
// Add recommendations based on validation
if (!botToken) {
diagnostics.recommendations.push(
"❌ DISCORD_BOT_TOKEN not set. Set it in environment variables.",
);
} else if ((botToken?.length || 0) < 20) {
diagnostics.recommendations.push(
`❌ DISCORD_BOT_TOKEN appears invalid (length: ${botToken?.length}). Should be 60+ characters.`,
);
} else {
diagnostics.recommendations.push(
"✅ DISCORD_BOT_TOKEN format looks valid",
);
}
if (!clientId) {
diagnostics.recommendations.push(
"�� DISCORD_CLIENT_ID not set. Set it to your application's ID.",
);
} else {
diagnostics.recommendations.push("�� DISCORD_CLIENT_ID is set");
}
if (!publicKey) {
diagnostics.recommendations.push(
"��� DISCORD_PUBLIC_KEY not set. Needed for signature verification.",
);
} else {
diagnostics.recommendations.push("✅ DISCORD_PUBLIC_KEY is set");
}
// Test if token works with Discord API
if (botToken && clientId) {
try {
const testResponse = await fetch(
`https://discord.com/api/v10/applications/${clientId}`,
{
headers: {
Authorization: `Bot ${botToken}`,
},
},
);
diagnostics.testRequest = {
...diagnostics.testRequest,
status:
testResponse.status === 200
? "✅ Success"
: `❌ Failed (${testResponse.status})`,
responseCode: testResponse.status,
};
if (testResponse.status === 401) {
diagnostics.recommendations.push(
"❌ Token authentication failed (401). The token may be invalid or revoked.",
);
} else if (testResponse.status === 200) {
diagnostics.recommendations.push(
"✅ Token authentication successful with Discord API!",
);
}
} catch (error) {
diagnostics.testRequest = {
...diagnostics.testRequest,
status: "⚠️ Network Error",
error: error instanceof Error ? error.message : "Unknown error",
};
}
}
res.setHeader("Content-Type", "application/json");
return res.json(diagnostics);
} catch (error: any) {
console.error("[Discord Diagnostic] Error:", error);
res.setHeader("Content-Type", "application/json");
return res.status(500).json({
error: error instanceof Error ? error.message : "Diagnostic failed",
});
}
});
// 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 = {};
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) => {
console.log("[API] POST /api/posts called");
const payload = req.body || {};
console.log("[API] Payload:", JSON.stringify(payload).slice(0, 200));
// Validation
if (!payload.author_id) {
console.log("[API] Error: author_id is required");
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" });
}
console.log("[API] Post created successfully:", data?.id);
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) });
}
});
// Profile update endpoint - used by Dashboard realm/settings
app.patch("/api/profile/update", async (req, res) => {
// Authenticate user via Bearer token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ error: "Authentication required" });
}
const token = authHeader.replace("Bearer ", "");
const { data: { user: authUser }, error: authError } = await adminSupabase.auth.getUser(token);
if (authError || !authUser) {
return res.status(401).json({ error: "Invalid or expired auth token" });
}
const { user_id, ...updates } = req.body || {};
// Ensure user can only update their own profile
if (user_id && user_id !== authUser.id) {
return res.status(403).json({ error: "Cannot update another user's profile" });
}
const targetUserId = user_id || authUser.id;
// Whitelist allowed fields for security
const allowedFields = [
"full_name",
"bio",
"avatar_url",
"banner_url",
"location",
"website_url",
"github_url",
"linkedin_url",
"twitter_url",
"primary_realm",
"experience_level",
"user_type",
];
const sanitizedUpdates: Record = {};
for (const key of allowedFields) {
if (key in updates) {
sanitizedUpdates[key] = updates[key];
}
}
if (Object.keys(sanitizedUpdates).length === 0) {
return res.status(400).json({ error: "No valid fields to update" });
}
try {
const { data, error } = await adminSupabase
.from("user_profiles")
.update({
...sanitizedUpdates,
updated_at: new Date().toISOString(),
})
.eq("id", targetUserId)
.select()
.single();
if (error) {
console.error("[Profile Update] Error:", error);
return res.status(500).json({ error: error.message });
}
return res.json(data);
} catch (e: any) {
console.error("[Profile Update] Exception:", e?.message);
return res.status(500).json({ error: e?.message || "Failed to update profile" });
}
});
// Wallet verification endpoint for Phase 2 Bridge UI
app.post("/api/profile/wallet-verify", async (req, res) => {
const { user_id, wallet_address } = req.body || {};
if (!user_id) {
return res.status(400).json({ error: "user_id is required" });
}
if (!wallet_address) {
return res.status(400).json({ error: "wallet_address is required" });
}
// Validate Ethereum address format (0x followed by 40 hex chars)
const ethAddressRegex = /^0x[a-fA-F0-9]{40}$/;
const normalizedAddress = String(wallet_address).toLowerCase();
if (!ethAddressRegex.test(normalizedAddress)) {
return res.status(400).json({
error: "Invalid Ethereum address format",
});
}
try {
// Check if wallet is already connected to a different user
const { data: existingUser, error: checkError } = await adminSupabase
.from("user_profiles")
.select("id, username")
.eq("wallet_address", normalizedAddress)
.neq("id", user_id)
.maybeSingle();
if (checkError && checkError.code !== "PGRST116") {
// PGRST116 = no rows returned (expected)
console.error("[Wallet Verify] Check error:", checkError);
return res.status(500).json({
error: "Failed to verify wallet availability",
});
}
if (existingUser) {
return res.status(409).json({
error: "This wallet is already connected to another account",
});
}
// Update user profile with wallet address
const { data, error } = await adminSupabase
.from("user_profiles")
.update({ wallet_address: normalizedAddress })
.eq("id", user_id)
.select()
.single();
if (error) {
console.error("[Wallet Verify] Update error:", error);
return res.status(500).json({
error: error.message || "Failed to connect wallet",
});
}
console.log("[Wallet Verify] Wallet connected for user:", user_id);
return res.json({
ok: true,
message: "Wallet connected successfully",
wallet_address: normalizedAddress,
user: data,
});
} catch (e: any) {
console.error("[Wallet Verify] Exception:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to connect wallet",
});
}
});
// Wallet disconnection endpoint
app.delete("/api/profile/wallet-verify", async (req, res) => {
const { user_id } = req.body || {};
if (!user_id) {
return res.status(400).json({ error: "user_id is required" });
}
try {
const { data, error } = await adminSupabase
.from("user_profiles")
.update({ wallet_address: null })
.eq("id", user_id)
.select()
.single();
if (error) {
console.error("[Wallet Verify] Disconnect error:", error);
return res.status(500).json({
error: error.message || "Failed to disconnect wallet",
});
}
console.log("[Wallet Verify] Wallet disconnected for user:", user_id);
return res.json({
ok: true,
message: "Wallet disconnected successfully",
user: data,
});
} catch (e: any) {
console.error("[Wallet Verify] Disconnect exception:", e?.message);
return res.status(500).json({
error: e?.message || "Failed to disconnect wallet",
});
}
});
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: "��️",
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 { createHash } = await import("crypto");
const generateDeterministicUUID = (str: string): string => {
const hash = createHash("sha256").update(str).digest("hex");
return [
hash.slice(0, 8),
hash.slice(8, 12),
"5" + hash.slice(13, 16),
((parseInt(hash.slice(16, 18), 16) & 0x3f) | 0x80)
.toString(16)
.padStart(2, "0") + hash.slice(18, 20),
hash.slice(20, 32),
].join("-");
};
const uuidId = generateDeterministicUUID(achievement.id);
const { error } = await adminSupabase.from("achievements").upsert(
{
id: uuidId,
name: achievement.name,
description: achievement.description,
icon: achievement.icon,
badge_color: achievement.badge_color,
xp_reward: achievement.xp_reward,
},
{ onConflict: "id", ignoreDuplicates: true },
);
if (error && error.code !== "23505") {
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(200, Number(req.query.limit) || 50));
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, body_html",
)
.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) {
// If table doesn't exist, return empty array (client will use seed data)
if (
error.message?.includes("does not exist") ||
error.code === "42P01"
) {
console.log(
"[Blog] blog_posts table not found, returning empty array",
);
return res.json([]);
}
console.error("[Blog] Error fetching blog posts:", error);
return res.status(500).json({ error: error.message });
}
console.log(
"[Blog] Successfully fetched",
(data || []).length,
"blog posts",
);
res.json(data || []);
} catch (e: any) {
console.error("[Blog] Exception:", e);
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) {
if (error.code === "PGRST116") {
// No rows returned - 404
return res.status(404).json({ error: "Blog post not found" });
}
if (
error.message?.includes("does not exist") ||
error.code === "42P01"
) {
// Table doesn't exist
return res.status(404).json({ error: "Blog not configured" });
}
return res.status(500).json({ error: error.message });
}
res.json(data || null);
} catch (e: any) {
console.error("[Blog] Error fetching blog post:", e);
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) });
}
});
// Get following list
app.get("/api/social/following", async (req, res) => {
const userId = req.query.userId as string;
if (!userId) {
return res
.status(400)
.json({ error: "userId query parameter required" });
}
try {
const { data, error } = await adminSupabase
.from("user_follows")
.select("following_id")
.eq("follower_id", userId);
if (error) {
return res
.status(500)
.json({ error: "Failed to fetch following list" });
}
return res.json({
data: (data || []).map((r: any) => r.following_id),
});
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Get followers list
app.get("/api/social/followers", async (req, res) => {
const userId = req.query.userId as string;
if (!userId) {
return res
.status(400)
.json({ error: "userId query parameter required" });
}
try {
const { data, error } = await adminSupabase
.from("user_follows")
.select("follower_id")
.eq("following_id", userId);
if (error) {
return res
.status(500)
.json({ error: "Failed to fetch followers list" });
}
return res.json({
data: (data || []).map((r: any) => r.follower_id),
});
} 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 {
// Get post author info before liking
const { data: post } = await adminSupabase
.from("community_posts")
.select("user_id")
.eq("id", postId)
.single();
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);
}
// Notify post author of like (only if different user)
if (post?.user_id && post.user_id !== user_id) {
try {
const { data: liker } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", user_id)
.single();
const likerName =
(liker as any)?.full_name ||
(liker as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: post.user_id,
type: "info",
title: "❤️ Your post was liked",
message: `${likerName} liked your post.`,
});
} catch (notifError) {
console.warn("Failed to create like notification:", notifError);
}
}
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 {
// Get post author info
const { data: post } = await adminSupabase
.from("community_posts")
.select("user_id")
.eq("id", postId)
.single();
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);
}
// Notify post author of comment (only if different user)
if (post?.user_id && post.user_id !== user_id) {
try {
const { data: commenter } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", user_id)
.single();
const commenterName =
(commenter as any)?.full_name ||
(commenter as any)?.username ||
"Someone";
const preview =
content.substring(0, 50) + (content.length > 50 ? "..." : "");
await adminSupabase.from("notifications").insert({
user_id: post.user_id,
type: "info",
title: "💬 New comment on your post",
message: `${commenterName} commented: "${preview}"`,
});
} catch (notifError) {
console.warn("Failed to create comment notification:", notifError);
}
}
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) => {
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/:identifier", async (req, res) => {
try {
const identifier = String(req.params.identifier || "").trim();
if (!identifier) {
return res.status(400).json({ error: "identifier required" });
}
// Check if identifier is a UUID (username-first, UUID fallback)
const isUUID =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
identifier,
);
let query = adminSupabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
created_at,
updated_at
`,
)
.eq("is_discoverable", true);
// Try username first (preferred), then UUID fallback
if (isUUID) {
query = query.eq("id", identifier);
} else {
query = query.eq("username", identifier);
}
const { data: creator, error } = await query.single();
// If username lookup failed and it's a valid UUID format, try UUID
if (error && !isUUID) {
if (
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
identifier,
)
) {
const { data: creatorByUUID, error: uuidError } =
await adminSupabase
.from("aethex_creators")
.select(
`
id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
created_at,
updated_at
`,
)
.eq("is_discoverable", true)
.eq("id", identifier)
.single();
if (!uuidError && creatorByUUID) {
// Found by UUID, optionally redirect to username canonical URL
const { data: devConnectLink } = await adminSupabase
.from("aethex_devconnect_links")
.select("devconnect_username, devconnect_profile_url")
.eq("aethex_creator_id", creatorByUUID.id)
.maybeSingle();
return res.json({
...creatorByUUID,
devconnect_link: devConnectLink,
});
}
}
if (error.code === "PGRST116") {
return res.status(404).json({ error: "Creator not found" });
}
throw error;
}
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" });
}
});
// Get creator by user_id
app.get("/api/creators/user/:userId", async (req, res) => {
try {
const userId = String(req.params.userId || "").trim();
if (!userId) {
return res.status(400).json({ error: "userId required" });
}
const { data: creator, error } = await adminSupabase
.from("aethex_creators")
.select(
`
id,
user_id,
username,
bio,
skills,
avatar_url,
experience_level,
arm_affiliations,
primary_arm,
spotify_profile_url,
created_at,
updated_at
`,
)
.eq("user_id", userId)
.eq("is_discoverable", true)
.maybeSingle();
if (!creator) {
return res.status(404).json({ error: "Creator not found" });
}
if (error) 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 by user_id:",
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;
// Notify opportunity poster of new application
if (opportunity?.posted_by_id) {
try {
const { data: creatorProfile } = await adminSupabase
.from("aethex_creators")
.select("user_id, full_name")
.eq("id", creator.id)
.single();
await adminSupabase.from("notifications").insert({
user_id: opportunity.posted_by_id,
type: "info",
title: `📋 New Application: ${opportunity.title}`,
message: `${creatorProfile?.full_name || "A creator"} applied for your opportunity.`,
});
} catch (notifError) {
console.warn(
"Failed to create application notification:",
notifError,
);
}
}
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;
// Notify applicant of status change
if (data) {
try {
const { data: applicantProfile } = await adminSupabase
.from("aethex_creators")
.select("user_id")
.eq("id", data.creator_id)
.single();
if (applicantProfile?.user_id) {
const statusEmoji =
status === "accepted"
? "✅"
: status === "rejected"
? "❌"
: "📝";
const statusMessage =
status === "accepted"
? "accepted"
: status === "rejected"
? "rejected"
: "updated";
await adminSupabase.from("notifications").insert({
user_id: applicantProfile.user_id,
type:
status === "accepted"
? "success"
: status === "rejected"
? "error"
: "info",
title: `${statusEmoji} Application ${statusMessage}`,
message:
response_message ||
`Your application has been ${statusMessage}.`,
});
}
} catch (notifError) {
console.warn("Failed to create status notification:", notifError);
}
}
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" });
}
});
// Ethos Tracks API
app.get("/api/ethos/tracks", async (req, res) => {
try {
const {
limit = 50,
offset = 0,
genre,
licenseType,
search,
} = req.query;
let dbQuery = adminSupabase
.from("ethos_tracks")
.select(
`
id,
user_id,
title,
description,
file_url,
duration_seconds,
genre,
license_type,
bpm,
is_published,
download_count,
rating,
price,
created_at,
updated_at,
user_profiles(id, full_name, avatar_url)
`,
{ count: "exact" },
)
.eq("is_published", true)
.order("created_at", { ascending: false });
if (genre) dbQuery = dbQuery.contains("genre", [genre]);
if (licenseType) dbQuery = dbQuery.eq("license_type", licenseType);
if (search) {
dbQuery = dbQuery.or(
`title.ilike.%${search}%,description.ilike.%${search}%`,
);
}
const { data, error, count } = await dbQuery.range(
Number(offset),
Number(offset) + Number(limit) - 1,
);
if (error) throw error;
res.json({
data: data || [],
total: count,
limit: Number(limit),
offset: Number(offset),
});
} catch (e: any) {
console.error("[Ethos Tracks API] Error fetching tracks:", e?.message);
res.status(500).json({ error: e?.message || "Failed to fetch tracks" });
}
});
app.post("/api/ethos/tracks", async (req, res) => {
try {
const userId = req.headers["x-user-id"] || req.body?.user_id;
if (!userId) {
return res.status(401).json({ error: "Unauthorized" });
}
const {
title,
description,
file_url,
duration_seconds,
genre,
license_type,
bpm,
is_published,
price,
} = req.body;
if (!title || !file_url || !license_type) {
return res.status(400).json({
error: "Missing required fields: title, file_url, license_type",
});
}
const { data, error } = await adminSupabase
.from("ethos_tracks")
.insert([
{
user_id: userId,
title,
description,
file_url,
duration_seconds,
genre: genre || [],
license_type,
bpm,
is_published: is_published !== false,
price,
},
])
.select();
if (error) throw error;
if (license_type === "ecosystem" && data && data[0]) {
const trackId = data[0].id;
await adminSupabase.from("ethos_ecosystem_licenses").insert([
{
track_id: trackId,
artist_id: userId,
accepted_at: new Date().toISOString(),
},
]);
}
res.status(201).json(data[0]);
} catch (e: any) {
console.error("[Ethos Tracks API] Error creating track:", e?.message);
res.status(500).json({ error: e?.message || "Failed to create track" });
}
});
// Ethos Artists API
app.get("/api/ethos/artists", async (req, res) => {
try {
const { id, limit = 50, offset = 0, verified, forHire } = req.query;
if (id) {
// Get single artist by ID
const { data: artist, error: artistError } = await adminSupabase
.from("ethos_artist_profiles")
.select(
`
user_id,
skills,
for_hire,
bio,
portfolio_url,
sample_price_track,
sample_price_sfx,
sample_price_score,
turnaround_days,
verified,
total_downloads,
created_at,
user_profiles(id, full_name, avatar_url, email)
`,
)
.eq("user_id", id)
.single();
if (artistError && artistError.code !== "PGRST116") throw artistError;
if (!artist) {
return res.status(404).json({ error: "Artist not found" });
}
const { data: tracks } = await adminSupabase
.from("ethos_tracks")
.select("*")
.eq("user_id", id)
.eq("is_published", true)
.order("created_at", { ascending: false });
return res.json({
...artist,
tracks: tracks || [],
});
}
// Get list of artists
let dbQuery = adminSupabase.from("ethos_artist_profiles").select(
`
user_id,
skills,
for_hire,
bio,
portfolio_url,
sample_price_track,
sample_price_sfx,
sample_price_score,
turnaround_days,
verified,
total_downloads,
created_at,
user_profiles(id, full_name, avatar_url)
`,
{ count: "exact" },
);
if (verified === "true") dbQuery = dbQuery.eq("verified", true);
if (forHire === "true") dbQuery = dbQuery.eq("for_hire", true);
const { data, error, count } = await dbQuery
.order("verified", { ascending: false })
.order("total_downloads", { ascending: false })
.range(Number(offset), Number(offset) + Number(limit) - 1);
if (error) throw error;
res.json({
data: data || [],
total: count,
limit: Number(limit),
offset: Number(offset),
});
} catch (e: any) {
console.error(
"[Ethos Artists API] Error fetching artists:",
e?.message,
);
res
.status(500)
.json({ error: e?.message || "Failed to fetch artists" });
}
});
app.put("/api/ethos/artists", async (req, res) => {
try {
const userId = req.headers["x-user-id"] || req.body?.user_id;
if (!userId) {
return res.status(401).json({ error: "Unauthorized" });
}
const {
skills,
for_hire,
bio,
portfolio_url,
sample_price_track,
sample_price_sfx,
sample_price_score,
turnaround_days,
} = req.body;
const { data, error } = await adminSupabase
.from("ethos_artist_profiles")
.upsert(
{
user_id: userId,
skills: skills || [],
for_hire: for_hire !== false,
bio,
portfolio_url,
sample_price_track,
sample_price_sfx,
sample_price_score,
turnaround_days,
},
{ onConflict: "user_id" },
)
.select();
if (error) throw error;
res.json(data[0]);
} catch (e: any) {
console.error("[Ethos Artists API] Error updating artist:", e?.message);
res
.status(500)
.json({ error: e?.message || "Failed to update artist profile" });
}
});
// Task assignment with notification
app.post("/api/tasks", async (req, res) => {
try {
const {
project_id,
title,
description,
assignee_id,
due_date,
user_id,
} = req.body;
if (!project_id || !title || !user_id) {
return res
.status(400)
.json({ error: "project_id, title, and user_id required" });
}
const { data, error } = await adminSupabase
.from("project_tasks")
.insert({
project_id,
title,
description: description || null,
assignee_id: assignee_id || null,
due_date: due_date || null,
status: "open",
})
.select()
.single();
if (error) throw error;
// Notify assignee if assigned
if (assignee_id && assignee_id !== user_id) {
try {
const { data: assigner } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", user_id)
.single();
const assignerName =
(assigner as any)?.full_name ||
(assigner as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: assignee_id,
type: "info",
title: "📋 Task assigned to you",
message: `${assignerName} assigned you a task: "${title}"`,
});
} catch (notifError) {
console.warn("Failed to create task notification:", notifError);
}
}
return res.status(201).json(data);
} catch (e: any) {
console.error("[Tasks API] Error creating task:", e?.message);
return res.status(500).json({ error: "Failed to create task" });
}
});
// Assign task with notification
app.put("/api/tasks/:id/assign", async (req, res) => {
try {
const taskId = String(req.params.id || "").trim();
const { assignee_id, user_id } = req.body;
if (!taskId || !assignee_id || !user_id) {
return res
.status(400)
.json({ error: "task id, assignee_id, and user_id required" });
}
const { data: task } = await adminSupabase
.from("project_tasks")
.select("title")
.eq("id", taskId)
.single();
if (!task) {
return res.status(404).json({ error: "Task not found" });
}
const { data, error } = await adminSupabase
.from("project_tasks")
.update({ assignee_id })
.eq("id", taskId)
.select()
.single();
if (error) throw error;
// Notify assignee
try {
const { data: assigner } = await adminSupabase
.from("user_profiles")
.select("full_name, username")
.eq("id", user_id)
.single();
const assignerName =
(assigner as any)?.full_name ||
(assigner as any)?.username ||
"Someone";
await adminSupabase.from("notifications").insert({
user_id: assignee_id,
type: "info",
title: "📋 Task assigned to you",
message: `${assignerName} assigned you: "${task.title}"`,
});
} catch (notifError) {
console.warn("Failed to create assignment notification:", notifError);
}
return res.json(data);
} catch (e: any) {
console.error("[Tasks API] Error assigning task:", e?.message);
return res.status(500).json({ error: "Failed to assign task" });
}
});
// Moderation report with staff notification
app.post("/api/moderation/reports", async (req, res) => {
try {
const { reported_user_id, report_type, description, reporter_id } =
req.body;
if (!reported_user_id || !report_type || !reporter_id) {
return res.status(400).json({
error: "reported_user_id, report_type, and reporter_id required",
});
}
const { data, error } = await adminSupabase
.from("moderation_reports")
.insert({
reported_user_id,
report_type,
description: description || null,
reporter_id,
status: "pending",
})
.select()
.single();
if (error) throw error;
// Notify staff members
try {
const { data: staffUsers } = await adminSupabase
.from("user_roles")
.select("user_id")
.in("role", ["owner", "admin", "moderator"]);
if (staffUsers && staffUsers.length > 0) {
const notifications = staffUsers.map((staff: any) => ({
user_id: staff.user_id,
type: "warning",
title: "🚨 New moderation report",
message: `A ${report_type} report has been submitted. Please review.`,
}));
await adminSupabase.from("notifications").insert(notifications);
}
} catch (notifError) {
console.warn("Failed to notify staff:", notifError);
}
return res.status(201).json(data);
} catch (e: any) {
console.error("[Moderation API] Error creating report:", e?.message);
return res.status(500).json({ error: "Failed to create report" });
}
});
// Staff Members API
app.get("/api/staff/members", async (_req, res) => {
try {
console.log(
"[Staff] GET /api/staff/members - adminSupabase initialized:",
!!adminSupabase,
);
if (!adminSupabase) {
console.error("[Staff] adminSupabase is not initialized");
return res.status(500).json({
error: "Supabase client not initialized",
message: "SUPABASE_URL or SUPABASE_SERVICE_ROLE not set",
});
}
const { data, error } = await adminSupabase
.from("staff_members")
.select("*")
.order("full_name", { ascending: true });
if (error) {
console.error("[Staff] Error fetching staff members:", error);
if (isTableMissing(error)) {
console.log("[Staff] Table not found, returning empty array");
return res.json([]);
}
return res.status(500).json({ error: error.message });
}
console.log(
"[Staff] Successfully fetched",
(data || []).length,
"staff members",
);
return res.json(data || []);
} catch (e: any) {
console.error("[Staff] Unexpected error:", e);
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/staff/members/seed", async (req, res) => {
try {
// Ensure response headers are set correctly
res.setHeader("Content-Type", "application/json");
if (!adminSupabase) {
console.error("[Staff Seed] adminSupabase is not initialized");
return res.status(500).json({
error: "Supabase client not initialized",
message: "SUPABASE_URL or SUPABASE_SERVICE_ROLE not set",
});
}
// First, check if table exists and is accessible
const { data: existingData, error: existingError } = await adminSupabase
.from("staff_members")
.select("count")
.limit(1);
if (existingError) {
console.error("[Staff Seed] Table check error:", existingError);
return res.status(500).json({
error: "Cannot access staff_members table",
tableError: existingError,
});
}
console.log("[Staff Seed] Table exists and is accessible");
const mockMembers = [
{
email: "alex@aethex.dev",
full_name: "Alex Chen",
position: "Founder & CEO",
department: "Executive",
phone: "+1 (555) 000-0001",
role: "owner",
location: "San Francisco, CA",
},
{
email: "jordan@aethex.dev",
full_name: "Jordan Martinez",
position: "CTO",
department: "Engineering",
phone: "+1 (555) 000-0002",
role: "admin",
location: "New York, NY",
},
{
email: "sam@aethex.dev",
full_name: "Sam Patel",
position: "Community Manager",
department: "Community",
phone: "+1 (555) 000-0003",
role: "staff",
location: "Austin, TX",
},
{
email: "taylor@aethex.dev",
full_name: "Taylor Kim",
position: "Operations Lead",
department: "Operations",
phone: "+1 (555) 000-0004",
role: "staff",
location: "Seattle, WA",
},
{
email: "casey@aethex.dev",
full_name: "Casey Zhang",
position: "Software Engineer",
department: "Engineering",
phone: "+1 (555) 000-0005",
role: "employee",
location: "San Francisco, CA",
},
{
email: "morgan@aethex.dev",
full_name: "Morgan Lee",
position: "Designer",
department: "Design",
phone: "+1 (555) 000-0006",
role: "employee",
location: "Los Angeles, CA",
},
{
email: "alex.kim@aethex.dev",
full_name: "Alex Kim",
position: "Marketing Manager",
department: "Marketing",
phone: "+1 (555) 000-0007",
role: "staff",
location: "Boston, MA",
},
{
email: "jordan.lee@aethex.dev",
full_name: "Jordan Lee",
position: "Data Analyst",
department: "Analytics",
phone: "+1 (555) 000-0008",
role: "employee",
location: "Denver, CO",
},
];
try {
const { data, error } = await adminSupabase
.from("staff_members")
.upsert(mockMembers, { onConflict: "email" })
.select();
if (error) {
console.error("[Staff Seed Error] Supabase error:", error);
return res.status(500).json({
error: "Failed to seed staff members",
supabaseError: error,
mockMembersCount: mockMembers.length,
});
}
console.log(
"[Staff Seed Success] Inserted",
data?.length || 0,
"members",
);
const response = {
success: true,
count: data?.length || 0,
members: data || [],
};
return res.status(201).json(response);
} catch (insertError: any) {
console.error("[Staff Seed Insert Error]", insertError);
return res.status(500).json({
error: "Exception during insert",
message: insertError?.message || String(insertError),
});
}
} catch (e: any) {
console.error("[Staff Seed Exception]", e);
res.status(500).json({ error: e?.message || String(e) });
}
});
app.post("/api/staff/members", async (req, res) => {
try {
const {
user_id,
email,
full_name,
position,
department,
phone,
avatar_url,
role,
hired_date,
} = req.body || {};
if (!email || !full_name) {
return res.status(400).json({
error: "Missing required fields: email, full_name",
});
}
const { data, error } = await adminSupabase
.from("staff_members")
.insert([
{
user_id: user_id || null,
email,
full_name,
position: position || null,
department: department || null,
phone: phone || null,
avatar_url: avatar_url || null,
role: role || "employee",
hired_date: hired_date || null,
},
])
.select();
if (error) {
return res.status(500).json({
error: "Failed to create staff member",
details: error.message,
});
}
return res.status(201).json(data?.[0] || {});
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.get("/api/staff/members-detail", async (req, res) => {
try {
const id = String(req.query.id || "");
if (!id) {
return res.status(400).json({ error: "Missing staff member ID" });
}
const { data, error } = await adminSupabase
.from("staff_members")
.select("*")
.eq("id", id)
.single();
if (error || !data) {
return res.status(404).json({ error: "Staff member not found" });
}
return res.json(data);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.put("/api/staff/members-detail", async (req, res) => {
try {
const id = String(req.query.id || "");
if (!id) {
return res.status(400).json({ error: "Missing staff member ID" });
}
const updates = req.body || {};
const { data, error } = await adminSupabase
.from("staff_members")
.update({
...updates,
updated_at: new Date().toISOString(),
})
.eq("id", id)
.select()
.single();
if (error) {
return res.status(500).json({
error: "Failed to update staff member",
details: error.message,
});
}
if (!data) {
return res.status(404).json({ error: "Staff member not found" });
}
return res.json(data);
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
app.delete("/api/staff/members-detail", async (req, res) => {
try {
const id = String(req.query.id || "");
if (!id) {
return res.status(400).json({ error: "Missing staff member ID" });
}
const { error } = await adminSupabase
.from("staff_members")
.delete()
.eq("id", id);
if (error) {
return res.status(500).json({
error: "Failed to delete staff member",
details: error.message,
});
}
return res.json({ success: true, id });
} catch (e: any) {
return res.status(500).json({ error: e?.message || String(e) });
}
});
// Track device login and send security alert
app.post("/api/auth/login-device", async (req, res) => {
try {
const { user_id, device_name, ip_address, user_agent } = req.body;
if (!user_id) {
return res.status(400).json({ error: "user_id required" });
}
// Store device login
const { data: sessionData } = await adminSupabase
.from("game_sessions")
.insert({
user_id,
device_id: device_name || "Unknown Device",
ip_address: ip_address || null,
user_agent: user_agent || null,
session_token: randomUUID(),
})
.select()
.single();
// Check if this is a new device (security alert)
const { data: previousSessions } = await adminSupabase
.from("game_sessions")
.select("device_id")
.eq("user_id", user_id)
.neq("device_id", device_name || "Unknown Device");
// If new device, send security notification
if (!previousSessions || previousSessions.length === 0) {
try {
await adminSupabase.from("notifications").insert({
user_id,
type: "warning",
title: "🔐 New device login detected",
message: `New login from ${device_name || "Unknown device"} at ${ip_address || "Unknown location"}. If this wasn't you, please secure your account.`,
});
} catch (notifError) {
console.warn("Failed to create security notification:", notifError);
}
}
return res.status(201).json(sessionData);
} catch (e: any) {
console.error("[Auth API] Error tracking login:", e?.message);
return res.status(500).json({ error: "Failed to track login" });
}
});
// Admin endpoint to delete user account
app.delete("/api/admin/users/delete", async (req, res) => {
try {
const adminToken =
req.headers.authorization?.replace("Bearer ", "") || "";
if (adminToken !== process.env.DISCORD_ADMIN_REGISTER_TOKEN) {
return res.status(401).json({ error: "Unauthorized" });
}
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "Missing email parameter" });
}
// Get the user by email
const { data: profile, error: profileError } = await adminSupabase
.from("user_profiles")
.select("user_id, email")
.eq("email", email)
.single();
if (profileError || !profile) {
return res.status(404).json({
error: "User not found",
details: profileError?.message,
});
}
const userId = profile.user_id;
// Delete from various tables in order
await adminSupabase
.from("achievements_earned")
.delete()
.eq("user_id", userId);
await adminSupabase.from("applications").delete().eq("user_id", userId);
await adminSupabase
.from("creator_profiles")
.delete()
.eq("user_id", userId);
await adminSupabase.from("projects").delete().eq("user_id", userId);
await adminSupabase.from("social_posts").delete().eq("user_id", userId);
await adminSupabase
.from("user_email_links")
.delete()
.eq("user_id", userId);
await adminSupabase
.from("discord_links")
.delete()
.eq("user_id", userId);
await adminSupabase.from("web3_wallets").delete().eq("user_id", userId);
// Delete user profile
const { error: profileDeleteError } = await adminSupabase
.from("user_profiles")
.delete()
.eq("user_id", userId);
if (profileDeleteError) {
return res.status(500).json({
error: "Failed to delete user profile",
details: profileDeleteError.message,
});
}
// Delete from Supabase auth
try {
await (adminSupabase.auth.admin as any).deleteUser(userId);
} catch (authError: any) {
console.warn("Auth deletion warning:", authError?.message);
}
return res.json({
success: true,
message: `User account ${email} has been successfully deleted`,
userId,
});
} catch (e: any) {
console.error("[Admin API] Error deleting user:", e?.message);
return res.status(500).json({
error: "Internal server error",
message: e?.message,
});
}
});
} catch (e) {
console.warn("Admin API not initialized:", e);
}
// Blog API routes
app.get("/api/blog", blogIndexHandler);
app.get("/api/blog/:slug", (req: express.Request, res: express.Response) => {
return blogSlugHandler(req, res);
});
return app;
}