import type { Express, Request, Response, NextFunction } from "express"; import { createServer, type Server } from "http"; import { spawn, type ChildProcessWithoutNullStreams } from "child_process"; import { randomUUID } from "crypto"; import { storage } from "./storage.js"; import { loginSchema, signupSchema } from "../shared/schema.js"; import { supabase } from "./supabase.js"; import { getChatResponse } from "./openai.js"; import { capabilityGuard } from "./capability-guard.js"; import { startOAuthLinking, handleOAuthCallback } from "./oauth-handlers.js"; import communityRoutes from "./community-routes.js"; import messagingRoutes from "./messaging-routes.js"; import gameforgeRoutes from "./gameforge-routes.js"; import callRoutes from "./call-routes.js"; import discordRoutes from "./discord-routes.js"; import { socketService } from "./socket-service.js"; import { attachOrgContext, requireOrgMember, assertProjectAccess } from "./org-middleware.js"; import { orgScoped, orgEq, getOrgIdOrThrow } from "./org-storage.js"; // Extend session type declare module 'express-session' { interface SessionData { userId?: string; isAdmin?: boolean; accessToken?: string; } } // Auth middleware - requires any authenticated user function requireAuth(req: Request, res: Response, next: NextFunction) { if (!req.session.userId) { return res.status(401).json({ error: "Unauthorized" }); } next(); } // Admin middleware - requires authenticated admin user function requireAdmin(req: Request, res: Response, next: NextFunction) { if (!req.session.userId) { return res.status(401).json({ error: "Unauthorized" }); } if (!req.session.isAdmin) { return res.status(403).json({ error: "Admin access required" }); } next(); } // Project access middleware - requires project access with minimum role function requireProjectAccess(minRole: 'owner' | 'admin' | 'contributor' | 'viewer' = 'viewer') { return async (req: Request, res: Response, next: NextFunction) => { const projectId = req.params.id || req.params.projectId || req.body.project_id; if (!projectId) { return res.status(400).json({ error: "Project ID required" }); } const userId = req.session.userId; if (!userId) { return res.status(401).json({ error: "Unauthorized" }); } const accessCheck = await assertProjectAccess(projectId, userId, minRole); if (!accessCheck.hasAccess) { return res.status(403).json({ error: "Access denied", message: accessCheck.reason || "You do not have permission to access this project" }); } // Attach project to request for later use (req as any).project = accessCheck.project; next(); }; } export async function registerRoutes( httpServer: Server, app: Express ): Promise { // Initialize Socket.io for real-time messaging socketService.initialize(httpServer); // ===== Admin CLI process registry ===== const CLI_ALLOWLIST: Record = { build: { cmd: "npm", args: ["run", "build"], label: "npm run build" }, "migrate-status": { cmd: "npx", args: ["drizzle-kit", "status"], label: "drizzle status" }, migrate: { cmd: "npx", args: ["drizzle-kit", "migrate:push"], label: "drizzle migrate" }, "migrate-os": { cmd: "npx", args: ["ts-node", "script/run-os-migration.ts"], label: "os kernel migrate" }, seed: { cmd: "npx", args: ["ts-node", "script/seed.ts"], label: "seed" }, test: { cmd: "bash", args: ["./test-implementation.sh"], label: "test-implementation" }, }; const cliProcesses = new Map(); // Apply capability guard to Hub and OS routes app.use("/api/hub/*", capabilityGuard); app.use("/api/os/entitlements/*", capabilityGuard); app.use("/api/os/link/*", capabilityGuard); // Mount community routes (events, opportunities, messages) app.use("/api", communityRoutes); // Mount messaging routes (conversations, direct messages) app.use("/api/conversations", messagingRoutes); // Mount GameForge routes (projects, team, builds, sprints, tasks) app.use("/api/gameforge", gameforgeRoutes); // Mount call routes (voice/video calls) app.use("/api/calls", callRoutes); // Mount Discord integration routes app.use("/api/discord", discordRoutes); // ========== ADMIN CLI ROUTES ========== // Start a CLI command app.post("/api/admin/cli/start", requireAdmin, async (req, res) => { try { const { command } = req.body; if (!command || !CLI_ALLOWLIST[command]) { return res.status(400).json({ error: "Invalid command", allowed: Object.keys(CLI_ALLOWLIST).map(k => ({ key: k, label: CLI_ALLOWLIST[k].label })) }); } const config = CLI_ALLOWLIST[command]; const processId = randomUUID(); // Spawn the process const proc = spawn(config.cmd, config.args, { cwd: process.cwd(), shell: true, env: { ...process.env } }); cliProcesses.set(processId, { proc, status: "running" }); // Handle process exit proc.on("close", (code) => { const entry = cliProcesses.get(processId); if (entry) { entry.status = code === 0 ? "exited" : "error"; } }); proc.on("error", (err) => { const entry = cliProcesses.get(processId); if (entry) { entry.status = "error"; } console.error(`[CLI ${processId}] Error:`, err); }); res.json({ success: true, processId, command: config.label }); } catch (err: any) { console.error("[CLI Start]", err); res.status(500).json({ error: err.message }); } }); // Stream output from a CLI process app.get("/api/admin/cli/stream/:id", requireAdmin, async (req, res) => { try { const { id } = req.params; const entry = cliProcesses.get(id); if (!entry) { return res.status(404).json({ error: "Process not found" }); } // Set up Server-Sent Events res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.flushHeaders(); const { proc, status } = entry; // If already finished, send status and close if (status !== "running") { res.write(`data: ${JSON.stringify({ type: "status", status })}\n\n`); res.write(`data: ${JSON.stringify({ type: "end" })}\n\n`); return res.end(); } // Stream stdout proc.stdout.on("data", (data: Buffer) => { res.write(`data: ${JSON.stringify({ type: "stdout", data: data.toString() })}\n\n`); }); // Stream stderr proc.stderr.on("data", (data: Buffer) => { res.write(`data: ${JSON.stringify({ type: "stderr", data: data.toString() })}\n\n`); }); // Handle process close proc.on("close", (code) => { res.write(`data: ${JSON.stringify({ type: "exit", code })}\n\n`); res.write(`data: ${JSON.stringify({ type: "end" })}\n\n`); res.end(); }); // Handle client disconnect req.on("close", () => { // Don't kill the process, just stop streaming }); } catch (err: any) { console.error("[CLI Stream]", err); res.status(500).json({ error: err.message }); } }); // Kill a running CLI process app.post("/api/admin/cli/kill/:id", requireAdmin, async (req, res) => { try { const { id } = req.params; const entry = cliProcesses.get(id); if (!entry) { return res.status(404).json({ error: "Process not found" }); } if (entry.status === "running") { entry.proc.kill("SIGTERM"); entry.status = "exited"; } res.json({ success: true }); } catch (err: any) { console.error("[CLI Kill]", err); res.status(500).json({ error: err.message }); } }); // List available CLI commands app.get("/api/admin/cli/commands", requireAdmin, async (req, res) => { res.json({ commands: Object.entries(CLI_ALLOWLIST).map(([key, config]) => ({ key, label: config.label })) }); }); // ========== OAUTH ROUTES ========== // Start OAuth linking flow (get authorization URL) app.post("/api/oauth/link/:provider", requireAuth, startOAuthLinking); // OAuth callback (provider redirects here with code) app.get("/api/oauth/callback/:provider", handleOAuthCallback); // ========== AETHEX COMPILER AND APP ROUTES ========== // Compile AeThex code app.post("/api/aethex/compile", requireAuth, async (req, res) => { try { const { code, target = "javascript" } = req.body; if (!code) { return res.status(400).json({ error: "Code is required" }); } if (!["javascript", "roblox", "uefn", "unity"].includes(target)) { return res.status(400).json({ error: "Invalid target. Must be: javascript, roblox, uefn, or unity" }); } // Write source to temp file const crypto = await import("crypto"); const fs = await import("fs"); const path = await import("path"); const os = await import("os"); const tempId = crypto.randomUUID(); const tempDir = path.join(os.tmpdir(), "aethex-compile"); await fs.promises.mkdir(tempDir, { recursive: true }); const sourceFile = path.join(tempDir, `${tempId}.aethex`); await fs.promises.writeFile(sourceFile, code, "utf-8"); // Compile using the AeThex CLI const { spawn } = await import("child_process"); const compilerPath = path.join(process.cwd(), "packages/aethex-cli/bin/aethex.js"); return new Promise((resolve) => { const proc = spawn("node", [compilerPath, "compile", sourceFile, "-t", target], { cwd: process.cwd(), }); let stdout = ""; let stderr = ""; proc.stdout.on("data", (data) => { stdout += data.toString(); }); proc.stderr.on("data", (data) => { stderr += data.toString(); }); proc.on("close", async (code) => { try { // Read compiled output const ext = target === "roblox" ? "lua" : target === "javascript" ? "js" : target; const outputFile = sourceFile.replace(".aethex", `.${ext}`); if (code === 0 && await fs.promises.access(outputFile).then(() => true).catch(() => false)) { const output = await fs.promises.readFile(outputFile, "utf-8"); // Cleanup await fs.promises.unlink(sourceFile).catch(() => {}); await fs.promises.unlink(outputFile).catch(() => {}); res.json({ success: true, output, target, message: "Compilation successful" }); } else { // Cleanup on error await fs.promises.unlink(sourceFile).catch(() => {}); res.status(400).json({ success: false, error: "Compilation failed", details: stderr || stdout || "Unknown error" }); } } catch (error) { console.error("Compile error:", error); res.status(500).json({ error: "Failed to read compilation output" }); } resolve(undefined); }); }); } catch (error) { console.error("Compilation error:", error); res.status(500).json({ error: "Compilation failed" }); } }); // Create or update an AeThex app app.post("/api/aethex/apps", requireAuth, async (req, res) => { try { const { name, description, source_code, icon_url, category, is_public, tags, targets } = req.body; const userId = req.session.userId!; if (!name || !source_code) { return res.status(400).json({ error: "Name and source_code are required" }); } // Compile the code to JavaScript by default const compileRes = await fetch(`http://localhost:${process.env.PORT || 5000}/api/aethex/compile`, { method: "POST", headers: { "Content-Type": "application/json", "Cookie": req.headers.cookie || "", }, body: JSON.stringify({ code: source_code, target: "javascript" }), }); const compileData = await compileRes.json(); if (!compileData.success) { return res.status(400).json({ error: "App code failed to compile", details: compileData.details }); } const { data: app, error } = await supabase .from("aethex_apps") .insert({ owner_id: userId, name, description, source_code, compiled_js: compileData.output, icon_url, category: category || "utility", is_public: is_public || false, tags: tags || [], targets: targets || ["javascript"], }) .select() .single(); if (error) throw error; res.json({ success: true, app }); } catch (error) { console.error("App creation error:", error); res.status(500).json({ error: "Failed to create app" }); } }); // Get all public apps (App Store) app.get("/api/aethex/apps", async (req, res) => { try { const { category, featured, search } = req.query; let query = supabase.from("aethex_apps").select("*").eq("is_public", true); if (category) { query = query.eq("category", category); } if (featured === "true") { query = query.eq("is_featured", true); } if (search) { query = query.or(`name.ilike.%${search}%,description.ilike.%${search}%`); } const { data: apps, error } = await query.order("install_count", { ascending: false }).limit(50); if (error) throw error; res.json({ apps }); } catch (error) { console.error("Apps fetch error:", error); res.status(500).json({ error: "Failed to fetch apps" }); } }); // Get user's own apps app.get("/api/aethex/apps/my", requireAuth, async (req, res) => { try { const userId = req.session.userId!; const { data: apps, error } = await supabase .from("aethex_apps") .select("*") .eq("owner_id", userId) .order("updated_at", { ascending: false }); if (error) throw error; res.json({ apps }); } catch (error) { console.error("My apps fetch error:", error); res.status(500).json({ error: "Failed to fetch your apps" }); } }); // Get a specific app app.get("/api/aethex/apps/:id", async (req, res) => { try { const { id } = req.params; const { data: app, error } = await supabase .from("aethex_apps") .select("*") .eq("id", id) .single(); if (error || !app) { return res.status(404).json({ error: "App not found" }); } // Only return source code if user is the owner or app is public if (!app.is_public && app.owner_id !== req.session.userId) { return res.status(403).json({ error: "Access denied" }); } res.json({ app }); } catch (error) { console.error("App fetch error:", error); res.status(500).json({ error: "Failed to fetch app" }); } }); // Install an app app.post("/api/aethex/apps/:id/install", requireAuth, async (req, res) => { try { const { id } = req.params; const userId = req.session.userId!; // Check if app exists const { data: app, error: appError } = await supabase .from("aethex_apps") .select("*") .eq("id", id) .eq("is_public", true) .single(); if (appError || !app) { return res.status(404).json({ error: "App not found or not public" }); } // Check if already installed const { data: existing } = await supabase .from("aethex_app_installations") .select("*") .eq("app_id", id) .eq("user_id", userId) .single(); if (existing) { return res.json({ success: true, message: "App already installed", installation: existing }); } // Install the app const { data: installation, error } = await supabase .from("aethex_app_installations") .insert({ app_id: id, user_id: userId, }) .select() .single(); if (error) throw error; // Increment install count await supabase .from("aethex_apps") .update({ install_count: (app.install_count || 0) + 1 }) .eq("id", id); res.json({ success: true, installation }); } catch (error) { console.error("App installation error:", error); res.status(500).json({ error: "Failed to install app" }); } }); // Get user's installed apps app.get("/api/aethex/apps/installed/my", requireAuth, async (req, res) => { try { const userId = req.session.userId!; const { data: installations, error } = await supabase .from("aethex_app_installations") .select(` *, app:aethex_apps(*) `) .eq("user_id", userId) .order("installed_at", { ascending: false }); if (error) throw error; res.json({ installations }); } catch (error) { console.error("Installed apps fetch error:", error); res.status(500).json({ error: "Failed to fetch installed apps" }); } }); // Run an installed app (get compiled code) app.post("/api/aethex/apps/:id/run", requireAuth, async (req, res) => { try { const { id } = req.params; const userId = req.session.userId!; // Check if user has installed the app const { data: installation } = await supabase .from("aethex_app_installations") .select(` *, app:aethex_apps(*) `) .eq("app_id", id) .eq("user_id", userId) .single(); if (!installation) { return res.status(403).json({ error: "App not installed" }); } // Update last_used_at await supabase .from("aethex_app_installations") .update({ last_used_at: new Date().toISOString() }) .eq("id", installation.id); res.json({ success: true, app: installation.app, compiled_code: (installation.app as any).compiled_js }); } catch (error) { console.error("App run error:", error); res.status(500).json({ error: "Failed to run app" }); } }); // ========== MODE MANAGEMENT ROUTES ========== // Get user mode preference app.get("/api/user/mode-preference", requireAuth, async (req, res) => { try { const { data, error } = await supabase .from("aethex_user_mode_preference") .select("mode") .eq("user_id", req.session.userId) .single(); if (error && error.code !== "PGRST116") { throw error; } res.json({ mode: data?.mode || "foundation" }); } catch (error) { console.error("Mode fetch error:", error); res.status(500).json({ error: "Failed to fetch mode preference" }); } }); // Update user mode preference app.put("/api/user/mode-preference", requireAuth, async (req, res) => { try { const { mode } = req.body; if (!mode || !["foundation", "corporation"].includes(mode)) { return res.status(400).json({ error: "Invalid mode" }); } const { error } = await supabase .from("aethex_user_mode_preference") .upsert({ user_id: req.session.userId, mode, updated_at: new Date().toISOString(), }); if (error) throw error; res.json({ success: true, mode }); } catch (error) { console.error("Mode update error:", error); res.status(500).json({ error: "Failed to update mode preference" }); } }); // Get workspace policy app.get("/api/workspace/policy", requireAuth, async (req, res) => { try { // For now, use a default workspace const workspaceId = "default"; const { data, error } = await supabase .from("aethex_workspace_policy") .select("*") .eq("workspace_id", workspaceId) .single(); if (error && error.code !== "PGRST116") { throw error; } res.json(data || {}); } catch (error) { console.error("Policy fetch error:", error); res.status(500).json({ error: "Failed to fetch workspace policy" }); } }); // ========== ORGANIZATION ROUTES (Multi-tenancy) ========== // Apply org context middleware to all org-scoped routes app.use("/api/orgs", requireAuth, attachOrgContext); app.use("/api/projects", attachOrgContext); app.use("/api/files", attachOrgContext); app.use("/api/marketplace", attachOrgContext); // Get user's organizations app.get("/api/orgs", async (req, res) => { try { const { data: memberships, error } = await supabase .from("organization_members") .select("organization_id, role, organizations(*)") .eq("user_id", req.session.userId); if (error) throw error; const orgs = memberships?.map(m => ({ ...m.organizations, userRole: m.role, })) || []; res.json({ organizations: orgs }); } catch (error: any) { console.error("Fetch orgs error:", error); res.status(500).json({ error: "Failed to fetch organizations" }); } }); // Create new organization app.post("/api/orgs", async (req, res) => { try { const { name, slug } = req.body; if (!name || !slug) { return res.status(400).json({ error: "Name and slug are required" }); } // Check slug uniqueness const { data: existing } = await supabase .from("organizations") .select("id") .eq("slug", slug) .single(); if (existing) { return res.status(400).json({ error: "Slug already taken" }); } // Create organization const { data: org, error: orgError } = await supabase .from("organizations") .insert({ name, slug, owner_user_id: req.session.userId, plan: "free", }) .select() .single(); if (orgError) throw orgError; // Add creator as owner member const { error: memberError } = await supabase .from("organization_members") .insert({ organization_id: org.id, user_id: req.session.userId, role: "owner", }); if (memberError) throw memberError; res.status(201).json({ organization: org }); } catch (error: any) { console.error("Create org error:", error); res.status(500).json({ error: error.message || "Failed to create organization" }); } }); // Get organization by slug app.get("/api/orgs/:slug", async (req, res) => { try { const { data: org, error } = await supabase .from("organizations") .select("*") .eq("slug", req.params.slug) .single(); if (error || !org) { return res.status(404).json({ error: "Organization not found" }); } // Check if user is member const { data: membership } = await supabase .from("organization_members") .select("role") .eq("organization_id", org.id) .eq("user_id", req.session.userId) .single(); if (!membership) { return res.status(403).json({ error: "Not a member of this organization" }); } res.json({ organization: { ...org, userRole: membership.role } }); } catch (error: any) { console.error("Fetch org error:", error); res.status(500).json({ error: "Failed to fetch organization" }); } }); // Get organization members app.get("/api/orgs/:slug/members", async (req, res) => { try { // Get org const { data: org, error: orgError } = await supabase .from("organizations") .select("id") .eq("slug", req.params.slug) .single(); if (orgError || !org) { return res.status(404).json({ error: "Organization not found" }); } // Check if user is member const { data: userMembership } = await supabase .from("organization_members") .select("role") .eq("organization_id", org.id) .eq("user_id", req.session.userId) .single(); if (!userMembership) { return res.status(403).json({ error: "Not a member of this organization" }); } // Get all members const { data: members, error: membersError } = await supabase .from("organization_members") .select("id, user_id, role, created_at, profiles(username, full_name, avatar_url, email)") .eq("organization_id", org.id); if (membersError) throw membersError; res.json({ members }); } catch (error: any) { console.error("Fetch members error:", error); res.status(500).json({ error: "Failed to fetch members" }); } }); // ========== AUTH ROUTES (Supabase Auth) ========== // Login via Supabase Auth app.post("/api/auth/login", async (req, res) => { try { const result = loginSchema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: "Invalid email or password format" }); } const { email, password } = result.data; // Authenticate with Supabase const { data, error } = await supabase.auth.signInWithPassword({ email, password, }); if (error || !data.user) { return res.status(401).json({ error: error?.message || "Invalid credentials" }); } // Get user profile from public.profiles const profile = await storage.getProfile(data.user.id); // Check if user is admin (based on profile role or email) const isAdmin = ['admin', 'oversee', 'employee'].includes(profile?.role || '') || email.includes('admin'); // Set express session req.session.regenerate((err) => { if (err) { return res.status(500).json({ error: "Session error" }); } req.session.userId = data.user.id; req.session.isAdmin = isAdmin; req.session.accessToken = data.session?.access_token; req.session.save((saveErr) => { if (saveErr) { return res.status(500).json({ error: "Session save error" }); } res.json({ success: true, user: { id: data.user.id, email: data.user.email, username: profile?.username || data.user.email?.split('@')[0], isAdmin } }); }); }); } catch (err: any) { console.error('Login error:', err); res.status(500).json({ error: err.message }); } }); // Signup via Supabase Auth app.post("/api/auth/signup", async (req, res) => { try { const result = signupSchema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: result.error.errors[0].message }); } const { email, password, username } = result.data; // Create user in Supabase Auth const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { username: username || email.split('@')[0] } } }); if (error || !data.user) { return res.status(400).json({ error: error?.message || "Signup failed" }); } res.json({ success: true, message: data.session ? "Account created successfully" : "Please check your email to confirm your account", user: { id: data.user.id, email: data.user.email } }); } catch (err: any) { console.error('Signup error:', err); res.status(500).json({ error: err.message }); } }); // Logout app.post("/api/auth/logout", async (req, res) => { try { // Sign out from Supabase await supabase.auth.signOut(); req.session.destroy((err) => { if (err) { return res.status(500).json({ error: "Logout failed" }); } res.clearCookie('connect.sid'); res.json({ success: true }); }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get current session app.get("/api/auth/session", async (req, res) => { if (!req.session.userId) { return res.json({ authenticated: false }); } // Get profile from storage const profile = await storage.getProfile(req.session.userId); res.json({ authenticated: true, user: { id: req.session.userId, username: profile?.username || 'User', email: profile?.email, isAdmin: req.session.isAdmin } }); }); // ========== AUTHENTICATED USER ROUTES ========== // Get current user's profile (for Passport app) app.get("/api/me/profile", requireAuth, async (req, res) => { try { const profile = await storage.getProfile(req.session.userId!); if (!profile) { return res.status(404).json({ error: "Profile not found" }); } res.json(profile); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get current user's achievements app.get("/api/me/achievements", requireAuth, async (req, res) => { try { const userAchievements = await storage.getUserAchievements(req.session.userId!); res.json(userAchievements); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get current user's passport app.get("/api/me/passport", requireAuth, async (req, res) => { try { const passport = await storage.getUserPassport(req.session.userId!); if (!passport) { return res.status(404).json({ error: "Passport not found" }); } res.json(passport); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Create passport for current user app.post("/api/me/passport", requireAuth, async (req, res) => { try { const passport = await storage.createUserPassport(req.session.userId!); res.json(passport); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== PUBLIC API ROUTES ========== // Get ecosystem metrics (public - for dashboard) app.get("/api/metrics", async (req, res) => { try { const metrics = await storage.getMetrics(); res.json(metrics); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Minimal tracking endpoint for upgrade clicks app.post("/api/track/upgrade-click", async (req, res) => { try { const { source, timestamp } = req.body || {}; await storage.logFunnelEvent({ user_id: req.session.userId, event_type: 'upgrade_click', source: source || 'unknown', created_at: timestamp, }); res.json({ ok: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Generic funnel event tracking app.post("/api/track/event", async (req, res) => { try { const { event_type, source, payload, timestamp } = req.body || {}; if (!event_type) return res.status(400).json({ error: 'event_type is required' }); await storage.logFunnelEvent({ user_id: req.session.userId, event_type, source, payload, created_at: timestamp, }); res.json({ ok: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== PAYMENTS ========== // Create Stripe Checkout Session app.post("/api/payments/create-checkout-session", async (req, res) => { try { const secret = process.env.STRIPE_SECRET_KEY; if (!secret) { return res.status(400).json({ error: "Stripe not configured" }); } const priceId = process.env.STRIPE_PRICE_ID; // optional const successUrl = process.env.STRIPE_SUCCESS_URL || `${req.headers.origin || "https://aethex.network"}/success`; const cancelUrl = process.env.STRIPE_CANCEL_URL || `${req.headers.origin || "https://aethex.network"}/cancel`; const body = new URLSearchParams(); body.set("mode", "payment"); body.set("success_url", successUrl); body.set("cancel_url", cancelUrl); body.set("client_reference_id", req.session.userId || "guest"); if (priceId) { body.set("line_items[0][price]", priceId); body.set("line_items[0][quantity]", "1"); } else { body.set("line_items[0][price_data][currency]", "usd"); body.set("line_items[0][price_data][product_data][name]", "Architect Access"); body.set("line_items[0][price_data][unit_amount]", String(50000)); // $500.00 body.set("line_items[0][quantity]", "1"); } const resp = await fetch("https://api.stripe.com/v1/checkout/sessions", { method: "POST", headers: { Authorization: `Bearer ${secret}`, "Content-Type": "application/x-www-form-urlencoded", }, body, }); const json = await resp.json(); if (!resp.ok) { return res.status(400).json({ error: json.error?.message || "Stripe error" }); } res.json({ url: json.url, id: json.id }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== PUBLIC DIRECTORY ROUTES ========== // Get public directory of founding architects only app.get("/api/directory/architects", async (req, res) => { try { const profiles = await storage.getProfiles(); // Only show the founding team members with leadership roles const LEADERSHIP_ROLES = ['oversee', 'admin']; const publicProfiles = profiles .filter(p => { const role = (p.role || '').toLowerCase(); return LEADERSHIP_ROLES.includes(role); }) .map((p, index) => ({ id: String(index + 1).padStart(3, '0'), name: p.full_name || p.username || p.email?.split('@')[0] || 'Architect', role: p.role || 'member', bio: p.bio, level: p.level, xp: p.total_xp, passportId: p.aethex_passport_id, skills: p.skills, username: p.username, })); res.json(publicProfiles); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get public directory of projects app.get("/api/directory/projects", async (req, res) => { try { const projects = await storage.getProjects(); // Map to public-safe fields const publicProjects = projects.map(p => ({ id: p.id, name: p.title, description: p.description, techStack: p.technologies, status: p.status, })); res.json(publicProjects); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get single architect profile by username/slug app.get("/api/directory/architects/:slug", async (req, res) => { try { const { slug } = req.params; const profiles = await storage.getProfiles(); const profile = profiles.find(p => p.aethex_passport_id?.toLowerCase() === slug.toLowerCase() || p.full_name?.toLowerCase() === slug.toLowerCase() || p.username?.toLowerCase() === slug.toLowerCase() || p.email?.split('@')[0].toLowerCase() === slug.toLowerCase() ); if (!profile) { return res.status(404).json({ error: "Architect not found" }); } // Return public-safe fields only const socialLinks = profile.social_links || {}; res.json({ id: profile.id, name: profile.full_name || profile.username || profile.email?.split('@')[0] || 'Architect', role: profile.role, bio: profile.bio, level: profile.level, xp: profile.total_xp, passportId: profile.aethex_passport_id, skills: profile.skills, isVerified: profile.is_verified, avatarUrl: profile.avatar_url, github: socialLinks.github, twitter: socialLinks.twitter, website: socialLinks.website, }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== ADMIN-PROTECTED API ROUTES ========== // Get all profiles (admin only) app.get("/api/profiles", requireAdmin, async (req, res) => { try { const profiles = await storage.getProfiles(); res.json(profiles); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get single profile (admin only) app.get("/api/profiles/:id", requireAdmin, async (req, res) => { try { const profile = await storage.getProfile(req.params.id); if (!profile) { return res.status(404).json({ error: "Profile not found" }); } res.json(profile); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update profile (self-update OR org admin) app.patch("/api/profiles/:id", requireAuth, attachOrgContext, async (req, res) => { try { const targetProfileId = req.params.id; const requesterId = req.session.userId!; // Check authorization: self-update OR org admin/owner const isSelfUpdate = requesterId === targetProfileId; const isOrgAdmin = req.orgRole && ['admin', 'owner'].includes(req.orgRole); if (!isSelfUpdate && !isOrgAdmin) { return res.status(403).json({ error: "Forbidden", message: "You can only update your own profile or must be an org admin/owner" }); } // Log org admin updates for audit trail if (!isSelfUpdate && isOrgAdmin && req.orgId) { console.log(`[AUDIT] Org ${req.orgRole} ${requesterId} updating profile ${targetProfileId} (org: ${req.orgId})`); } const profile = await storage.updateProfile(targetProfileId, req.body); if (!profile) { return res.status(404).json({ error: "Profile not found" }); } res.json(profile); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get all projects (admin only OR org-scoped for user) app.get("/api/projects", requireAuth, async (req, res) => { try { // Admin sees all if (req.session.isAdmin) { const projects = await storage.getProjects(); return res.json(projects); } // Regular user: filter by org if available if (req.orgId) { const { data, error } = await supabase .from("projects") .select("*") .eq("organization_id", req.orgId); if (error) throw error; return res.json(data || []); } // Fallback: user's own projects const { data, error } = await supabase .from("projects") .select("*") .or(`owner_user_id.eq.${req.session.userId},user_id.eq.${req.session.userId}`); if (error) throw error; res.json(data || []); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get single project app.get("/api/projects/:id", requireAuth, requireProjectAccess('viewer'), async (req, res) => { try { res.json((req as any).project); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get project collaborators app.get("/api/projects/:id/collaborators", requireAuth, requireProjectAccess('contributor'), async (req, res) => { try { const { data, error } = await supabase .from("project_collaborators") .select("id, user_id, role, permissions, created_at, profiles(username, full_name, avatar_url, email)") .eq("project_id", req.params.id); if (error) throw error; res.json({ collaborators: data || [] }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Add project collaborator app.post("/api/projects/:id/collaborators", requireAuth, async (req, res) => { try { const accessCheck = await assertProjectAccess( req.params.id, req.session.userId!, 'admin' ); if (!accessCheck.hasAccess) { return res.status(403).json({ error: "Only project owners/admins can add collaborators" }); } const { user_id, role = 'contributor' } = req.body; if (!user_id) { return res.status(400).json({ error: "user_id is required" }); } // Check if user exists const { data: userExists } = await supabase .from("profiles") .select("id") .eq("id", user_id) .single(); if (!userExists) { return res.status(404).json({ error: "User not found" }); } // Add collaborator const { data, error } = await supabase .from("project_collaborators") .insert({ project_id: req.params.id, user_id, role, }) .select() .single(); if (error) { if (error.code === '23505') { // Unique violation return res.status(400).json({ error: "User is already a collaborator" }); } throw error; } res.status(201).json({ collaborator: data }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update collaborator role/permissions app.patch("/api/projects/:id/collaborators/:collabId", requireAuth, async (req, res) => { try { const accessCheck = await assertProjectAccess( req.params.id, req.session.userId!, 'admin' ); if (!accessCheck.hasAccess) { return res.status(403).json({ error: "Only project owners/admins can modify collaborators" }); } const { role, permissions } = req.body; const updates: any = {}; if (role) updates.role = role; if (permissions !== undefined) updates.permissions = permissions; const { data, error } = await supabase .from("project_collaborators") .update(updates) .eq("id", req.params.collabId) .eq("project_id", req.params.id) .select() .single(); if (error) throw error; if (!data) { return res.status(404).json({ error: "Collaborator not found" }); } res.json({ collaborator: data }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Remove collaborator app.delete("/api/projects/:id/collaborators/:collabId", requireAuth, async (req, res) => { try { const accessCheck = await assertProjectAccess( req.params.id, req.session.userId!, 'admin' ); if (!accessCheck.hasAccess) { return res.status(403).json({ error: "Only project owners/admins can remove collaborators" }); } const { error } = await supabase .from("project_collaborators") .delete() .eq("id", req.params.collabId) .eq("project_id", req.params.id); if (error) throw error; res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== NEW ADMIN ROUTES ========== // Get all aethex sites (admin only) // List all sites app.get("/api/sites", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const { data, error } = await orgScoped('aethex_sites', req) .select('*') .order('last_check', { ascending: false }); if (error) throw error; res.json(data || []); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Create a new site app.post("/api/sites", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_sites') .insert({ ...req.body, organization_id: orgId }) .select() .single(); if (error) throw error; res.status(201).json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update a site app.patch("/api/sites/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_sites') .update(req.body) .eq('id', req.params.id) .eq('organization_id', orgId) .select() .single(); if (error) throw error; if (!data) { return res.status(404).json({ error: "Site not found or access denied" }); } res.json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Delete a site app.delete("/api/sites/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { error, count } = await supabase .from('aethex_sites') .delete({ count: 'exact' }) .eq('id', req.params.id) .eq('organization_id', orgId); if (error) throw error; if ((count ?? 0) === 0) { return res.status(404).json({ error: "Site not found or access denied" }); } res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get auth logs (admin only) app.get("/api/auth-logs", requireAdmin, async (req, res) => { try { const logs = await storage.getAuthLogs(); res.json(logs); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get all achievements (public - shows what achievements exist) app.get("/api/achievements", async (req, res) => { try { const achievements = await storage.getAchievements(); res.json(achievements); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get applications (admin only) app.get("/api/applications", requireAdmin, async (req, res) => { try { const applications = await storage.getApplications(); res.json(applications); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get alerts for Aegis (admin only) app.get("/api/alerts", requireAdmin, async (req, res) => { try { const alerts = await storage.getAlerts(); res.json(alerts); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Resolve alert (admin only) app.patch("/api/alerts/:id", requireAdmin, async (req, res) => { try { const alert = await storage.updateAlert(req.params.id, req.body); if (!alert) { return res.status(404).json({ error: "Alert not found" }); } res.json(alert); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update application status (admin only) app.patch("/api/applications/:id", requireAdmin, async (req, res) => { try { const application = await storage.updateApplication(req.params.id, req.body); if (!application) { return res.status(404).json({ error: "Application not found" }); } res.json(application); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== PUBLIC OS API ROUTES ========== // Get public project summaries for OS (limited data, no auth required) app.get("/api/os/projects", async (req, res) => { try { const projects = await storage.getProjects(); const summaries = projects.slice(0, 10).map(p => ({ id: p.id, title: p.title, status: p.status, engine: p.engine, })); res.json(summaries); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get public architect summaries for OS (limited data, no auth required) app.get("/api/os/architects", async (req, res) => { try { const profiles = await storage.getProfiles(); const summaries = profiles.slice(0, 10).map(p => ({ id: p.id, username: p.username, level: p.level || 1, xp: p.total_xp || 0, verified: p.is_verified || false, })); res.json(summaries); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get achievements list for OS (public) app.get("/api/os/achievements", async (req, res) => { try { const achievements = await storage.getAchievements(); res.json(achievements.slice(0, 20)); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get recent activity/notifications for OS (public summary) app.get("/api/os/notifications", async (req, res) => { try { const metrics = await storage.getMetrics(); const notifications = [ { id: 1, message: `${metrics.totalProfiles} architects in network`, type: 'info' }, { id: 2, message: `${metrics.totalProjects} active projects`, type: 'info' }, { id: 3, message: 'Aegis security active', type: 'success' }, ]; res.json(notifications); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== CHATBOT API (Rate limited) ========== const chatRateLimits = new Map(); // Get chat history app.get("/api/chat/history", requireAuth, async (req, res) => { try { const userId = req.session.userId!; const history = await storage.getChatHistory(userId, 20); res.json({ history }); } catch (err: any) { console.error("Get chat history error:", err); res.status(500).json({ error: "Failed to get chat history" }); } }); app.post("/api/chat", async (req, res) => { try { const userId = req.session?.userId; const clientIP = req.ip || req.socket.remoteAddress || 'unknown'; const rateLimitKey = userId ? `user:${userId}` : `ip:${clientIP}`; const maxRequests = userId ? 30 : 10; const now = Date.now(); const rateLimit = chatRateLimits.get(rateLimitKey); if (rateLimit) { if (now < rateLimit.resetTime) { if (rateLimit.count >= maxRequests) { return res.status(429).json({ error: "Rate limit exceeded. Please wait before sending more messages." }); } rateLimit.count++; } else { chatRateLimits.set(rateLimitKey, { count: 1, resetTime: now + 60000 }); } } else { chatRateLimits.set(rateLimitKey, { count: 1, resetTime: now + 60000 }); } const { message, history } = req.body; if (!message || typeof message !== "string") { return res.status(400).json({ error: "Message is required" }); } // Save user message if user is authenticated if (userId) { const messageId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await storage.saveChatMessage(messageId, userId, 'user', message); } // Get full chat history for context if user is authenticated let fullHistory = history || []; if (userId) { const savedHistory = await storage.getChatHistory(userId, 20); fullHistory = savedHistory.map(msg => ({ role: msg.role, content: msg.content })); } const response = await getChatResponse(message, fullHistory, userId); // Save assistant response if user is authenticated if (userId) { const responseId = `assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; await storage.saveChatMessage(responseId, userId, 'assistant', response); } res.json({ response }); } catch (err: any) { console.error("Chat error:", err); res.status(500).json({ error: "Failed to get response" }); } }); // ========== AXIOM OPPORTUNITIES ROUTES ========== // Get all opportunities (public) app.get("/api/opportunities", async (req, res) => { try { let query = supabase .from('aethex_opportunities') .select('*') .order('created_at', { ascending: false }); // Optional org filter if (req.query.org_id) { query = query.eq('organization_id', req.query.org_id as string); } const { data, error } = await query; if (error) throw error; res.json(data || []); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get single opportunity // PUBLIC: Opportunities are publicly viewable for discovery app.get("/api/opportunities/:id", async (req, res) => { const IS_PUBLIC = true; // Intentionally public for marketplace discovery try { const opportunity = await storage.getOpportunity(req.params.id); if (!opportunity) { return res.status(404).json({ error: "Opportunity not found" }); } res.json(opportunity); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Create opportunity (admin only) app.post("/api/opportunities", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_opportunities') .insert({ ...req.body, organization_id: orgId }) .select() .single(); if (error) throw error; res.status(201).json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update opportunity (admin only) app.patch("/api/opportunities/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_opportunities') .update({ ...req.body, updated_at: new Date().toISOString() }) .eq('id', req.params.id) .eq('organization_id', orgId) .select() .single(); if (error) throw error; if (!data) { return res.status(404).json({ error: "Opportunity not found or access denied" }); } res.json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Delete opportunity (admin only) app.delete("/api/opportunities/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { error, count } = await supabase .from('aethex_opportunities') .delete({ count: 'exact' }) .eq('id', req.params.id) .eq('organization_id', orgId); if (error) throw error; if ((count ?? 0) === 0) { return res.status(404).json({ error: "Opportunity not found or access denied" }); } res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== AXIOM EVENTS ROUTES ========== // Get all events (public) // PUBLIC: Events are publicly viewable for community discovery, with optional org filtering app.get("/api/events", async (req, res) => { const IS_PUBLIC = true; // Intentionally public for community calendar try { let query = supabase .from('aethex_events') .select('*') .order('date', { ascending: true }); // Optional org filter if (req.query.org_id) { query = query.eq('organization_id', req.query.org_id as string); } const { data, error } = await query; if (error) throw error; res.json(data || []); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Get single event // PUBLIC: Events are publicly viewable for sharing/discovery app.get("/api/events/:id", async (req, res) => { const IS_PUBLIC = true; // Intentionally public for event sharing try { const event = await storage.getEvent(req.params.id); if (!event) { return res.status(404).json({ error: "Event not found" }); } res.json(event); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Create event (admin only) app.post("/api/events", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_events') .insert({ ...req.body, organization_id: orgId }) .select() .single(); if (error) throw error; res.status(201).json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Update event (admin only) app.patch("/api/events/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { data, error } = await supabase .from('aethex_events') .update({ ...req.body, updated_at: new Date().toISOString() }) .eq('id', req.params.id) .eq('organization_id', orgId) .select() .single(); if (error) throw error; if (!data) { return res.status(404).json({ error: "Event not found or access denied" }); } res.json(data); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // Delete event (admin only) app.delete("/api/events/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const orgId = getOrgIdOrThrow(req); const { error, count } = await supabase .from('aethex_events') .delete({ count: 'exact' }) .eq('id', req.params.id) .eq('organization_id', orgId); if (error) throw error; if ((count ?? 0) === 0) { return res.status(404).json({ error: "Event not found or access denied" }); } res.json({ success: true }); } catch (err: any) { res.status(500).json({ error: err.message }); } }); // ========== MARKETPLACE ROUTES (LEDGER-3) ========== // Purchase marketplace listing app.post("/api/marketplace/purchase", requireAuth, async (req, res) => { try { const { listing_id } = req.body; const buyer_id = req.session.userId!; if (!listing_id) { return res.status(400).json({ error: "listing_id is required" }); } // Fetch listing details const { data: listing, error: listingError } = await supabase .from("marketplace_listings") .select("*") .eq("id", listing_id) .single(); if (listingError || !listing) { return res.status(404).json({ error: "Listing not found" }); } // Prevent self-purchase if (listing.seller_id === buyer_id) { return res.status(400).json({ error: "Cannot purchase your own listing" }); } // Create transaction const transactionId = randomUUID(); const { error: transError } = await supabase .from("marketplace_transactions") .insert({ id: transactionId, buyer_id, seller_id: listing.seller_id, listing_id, amount: listing.price, status: "completed", }); if (transError) throw transError; // Emit revenue event (LEDGER-3) const { recordRevenueEvent } = await import("./revenue.js"); const revResult = await recordRevenueEvent({ source_type: "marketplace", source_id: transactionId, gross_amount: listing.price, platform_fee: 0, // Can be configured per transaction or org policy currency: "POINTS", project_id: (listing as any).project_id || null, metadata: { listing_id, buyer_id, seller_id: listing.seller_id, title: listing.title, category: listing.category, }, }); if (revResult.success && revResult.id && (listing as any).project_id) { // Compute and record splits if project_id exists (SPLITS-1) const { computeRevenueSplits, recordSplitAllocations } = await import( "./splits.js" ); const splitsResult = await computeRevenueSplits( (listing as any).project_id, listing.price ); if (splitsResult.success && splitsResult.allocations) { await recordSplitAllocations( revResult.id, (listing as any).project_id, splitsResult.allocations, splitsResult.splitVersion || 1 ); } } // Update listing purchase count await supabase .from("marketplace_listings") .update({ purchase_count: (listing.purchase_count || 0) + 1 }) .eq("id", listing_id); res.status(201).json({ success: true, transaction_id: transactionId, message: "Purchase completed", }); } catch (err: any) { console.error("Marketplace purchase error:", err); res.status(500).json({ error: err.message }); } }); // Get organization revenue summary by month (LEDGER-4) app.get("/api/revenue/summary", requireAuth, async (req, res) => { try { const org_id = (req.headers["x-org-id"] as string) || req.session.userId; // Org context from header or user const monthsParam = parseInt(req.query.months as string) || 6; const months = Math.min(monthsParam, 24); // Cap at 24 months if (!org_id) { return res.status(400).json({ error: "Org context required" }); } // Query revenue events for this org, past N months const startDate = new Date(); startDate.setMonth(startDate.getMonth() - months); const { data: events, error } = await supabase .from("revenue_events") .select("*") .eq("org_id", org_id) .gte("created_at", startDate.toISOString()) .order("created_at", { ascending: true }); if (error) throw error; // Aggregate by month const byMonth: Record< string, { gross: number; fees: number; net: number } > = {}; (events || []).forEach((event: any) => { const date = new Date(event.created_at); const monthKey = date.toISOString().substring(0, 7); // "2026-01" if (!byMonth[monthKey]) { byMonth[monthKey] = { gross: 0, fees: 0, net: 0 }; } byMonth[monthKey].gross += parseFloat(event.gross_amount || "0"); byMonth[monthKey].fees += parseFloat(event.platform_fee || "0"); byMonth[monthKey].net += parseFloat(event.net_amount || "0"); }); // Format response const summary = Object.entries(byMonth) .map(([month, { gross, fees, net }]) => ({ month, gross: gross.toFixed(2), fees: fees.toFixed(2), net: net.toFixed(2), })) .sort(); res.json(summary); } catch (err: any) { console.error("Revenue summary error:", err); res.status(500).json({ error: err.message }); } }); // Get revenue splits for a project (SPLITS-1) app.get("/api/revenue/splits/:projectId", requireAuth, async (req, res) => { try { const { projectId } = req.params; // Fetch the currently active split rule const { data: splits, error: splitsError } = await supabase .from("revenue_splits") .select("*") .eq("project_id", projectId) .is("active_until", null) // Only active rules .order("split_version", { ascending: false }) .limit(1); if (splitsError) throw splitsError; if (!splits || splits.length === 0) { return res.json({ split_version: 0, rule: {}, allocations: [], }); } const split = splits[0]; // Fetch all allocations for this project (for reporting) const { data: allocations, error: allocError } = await supabase .from("split_allocations") .select("*") .eq("project_id", projectId) .order("created_at", { ascending: false }) .limit(100); if (allocError) throw allocError; // Aggregate allocations by user const byUser: Record< string, { user_id: string; total_allocated: number; allocation_count: number; } > = {}; (allocations || []).forEach((alloc: any) => { if (!byUser[alloc.user_id]) { byUser[alloc.user_id] = { user_id: alloc.user_id, total_allocated: 0, allocation_count: 0, }; } byUser[alloc.user_id].total_allocated += parseFloat( alloc.allocated_amount || "0" ); byUser[alloc.user_id].allocation_count += 1; }); res.json({ split_version: split.split_version, rule: split.rule, active_from: split.active_from, allocations_summary: Object.values(byUser), }); } catch (err: any) { console.error("Revenue splits fetch error:", err); res.status(500).json({ error: err.message }); } }); // Get split rule history for a project (SPLITS-HISTORY) app.get("/api/revenue/splits/:projectId/history", requireAuth, async (req, res) => { try { const { projectId } = req.params; // Fetch all split versions for this project, ordered by version desc const { data: splitHistory, error: historyError } = await supabase .from("revenue_splits") .select("*") .eq("project_id", projectId) .order("split_version", { ascending: false }); if (historyError) throw historyError; if (!splitHistory || splitHistory.length === 0) { return res.json({ project_id: projectId, total_versions: 0, history: [], }); } // Enrich history with allocation counts per version const enriched = await Promise.all( splitHistory.map(async (split: any) => { const { count, error: countError } = await supabase .from("split_allocations") .select("id", { count: "exact" }) .eq("project_id", projectId) .eq("split_version", split.split_version); if (countError) console.error("Count error:", countError); return { split_version: split.split_version, rule: split.rule, active_from: split.active_from, active_until: split.active_until, is_active: !split.active_until, created_by: split.created_by, created_at: split.created_at, allocations_count: count || 0, }; }) ); res.json({ project_id: projectId, total_versions: enriched.length, history: enriched, }); } catch (err: any) { console.error("Split history fetch error:", err); res.status(500).json({ error: err.message }); } }); // ========== GOVERNANCE: SPLIT VOTING SYSTEM ========== // Import voting functions const { createSplitProposal, castVote, evaluateProposal, getProposalWithVotes } = await import( "./votes.js" ); // Create a proposal to change split rules (SPLITS-VOTING-1) app.post("/api/revenue/splits/:projectId/propose", requireAuth, async (req, res) => { try { const { projectId } = req.params; const { proposed_rule, voting_rule, description, expires_at } = req.body; const userId = req.session.userId; if (!proposed_rule || !voting_rule) { return res .status(400) .json({ error: "Missing proposed_rule or voting_rule" }); } if (voting_rule !== "unanimous" && voting_rule !== "majority") { return res .status(400) .json({ error: "voting_rule must be 'unanimous' or 'majority'" }); } const result = await createSplitProposal({ project_id: projectId, proposed_by: userId, proposed_rule, voting_rule, description, expires_at: expires_at ? new Date(expires_at) : undefined, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, proposal_id: result.proposal_id, message: "Proposal created successfully", }); } catch (err: any) { console.error("Create proposal error:", err); res.status(500).json({ error: err.message }); } }); // Cast a vote on a proposal (SPLITS-VOTING-2) app.post("/api/revenue/splits/proposals/:proposalId/vote", requireAuth, async (req, res) => { try { const { proposalId } = req.params; const { vote, reason } = req.body; const userId = req.session.userId; if (!vote || (vote !== "approve" && vote !== "reject")) { return res.status(400).json({ error: "vote must be 'approve' or 'reject'" }); } const result = await castVote({ proposal_id: proposalId, voter_id: userId, vote, reason, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, vote_id: result.vote_id, message: "Vote recorded successfully", }); } catch (err: any) { console.error("Cast vote error:", err); res.status(500).json({ error: err.message }); } }); // Get proposal details with vote counts (SPLITS-VOTING-3) app.get( "/api/revenue/splits/proposals/:proposalId", requireAuth, async (req, res) => { try { const { proposalId } = req.params; const result = await getProposalWithVotes(proposalId); if (!result.success) { return res.status(404).json({ error: result.error }); } res.json({ proposal: result.proposal, votes: result.votes, stats: result.stats, }); } catch (err: any) { console.error("Get proposal error:", err); res.status(500).json({ error: err.message }); } } ); // Evaluate proposal consensus and apply if approved (SPLITS-VOTING-4) app.post( "/api/revenue/splits/proposals/:proposalId/evaluate", requireAuth, async (req, res) => { try { const { proposalId } = req.params; const userId = req.session.userId; const result = await evaluateProposal(proposalId, userId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: result.success, approved: result.approved, stats: { approve_count: result.approve_count, reject_count: result.reject_count, total_votes: result.total_votes, }, message: result.message, }); } catch (err: any) { console.error("Evaluate proposal error:", err); res.status(500).json({ error: err.message }); } } ); // List all proposals for a project (SPLITS-VOTING-5) app.get( "/api/revenue/splits/:projectId/proposals", requireAuth, async (req, res) => { try { const { projectId } = req.params; const { data: proposals, error } = await supabase .from("split_proposals") .select("*") .eq("project_id", projectId) .order("created_at", { ascending: false }); if (error) throw error; res.json({ project_id: projectId, proposals: proposals || [], count: proposals?.length || 0, }); } catch (err: any) { console.error("List proposals error:", err); res.status(500).json({ error: err.message }); } } ); // ========== SETTLEMENT: ESCROW & PAYOUT SYSTEM ========== // Import settlement functions const { getEscrowBalance, depositToEscrow, createPayoutRequest, reviewPayoutRequest, registerPayoutMethod, processPayout, completePayout, failPayout, getPayoutHistory, } = await import("./settlement.js"); // Get escrow balance for user on a project (SETTLEMENT-1) app.get( "/api/settlement/escrow/:projectId", requireAuth, async (req, res) => { try { const { projectId } = req.params; const userId = req.session.userId; const result = await getEscrowBalance(userId, projectId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ user_id: userId, project_id: projectId, balance: result.balance, held_amount: result.held, released_amount: result.released, }); } catch (err: any) { console.error("Get escrow balance error:", err); res.status(500).json({ error: err.message }); } } ); // Create a payout request (SETTLEMENT-2) app.post("/api/settlement/payout-request", requireAuth, async (req, res) => { try { const { escrow_account_id, request_amount, reason } = req.body; const userId = req.session.userId; if (!escrow_account_id || !request_amount) { return res .status(400) .json({ error: "Missing escrow_account_id or request_amount" }); } const result = await createPayoutRequest({ user_id: userId, escrow_account_id, request_amount, reason, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, request_id: result.request_id, message: "Payout request created successfully", }); } catch (err: any) { console.error("Create payout request error:", err); res.status(500).json({ error: err.message }); } }); // Get user's payout requests (SETTLEMENT-3) app.get("/api/settlement/payout-requests", requireAuth, async (req, res) => { try { const userId = req.session.userId; const { data: requests, error } = await supabase .from("payout_requests") .select("*") .eq("user_id", userId) .order("requested_at", { ascending: false }); if (error) throw error; res.json({ user_id: userId, payout_requests: requests || [], count: requests?.length || 0, }); } catch (err: any) { console.error("Get payout requests error:", err); res.status(500).json({ error: err.message }); } }); // Register a payout method (SETTLEMENT-4) app.post("/api/settlement/payout-methods", requireAuth, async (req, res) => { try { const { method_type, metadata, is_primary } = req.body; const userId = req.session.userId; if (!method_type || !metadata) { return res .status(400) .json({ error: "Missing method_type or metadata" }); } const result = await registerPayoutMethod({ user_id: userId, method_type, metadata, is_primary, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, method_id: result.method_id, message: "Payout method registered successfully", }); } catch (err: any) { console.error("Register payout method error:", err); res.status(500).json({ error: err.message }); } }); // Get user's payout methods (SETTLEMENT-5) app.get( "/api/settlement/payout-methods", requireAuth, async (req, res) => { try { const userId = req.session.userId; const { data: methods, error } = await supabase .from("payout_methods") .select("*") .eq("user_id", userId) .order("created_at", { ascending: false }); if (error) throw error; res.json({ user_id: userId, payout_methods: methods || [], count: methods?.length || 0, }); } catch (err: any) { console.error("Get payout methods error:", err); res.status(500).json({ error: err.message }); } } ); // Process a payout (admin/system) (SETTLEMENT-6) app.post( "/api/settlement/payouts/process", requireAuth, async (req, res) => { try { const { payout_request_id, escrow_account_id, payout_method_id, amount, } = req.body; const userId = req.session.userId; if ( !escrow_account_id || !payout_method_id || !amount ) { return res.status(400).json({ error: "Missing escrow_account_id, payout_method_id, or amount", }); } const result = await processPayout({ payout_request_id, user_id: userId, escrow_account_id, payout_method_id, amount, }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, payout_id: result.payout_id, message: "Payout processing started", }); } catch (err: any) { console.error("Process payout error:", err); res.status(500).json({ error: err.message }); } } ); // Complete a payout (SETTLEMENT-7) app.post( "/api/settlement/payouts/:payoutId/complete", requireAuth, async (req, res) => { try { const { payoutId } = req.params; const { external_transaction_id } = req.body; const result = await completePayout(payoutId, external_transaction_id); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: "Payout completed successfully", }); } catch (err: any) { console.error("Complete payout error:", err); res.status(500).json({ error: err.message }); } } ); // Fail a payout (SETTLEMENT-8) app.post( "/api/settlement/payouts/:payoutId/fail", requireAuth, async (req, res) => { try { const { payoutId } = req.params; const { failure_reason } = req.body; const result = await failPayout(payoutId, failure_reason); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: "Payout marked as failed, funds restored to escrow", }); } catch (err: any) { console.error("Fail payout error:", err); res.status(500).json({ error: err.message }); } } ); // Get user's payout history (SETTLEMENT-9) app.get("/api/settlement/payouts", requireAuth, async (req, res) => { try { const userId = req.session.userId; const limit = parseInt(req.query.limit as string) || 50; const result = await getPayoutHistory(userId, limit); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ user_id: userId, payouts: result.payouts, count: result.count, }); } catch (err: any) { console.error("Get payout history error:", err); res.status(500).json({ error: err.message }); } }); // ========== CONTRIBUTOR DASHBOARD: EARNINGS VIEW ========== // Import dashboard functions const { getUserEarnings, getProjectEarnings, getEarningsSummary, getProjectLeaderboard, } = await import("./dashboard.js"); // Get all earnings for authenticated user (DASHBOARD-1) app.get("/api/dashboard/earnings", requireAuth, async (req, res) => { try { const userId = req.session.userId; const result = await getUserEarnings(userId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json(result.data); } catch (err: any) { console.error("Get user earnings error:", err); res.status(500).json({ error: err.message }); } }); // Get earnings for a specific project (DASHBOARD-2) app.get("/api/dashboard/earnings/:projectId", requireAuth, async (req, res) => { try { const { projectId } = req.params; const userId = req.session.userId; const result = await getProjectEarnings(userId, projectId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json(result.data); } catch (err: any) { console.error("Get project earnings error:", err); res.status(500).json({ error: err.message }); } }); // Get earnings summary for user (DASHBOARD-3) app.get("/api/dashboard/summary", requireAuth, async (req, res) => { try { const userId = req.session.userId; const result = await getEarningsSummary(userId); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json(result.data); } catch (err: any) { console.error("Get earnings summary error:", err); res.status(500).json({ error: err.message }); } }); // Get leaderboard for a project (DASHBOARD-4) app.get( "/api/dashboard/leaderboard/:projectId", async (req, res) => { try { const { projectId } = req.params; const limit = parseInt(req.query.limit as string) || 20; const result = await getProjectLeaderboard(projectId, limit); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json(result.data); } catch (err: any) { console.error("Get leaderboard error:", err); res.status(500).json({ error: err.message }); } } ); // ========== API-TRIGGERED REVENUE ========== // Record custom revenue event (API trigger) (API-REVENUE-1) app.post("/api/revenue/trigger", requireAuth, async (req, res) => { try { const { source_type, project_id, gross_amount, platform_fee, metadata, } = req.body; const userId = req.session.userId; if (!source_type || !project_id || !gross_amount) { return res.status(400).json({ error: "Missing source_type, project_id, or gross_amount", }); } if (!["api", "subscription", "donation"].includes(source_type)) { return res.status(400).json({ error: "source_type must be 'api', 'subscription', or 'donation'", }); } // Record revenue event const eventResult = await recordRevenueEvent({ source_type, source_id: `api-${Date.now()}-${Math.random().toString(36).substring(7)}`, gross_amount: parseFloat(gross_amount), platform_fee: platform_fee ? parseFloat(platform_fee) : 0, currency: "USD", project_id, org_id: null, metadata, requester_org_id: userId, }); if (!eventResult.success) { return res.status(400).json({ error: eventResult.error }); } // Compute and record splits const splitsResult = await computeRevenueSplits( project_id, (parseFloat(gross_amount) - (platform_fee ? parseFloat(platform_fee) : 0)).toFixed(2), new Date() ); if (!splitsResult.success) { return res.status(400).json({ error: `Failed to compute splits: ${splitsResult.error}`, }); } // Record allocations const allocResult = await recordSplitAllocations( eventResult.id, project_id, splitsResult.allocations, splitsResult.split_version ); if (!allocResult.success) { return res.status(400).json({ error: `Failed to record allocations: ${allocResult.error}`, }); } // Deposit to escrow for each contributor for (const [userId, allocation] of Object.entries( splitsResult.allocations || {} )) { const allocationData = allocation as any; await depositToEscrow( userId, project_id, allocationData.allocated_amount ); } res.status(201).json({ success: true, revenue_event_id: eventResult.id, allocations: splitsResult.allocations, message: "Revenue recorded and splits computed", }); } catch (err: any) { console.error("API revenue trigger error:", err); res.status(500).json({ error: err.message }); } }); // Get API revenue events for a project (API-REVENUE-2) app.get( "/api/revenue/api-events/:projectId", requireAuth, async (req, res) => { try { const { projectId } = req.params; const { data: events, error } = await supabase .from("revenue_events") .select("*") .eq("project_id", projectId) .eq("source_type", "api") .order("created_at", { ascending: false }); if (error) throw error; res.json({ project_id: projectId, api_events: events || [], count: events?.length || 0, }); } catch (err: any) { console.error("Get API revenue events error:", err); res.status(500).json({ error: err.message }); } } ); // ========== OS KERNEL ROUTES ========== // Identity Linking app.post("/api/os/link/start", async (req, res) => { try { const { provider } = req.body; const userId = (req.headers["x-user-id"] as string) || req.session.userId; if (!provider || !userId) { return res.status(400).json({ error: "Missing provider or user" }); } const linkingSession = { id: `link_${Date.now()}`, state: Math.random().toString(36).substring(7), expires_at: new Date(Date.now() + 10 * 60 * 1000), }; res.json({ linking_session_id: linkingSession.id, state: linkingSession.state, redirect_url: `/os/link/redirect?provider=${provider}&state=${linkingSession.state}`, }); } catch (error) { console.error("Link start error:", error); res.status(500).json({ error: "Failed to start linking" }); } }); app.post("/api/os/link/complete", async (req, res) => { try { const { provider, external_id, external_username } = req.body; const userId = (req.headers["x-user-id"] as string) || req.session.userId; if (!provider || !external_id || !userId) { return res.status(400).json({ error: "Missing required fields" }); } const { data, error } = await supabase .from("aethex_subject_identities") .upsert( { provider, external_id, external_username, verified_at: new Date().toISOString(), }, { onConflict: "provider,external_id", } ) .select(); if (error) throw error; await supabase.from("aethex_audit_log").insert({ action: "link_identity", actor_id: userId, actor_type: "user", resource_type: "subject_identity", resource_id: data?.[0]?.id || "unknown", changes: { provider, external_id }, status: "success", }); res.json({ success: true, identity: { provider, external_id, verified_at: new Date().toISOString(), }, }); } catch (error) { console.error("Link complete error:", error); res.status(500).json({ error: "Failed to complete linking" }); } }); app.post("/api/os/link/unlink", async (req, res) => { try { const { provider, external_id } = req.body; const userId = (req.headers["x-user-id"] as string) || req.session.userId; if (!provider || !external_id) { return res.status(400).json({ error: "Missing provider or external_id" }); } const { data, error } = await supabase .from("aethex_subject_identities") .update({ revoked_at: new Date().toISOString() }) .match({ provider, external_id }) .select(); if (error) throw error; await supabase.from("aethex_audit_log").insert({ action: "unlink_identity", actor_id: userId, actor_type: "user", resource_type: "subject_identity", resource_id: data?.[0]?.id || "unknown", changes: { revoked: true }, status: "success", }); res.json({ success: true, message: "Identity unlinked" }); } catch (error) { console.error("Unlink error:", error); res.status(500).json({ error: "Failed to unlink identity" }); } }); // Entitlements app.post("/api/os/entitlements/issue", async (req, res) => { try { const issuerId = req.headers["x-issuer-id"] as string; const { subject_id, external_subject_ref, entitlement_type, scope, data, expires_at, } = req.body; if (!issuerId || (!subject_id && !external_subject_ref)) { return res .status(400) .json({ error: "Missing issuer_id or subject reference" }); } const { data: entitlement, error } = await supabase .from("aethex_entitlements") .insert({ issuer_id: issuerId, subject_id: subject_id || null, external_subject_ref: external_subject_ref || null, entitlement_type, scope, data: data || {}, status: "active", expires_at: expires_at || null, }) .select() .single(); if (error) throw error; await supabase.from("aethex_audit_log").insert({ action: "issue_entitlement", actor_id: issuerId, actor_type: "issuer", resource_type: "entitlement", resource_id: entitlement?.id || "unknown", changes: { entitlement_type, scope }, status: "success", }); res.json({ success: true, entitlement: { id: entitlement?.id, type: entitlement_type, scope, created_at: entitlement?.created_at, }, }); } catch (error) { console.error("Issue error:", error); res.status(500).json({ error: "Failed to issue entitlement" }); } }); app.post("/api/os/entitlements/verify", async (req, res) => { try { const { entitlement_id } = req.body; if (!entitlement_id) { return res.status(400).json({ error: "Missing entitlement_id" }); } const { data: entitlement, error } = await supabase .from("aethex_entitlements") .select("*, issuer:aethex_issuers(*)") .eq("id", entitlement_id) .single(); if (error || !entitlement) { return res .status(404) .json({ valid: false, reason: "Entitlement not found" }); } if (entitlement.status === "revoked") { return res.json({ valid: false, reason: "revoked", revoked_at: entitlement.revoked_at, revocation_reason: entitlement.revocation_reason, }); } if ( entitlement.status === "expired" || (entitlement.expires_at && new Date() > new Date(entitlement.expires_at)) ) { return res.json({ valid: false, reason: "expired", expires_at: entitlement.expires_at, }); } await supabase.from("aethex_entitlement_events").insert({ entitlement_id, event_type: "verified", actor_type: "system", reason: "API verification", }); res.json({ valid: true, entitlement: { id: entitlement.id, type: entitlement.entitlement_type, scope: entitlement.scope, data: entitlement.data, issuer: { id: entitlement.issuer?.id, name: entitlement.issuer?.name, class: entitlement.issuer?.issuer_class, }, issued_at: entitlement.created_at, expires_at: entitlement.expires_at, }, }); } catch (error) { console.error("Verify error:", error); res.status(500).json({ error: "Failed to verify entitlement" }); } }); app.get("/api/os/entitlements/resolve", async (req, res) => { try { const { platform, id, subject_id } = req.query; let entitlements: any[] = []; if (subject_id) { const { data, error } = await supabase .from("aethex_entitlements") .select("*, issuer:aethex_issuers(*)") .eq("subject_id", subject_id as string) .eq("status", "active"); if (error) throw error; entitlements = data || []; } else if (platform && id) { const externalRef = `${platform}:${id}`; const { data, error } = await supabase .from("aethex_entitlements") .select("*, issuer:aethex_issuers(*)") .eq("external_subject_ref", externalRef) .eq("status", "active"); if (error) throw error; entitlements = data || []; } else { return res.status(400).json({ error: "Missing platform/id or subject_id" }); } res.json({ entitlements: entitlements.map((e) => ({ id: e.id, type: e.entitlement_type, scope: e.scope, data: e.data, issuer: { name: e.issuer?.name, class: e.issuer?.issuer_class, }, issued_at: e.created_at, expires_at: e.expires_at, })), }); } catch (error) { console.error("Resolve error:", error); res.status(500).json({ error: "Failed to resolve entitlements" }); } }); app.post("/api/os/entitlements/revoke", async (req, res) => { try { const issuerId = req.headers["x-issuer-id"] as string; const { entitlement_id, reason } = req.body; if (!entitlement_id || !reason) { return res .status(400) .json({ error: "Missing entitlement_id or reason" }); } const { data, error } = await supabase .from("aethex_entitlements") .update({ status: "revoked", revoked_at: new Date().toISOString(), revocation_reason: reason, }) .eq("id", entitlement_id) .select(); if (error) throw error; await supabase.from("aethex_entitlement_events").insert({ entitlement_id, event_type: "revoked", actor_id: issuerId, actor_type: "issuer", reason, }); await supabase.from("aethex_audit_log").insert({ action: "revoke_entitlement", actor_id: issuerId, actor_type: "issuer", resource_type: "entitlement", resource_id: entitlement_id, changes: { status: "revoked", reason }, status: "success", }); res.json({ success: true, message: "Entitlement revoked" }); } catch (error) { console.error("Revoke error:", error); res.status(500).json({ error: "Failed to revoke entitlement" }); } }); // Simple in-memory file storage (per-user, per-org, session-based) const fileStore = new Map(); app.get("/api/files", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const userId = req.session.userId; const orgId = getOrgIdOrThrow(req); if (!userId) return res.status(401).json({ error: "Unauthorized" }); const key = `${userId}:${orgId}`; const files = fileStore.get(key) || []; const { path } = req.query; // Filter by path const filtered = path ? files.filter(f => f.path.startsWith(`${path}/`) || f.path === path) : files.filter(f => f.path === '/'); res.json({ files: filtered }); } catch (error) { console.error("File list error:", error); res.status(500).json({ error: "Failed to fetch files" }); } }); app.post("/api/files", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const userId = req.session.userId; const orgId = getOrgIdOrThrow(req); if (!userId) return res.status(401).json({ error: "Unauthorized" }); const { name, type, path, content, language, project_id } = req.body; if (!name || !type || !path) { return res.status(400).json({ error: "Missing required fields" }); } const fileId = randomUUID(); const newFile = { id: fileId, user_id: userId, organization_id: orgId, project_id: project_id || null, name, type, path, content: content || '', language: language || null, size: content?.length || 0, mime_type: null, parent_id: null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }; const key = `${userId}:${orgId}`; const files = fileStore.get(key) || []; files.push(newFile); fileStore.set(key, files); res.json(newFile); } catch (error) { console.error("File creation error:", error); res.status(500).json({ error: "Failed to create file" }); } }); app.patch("/api/files/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const userId = req.session.userId; const orgId = getOrgIdOrThrow(req); if (!userId) return res.status(401).json({ error: "Unauthorized" }); const { id } = req.params; const { name, content } = req.body; const key = `${userId}:${orgId}`; const files = fileStore.get(key) || []; const file = files.find(f => f.id === id); if (!file) { return res.status(404).json({ error: "File not found" }); } if (name) file.name = name; if (content !== undefined) file.content = content; file.updated_at = new Date().toISOString(); res.json(file); } catch (error) { console.error("File update error:", error); res.status(500).json({ error: "Failed to update file" }); } }); app.delete("/api/files/:id", requireAuth, attachOrgContext, requireOrgMember, async (req, res) => { try { const userId = req.session.userId; const orgId = getOrgIdOrThrow(req); if (!userId) return res.status(401).json({ error: "Unauthorized" }); const { id } = req.params; const key = `${userId}:${orgId}`; let files = fileStore.get(key) || []; const fileToDelete = files.find(f => f.id === id); if (!fileToDelete) { return res.status(404).json({ error: "File not found" }); } // If folder, delete all files inside if (fileToDelete.type === 'folder') { files = files.filter(f => !f.path.startsWith(fileToDelete.path + '/') && f.id !== id); } else { files = files.filter(f => f.id !== id); } fileStore.set(key, files); res.json({ id, deleted: true }); } catch (error) { console.error("File delete error:", error); res.status(500).json({ error: "Failed to delete file" }); } }); app.get("/api/os/issuers/:id", async (req, res) => { try { const { id } = req.params; const { data: issuer, error } = await supabase .from("aethex_issuers") .select("*") .eq("id", id) .single(); if (error || !issuer) { return res.status(404).json({ error: "Issuer not found" }); } res.json({ id: issuer.id, name: issuer.name, class: issuer.issuer_class, scopes: issuer.scopes, public_key: issuer.public_key, is_active: issuer.is_active, metadata: issuer.metadata, }); } catch (error) { console.error("Issuer fetch error:", error); res.status(500).json({ error: "Failed to fetch issuer" }); } }); return httpServer; }