diff --git a/api/discord/link.ts b/api/discord/link.ts
index c98b0844..7349388d 100644
--- a/api/discord/link.ts
+++ b/api/discord/link.ts
@@ -64,18 +64,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
if (existingLink) {
return res.status(409).json({
- error: "This Discord account is already linked to another AeThex account",
+ error:
+ "This Discord account is already linked to another AeThex account",
});
}
// Create the link
- const { error: linkError } = await supabase
- .from("discord_links")
- .insert({
- discord_id: verification.discord_id,
- user_id,
- primary_arm: "labs", // Default to labs
- });
+ const { error: linkError } = await supabase.from("discord_links").insert({
+ discord_id: verification.discord_id,
+ user_id,
+ primary_arm: "labs", // Default to labs
+ });
if (linkError) {
console.error("Failed to create discord link:", linkError);
diff --git a/api/discord/sync-roles.ts b/api/discord/sync-roles.ts
index e88697d0..6a51dd69 100644
--- a/api/discord/sync-roles.ts
+++ b/api/discord/sync-roles.ts
@@ -24,7 +24,10 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Verify request is from Discord bot (simple verification)
const authorization = req.headers.authorization;
- if (!authorization || authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}`) {
+ if (
+ !authorization ||
+ authorization !== `Bearer ${process.env.DISCORD_BOT_TOKEN}`
+ ) {
return res.status(401).json({ error: "Unauthorized" });
}
diff --git a/api/games/roblox-auth.ts b/api/games/roblox-auth.ts
index 16f45130..6eec16a7 100644
--- a/api/games/roblox-auth.ts
+++ b/api/games/roblox-auth.ts
@@ -31,7 +31,11 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const secret = process.env.ROBLOX_SHARED_SECRET || "";
// Verify signature for security
- if (signature && secret && !verifyRobloxSignature(payload, signature, secret)) {
+ if (
+ signature &&
+ secret &&
+ !verifyRobloxSignature(payload, signature, secret)
+ ) {
console.warn("Invalid Roblox signature");
return res.status(401).json({ error: "Invalid signature" });
}
diff --git a/api/games/verify-token.ts b/api/games/verify-token.ts
index 470c23eb..97ded114 100644
--- a/api/games/verify-token.ts
+++ b/api/games/verify-token.ts
@@ -13,7 +13,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
}
try {
- const { session_token, game } = req.method === "POST" ? req.body : req.query;
+ const { session_token, game } =
+ req.method === "POST" ? req.body : req.query;
if (!session_token) {
return res.status(400).json({ error: "session_token is required" });
@@ -22,7 +23,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Find the session
const { data: sessionData, error: sessionError } = await supabase
.from("game_sessions")
- .select("*, user_profiles!inner(id, username, email, full_name, metadata)")
+ .select(
+ "*, user_profiles!inner(id, username, email, full_name, metadata)",
+ )
.eq("session_token", String(session_token))
.single();
@@ -38,7 +41,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Optional: Verify game matches if provided
if (game && sessionData.game !== String(game).toLowerCase()) {
- return res.status(403).json({ error: "Token is not valid for this game" });
+ return res
+ .status(403)
+ .json({ error: "Token is not valid for this game" });
}
// Update last activity
diff --git a/api/roblox/oauth-callback.ts b/api/roblox/oauth-callback.ts
index 37003438..f2221a20 100644
--- a/api/roblox/oauth-callback.ts
+++ b/api/roblox/oauth-callback.ts
@@ -31,7 +31,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID;
const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET;
const redirectUri =
- process.env.ROBLOX_OAUTH_REDIRECT_URI || "https://aethex.dev/roblox-callback";
+ process.env.ROBLOX_OAUTH_REDIRECT_URI ||
+ "https://aethex.dev/roblox-callback";
if (!clientId || !clientSecret) {
return res.status(500).json({ error: "Roblox OAuth not configured" });
@@ -59,9 +60,12 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const tokenData: RobloxTokenResponse = await tokenResponse.json();
// Get user info with access token
- const userResponse = await fetch("https://apis.roblox.com/oauth/v1/userinfo", {
- headers: { Authorization: `Bearer ${tokenData.access_token}` },
- });
+ const userResponse = await fetch(
+ "https://apis.roblox.com/oauth/v1/userinfo",
+ {
+ headers: { Authorization: `Bearer ${tokenData.access_token}` },
+ },
+ );
if (!userResponse.ok) {
console.error("Failed to fetch Roblox user info");
diff --git a/api/user/link-roblox.ts b/api/user/link-roblox.ts
index d0a768d7..ae16868f 100644
--- a/api/user/link-roblox.ts
+++ b/api/user/link-roblox.ts
@@ -21,7 +21,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7);
// Verify token with Supabase
- const { data: userData, error: authError } = await supabase.auth.getUser(token);
+ const { data: userData, error: authError } =
+ await supabase.auth.getUser(token);
if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" });
}
diff --git a/api/user/link-web3.ts b/api/user/link-web3.ts
index 5db2f2f3..fd6bba1d 100644
--- a/api/user/link-web3.ts
+++ b/api/user/link-web3.ts
@@ -22,7 +22,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7);
// Verify token with Supabase
- const { data: userData, error: authError } = await supabase.auth.getUser(token);
+ const { data: userData, error: authError } =
+ await supabase.auth.getUser(token);
if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" });
}
@@ -66,15 +67,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
.eq("nonce", nonce);
// Link wallet to existing user
- const { error: linkError } = await supabase
- .from("web3_wallets")
- .insert({
- user_id: userData.user.id,
- wallet_address: normalizedAddress,
- chain_id: 1, // Ethereum mainnet
- });
+ const { error: linkError } = await supabase.from("web3_wallets").insert({
+ user_id: userData.user.id,
+ wallet_address: normalizedAddress,
+ chain_id: 1, // Ethereum mainnet
+ });
- if (linkError && !linkError.message.includes("violates unique constraint")) {
+ if (
+ linkError &&
+ !linkError.message.includes("violates unique constraint")
+ ) {
console.error("Failed to link wallet:", linkError);
return res.status(500).json({ error: "Failed to link wallet" });
}
diff --git a/api/web3/verify.ts b/api/web3/verify.ts
index ae4f17a4..8c2c3424 100644
--- a/api/web3/verify.ts
+++ b/api/web3/verify.ts
@@ -80,15 +80,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const username = normalizedAddress.substring(2, 10);
// Create Supabase auth user
- const { data: authData, error: authError } = await supabase.auth.admin.createUser({
- email,
- password: require("crypto").randomBytes(32).toString("hex"),
- email_confirm: true,
- user_metadata: {
- wallet_address: normalizedAddress,
- auth_method: "web3",
- },
- });
+ const { data: authData, error: authError } =
+ await supabase.auth.admin.createUser({
+ email,
+ password: require("crypto").randomBytes(32).toString("hex"),
+ email_confirm: true,
+ user_metadata: {
+ wallet_address: normalizedAddress,
+ auth_method: "web3",
+ },
+ });
if (authError || !authData.user) {
console.error("Failed to create auth user:", authError);
diff --git a/client/App.tsx b/client/App.tsx
index 7f76f5d5..4d7272b4 100644
--- a/client/App.tsx
+++ b/client/App.tsx
@@ -119,232 +119,247 @@ const App = () => (
-
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- }
- />
- } />
- } />
- }
- />
-
- } />
- }
- />
- }
- />
- }
- />
- }
- />
- }
- />
-
- }
- />
- } />
- }
- />
- } />
- } />
- } />
- } />
- } />
- } />
-
- {/* Creator Network routes */}
- } />
- }
- />
- } />
- }
- />
-
- {/* Service routes */}
- } />
- } />
- } />
- } />
- }
- />
- } />
-
- {/* New Arm Landing Pages */}
- } />
- }
- />
- } />
- }
- />
-
- } />
- }
- />
- }
- />
- }
- />
-
- } />
- }
- />
- }
- />
- } />
-
- } />
- }
- />
- }
- />
- }
- />
-
- {/* Dev-Link routes */}
- } />
- }
- />
-
- {/* Nexus routes */}
- } />
-
- {/* Resource routes */}
- }>
- } />
- } />
- } />
+
+
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
}
+ path="/projects/:projectId/board"
+ element={}
+ />
+ } />
+ } />
+ }
/>
- } />
- } />
- } />
- } />
- } />
-
- } />
- } />
- } />
- } />
- } />
- } />
- }
- />
- }
- />
- }
- />
- } />
- } />
- } />
- } />
- } />
- {/* Informational routes */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
- {/* Legal routes */}
- } />
- } />
+ }
+ />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
- {/* Discord routes */}
- } />
- }
- />
+ {/* Creator Network routes */}
+ } />
+ }
+ />
+ } />
+ }
+ />
- {/* Explicit 404 route for static hosting fallbacks */}
- } />
- {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
- } />
-
-
-
-
+ {/* Service routes */}
+ }
+ />
+ }
+ />
+ } />
+ } />
+ }
+ />
+ } />
+
+ {/* New Arm Landing Pages */}
+ } />
+ }
+ />
+ } />
+ }
+ />
+
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ } />
+ }
+ />
+ }
+ />
+ } />
+
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+
+ {/* Dev-Link routes */}
+ } />
+ }
+ />
+
+ {/* Nexus routes */}
+ } />
+
+ {/* Resource routes */}
+ }>
+ } />
+ } />
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Informational routes */}
+ } />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Legal routes */}
+ } />
+ } />
+
+ {/* Discord routes */}
+ } />
+ }
+ />
+
+ {/* Explicit 404 route for static hosting fallbacks */}
+ } />
+ {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
+ } />
+
+
+
+
diff --git a/client/components/onboarding/RealmSelection.tsx b/client/components/onboarding/RealmSelection.tsx
index 9593089f..2ba8bcb8 100644
--- a/client/components/onboarding/RealmSelection.tsx
+++ b/client/components/onboarding/RealmSelection.tsx
@@ -1,4 +1,10 @@
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { CheckCircle2 } from "lucide-react";
@@ -12,7 +18,8 @@ const REALMS = [
{
id: "labs",
title: "๐งช Labs",
- description: "Research & Development - Cutting-edge innovation and experimentation",
+ description:
+ "Research & Development - Cutting-edge innovation and experimentation",
color: "from-yellow-500/20 to-yellow-600/20",
borderColor: "border-yellow-400",
},
@@ -46,18 +53,25 @@ const REALMS = [
},
];
-export default function RealmSelection({ selectedRealm, onSelect, onNext }: RealmSelectionProps) {
+export default function RealmSelection({
+ selectedRealm,
+ onSelect,
+ onNext,
+}: RealmSelectionProps) {
return (
-
Choose Your Primary Realm
+
+ Choose Your Primary Realm
+
- Select the AeThex realm that best matches your primary focus. You can always change this later.
+ Select the AeThex realm that best matches your primary focus. You can
+ always change this later.
- {REALMS.map(realm => (
+ {REALMS.map((realm) => (
onSelect(realm.id)}
@@ -67,7 +81,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
>
@@ -81,7 +97,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
- {realm.description}
+
+ {realm.description}
+
diff --git a/client/contexts/AuthContext.tsx b/client/contexts/AuthContext.tsx
index 9d765079..5d3d88f0 100644
--- a/client/contexts/AuthContext.tsx
+++ b/client/contexts/AuthContext.tsx
@@ -836,8 +836,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
window.localStorage.removeItem("onboarding_complete");
window.localStorage.removeItem("aethex_onboarding_progress_v1");
Object.keys(window.localStorage)
- .filter(key => key.startsWith("sb-") || key.includes("supabase") || key.startsWith("mock_") || key.startsWith("demo_"))
- .forEach(key => window.localStorage.removeItem(key));
+ .filter(
+ (key) =>
+ key.startsWith("sb-") ||
+ key.includes("supabase") ||
+ key.startsWith("mock_") ||
+ key.startsWith("demo_"),
+ )
+ .forEach((key) => window.localStorage.removeItem(key));
console.log("localStorage cleared");
} catch (e) {
console.warn("localStorage clear failed:", e);
diff --git a/client/contexts/Web3Context.tsx b/client/contexts/Web3Context.tsx
index 234de556..9c509414 100644
--- a/client/contexts/Web3Context.tsx
+++ b/client/contexts/Web3Context.tsx
@@ -1,4 +1,10 @@
-import React, { createContext, useContext, useState, useCallback, useEffect } from "react";
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useCallback,
+ useEffect,
+} from "react";
import { aethexToast } from "@/lib/aethex-toast";
export interface Web3ContextType {
@@ -13,7 +19,9 @@ export interface Web3ContextType {
const Web3Context = createContext
(undefined);
-export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
const [account, setAccount] = useState(null);
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
@@ -100,7 +108,11 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
const signMessage = useCallback(
async (message: string): Promise => {
- if (!account || typeof window === "undefined" || !(window as any).ethereum) {
+ if (
+ !account ||
+ typeof window === "undefined" ||
+ !(window as any).ethereum
+ ) {
throw new Error("Wallet not connected");
}
@@ -144,8 +156,14 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
(window as any).ethereum.on("chainChanged", handleChainChanged);
return () => {
- (window as any).ethereum?.removeListener("accountsChanged", handleAccountsChanged);
- (window as any).ethereum?.removeListener("chainChanged", handleChainChanged);
+ (window as any).ethereum?.removeListener(
+ "accountsChanged",
+ handleAccountsChanged,
+ );
+ (window as any).ethereum?.removeListener(
+ "chainChanged",
+ handleChainChanged,
+ );
};
}, []);
diff --git a/client/pages/Admin.tsx b/client/pages/Admin.tsx
index db448299..f86cb2c9 100644
--- a/client/pages/Admin.tsx
+++ b/client/pages/Admin.tsx
@@ -1513,27 +1513,59 @@ export default function Admin() {
-
Realm to Discord Role Mappings
+
+ Realm to Discord Role Mappings
+
- Configure which Discord roles are assigned for each AeThex realm and user type.
+ Configure which Discord roles are assigned for each
+ AeThex realm and user type.
{[
- { realm: "Labs", role: "Labs Creator", members: 234 },
- { realm: "GameForge", role: "GameForge Creator", members: 456 },
- { realm: "Corp", role: "Corp Member", members: 89 },
- { realm: "Foundation", role: "Foundation Member", members: 145 },
- { realm: "Dev-Link", role: "Dev-Link Member", members: 78 },
+ {
+ realm: "Labs",
+ role: "Labs Creator",
+ members: 234,
+ },
+ {
+ realm: "GameForge",
+ role: "GameForge Creator",
+ members: 456,
+ },
+ {
+ realm: "Corp",
+ role: "Corp Member",
+ members: 89,
+ },
+ {
+ realm: "Foundation",
+ role: "Foundation Member",
+ members: 145,
+ },
+ {
+ realm: "Dev-Link",
+ role: "Dev-Link Member",
+ members: 78,
+ },
].map((mapping, idx) => (
-
+
{mapping.realm}
-
{mapping.role}
+
+ {mapping.role}
+
-
{mapping.members}
-
assigned members
+
+ {mapping.members}
+
+
+ assigned members
+
))}
@@ -1542,19 +1574,31 @@ export default function Admin() {
-
Bot Configuration
+
+ Bot Configuration
+
Bot Status
- Online
+
+ Online
+
- Servers Connected
- 12 servers
+
+ Servers Connected
+
+
+ 12 servers
+
- Linked Accounts
- 1,234 users
+
+ Linked Accounts
+
+
+ 1,234 users
+
diff --git a/client/pages/DiscordVerify.tsx b/client/pages/DiscordVerify.tsx
index 234c1d62..4e762bda 100644
--- a/client/pages/DiscordVerify.tsx
+++ b/client/pages/DiscordVerify.tsx
@@ -55,7 +55,8 @@ export default function DiscordVerify() {
toastSuccess({
title: "Discord Linked!",
- description: "Your Discord account has been successfully linked to AeThex",
+ description:
+ "Your Discord account has been successfully linked to AeThex",
});
// Redirect to profile settings
diff --git a/client/pages/Onboarding.tsx b/client/pages/Onboarding.tsx
index 6f43b049..c6c16edb 100644
--- a/client/pages/Onboarding.tsx
+++ b/client/pages/Onboarding.tsx
@@ -514,7 +514,10 @@ export default function Onboarding() {
selectedRealm={data.creatorProfile.primaryArm || ""}
onSelect={(realm) =>
updateData({
- creatorProfile: { ...data.creatorProfile, primaryArm: realm },
+ creatorProfile: {
+ ...data.creatorProfile,
+ primaryArm: realm,
+ },
})
}
onNext={nextStep}
diff --git a/client/pages/RobloxCallback.tsx b/client/pages/RobloxCallback.tsx
index aa740412..6ab235ef 100644
--- a/client/pages/RobloxCallback.tsx
+++ b/client/pages/RobloxCallback.tsx
@@ -47,7 +47,8 @@ export default function RobloxCallback() {
const errorData = await response.json();
toastError({
title: "Authentication failed",
- description: errorData.error || "Could not authenticate with Roblox",
+ description:
+ errorData.error || "Could not authenticate with Roblox",
});
navigate("/login");
return;
diff --git a/client/pages/Web3Callback.tsx b/client/pages/Web3Callback.tsx
index af848272..82db41af 100644
--- a/client/pages/Web3Callback.tsx
+++ b/client/pages/Web3Callback.tsx
@@ -60,7 +60,8 @@ export default function Web3Callback() {
if (user && data.success) {
toastSuccess({
title: "Wallet linked",
- description: "Your Ethereum wallet is now connected to your account",
+ description:
+ "Your Ethereum wallet is now connected to your account",
});
navigate("/dashboard?tab=connections");
return;
@@ -93,11 +94,21 @@ export default function Web3Callback() {
if (account && !authLoading) {
handleWeb3Auth();
}
- }, [account, authLoading, signMessage, user, navigate, toastError, toastSuccess]);
+ }, [
+ account,
+ authLoading,
+ signMessage,
+ user,
+ navigate,
+ toastError,
+ toastSuccess,
+ ]);
return (
diff --git a/discord-bot/DEPLOYMENT_GUIDE.md b/discord-bot/DEPLOYMENT_GUIDE.md
index 2e216d3f..9b1c532e 100644
--- a/discord-bot/DEPLOYMENT_GUIDE.md
+++ b/discord-bot/DEPLOYMENT_GUIDE.md
@@ -12,6 +12,7 @@
### Step 1: Prepare the Bot Directory
Ensure all bot files are committed:
+
```
code/discord-bot/
โโโ bot.js
@@ -53,16 +54,19 @@ NODE_ENV=production
In Spaceship Application Settings:
**Build Command:**
+
```bash
npm install
```
**Start Command:**
+
```bash
npm start
```
**Root Directory:**
+
```
code/discord-bot
```
@@ -80,6 +84,7 @@ code/discord-bot
### Step 6: Verify Bot is Online
Once deployed:
+
1. Go to your Discord server
2. Type `/verify` - the command autocomplete should appear
3. Bot should be online with status "Listening to /verify to link your AeThex account"
@@ -87,6 +92,7 @@ Once deployed:
## ๐ก Discord Bot Endpoints
The bot will be accessible at:
+
```
https://
/
```
@@ -96,11 +102,13 @@ The bot uses Discord's WebSocket connection (not HTTP), so it doesn't need to ex
## ๐ API Integration
Frontend calls to link Discord accounts:
+
- **Endpoint:** `POST /api/discord/link`
- **Body:** `{ verification_code, user_id }`
- **Response:** `{ success: true, message: "..." }`
Discord Verify page (`/discord-verify?code=XXX`) will automatically:
+
1. Call `/api/discord/link` with the verification code
2. Link the Discord ID to the AeThex user account
3. Redirect to dashboard on success
@@ -108,22 +116,26 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## ๐ ๏ธ Debugging
### Check bot logs on Spaceship:
+
- Application โ Logs
- Filter for "bot.js" or "error"
### Common issues:
**"Discord bot not responding to commands"**
+
- Check: `DISCORD_BOT_TOKEN` is correct
- Check: Bot is added to the Discord server with "applications.commands" scope
- Check: Spaceship logs show "โ
Logged in"
**"Supabase verification fails"**
+
- Check: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` are correct
- Check: `discord_links` and `discord_verifications` tables exist
- Run migration: `code/supabase/migrations/20250107_add_discord_integration.sql`
**"Slash commands not appearing in Discord"**
+
- Check: Logs show "โ
Successfully registered X slash commands"
- Discord may need 1-2 minutes to sync commands
- Try typing `/` in Discord to force refresh
@@ -132,12 +144,14 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## ๐ Monitoring
### Key metrics to monitor:
+
- Bot uptime (should be 24/7)
- Command usage (in Supabase)
- Verification code usage (in Supabase)
- Discord role sync success rate
### View in Admin Dashboard:
+
- AeThex Admin Panel โ Discord Management tab
- Shows:
- Bot status
@@ -156,6 +170,7 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## ๐ Support
For issues:
+
1. Check Spaceship logs
2. Review `/api/discord/link` endpoint response
3. Verify all environment variables are set correctly
@@ -177,6 +192,7 @@ Run this migration on your AeThex Supabase:
## ๐ You're All Set!
Once deployed, users can:
+
1. Click "Link Discord" in their profile settings
2. Type `/verify` in Discord
3. Click the verification link
diff --git a/discord-bot/bot.js b/discord-bot/bot.js
index 44c65afc..dc06772c 100644
--- a/discord-bot/bot.js
+++ b/discord-bot/bot.js
@@ -1,11 +1,20 @@
-const { Client, GatewayIntentBits, REST, Routes, Collection, EmbedBuilder } = require('discord.js');
-const { createClient } = require('@supabase/supabase-js');
-const fs = require('fs');
-const path = require('path');
-require('dotenv').config();
+const {
+ Client,
+ GatewayIntentBits,
+ REST,
+ Routes,
+ Collection,
+ EmbedBuilder,
+} = require("discord.js");
+const { createClient } = require("@supabase/supabase-js");
+const fs = require("fs");
+const path = require("path");
+require("dotenv").config();
// Initialize Discord client
-const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages] });
+const client = new Client({
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages],
+});
// Initialize Supabase
const supabase = createClient(
@@ -17,35 +26,41 @@ const supabase = createClient(
client.commands = new Collection();
// Load commands from commands directory
-const commandsPath = path.join(__dirname, 'commands');
-const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
+const commandsPath = path.join(__dirname, "commands");
+const commandFiles = fs
+ .readdirSync(commandsPath)
+ .filter((file) => file.endsWith(".js"));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
- if ('data' in command && 'execute' in command) {
+ if ("data" in command && "execute" in command) {
client.commands.set(command.data.name, command);
console.log(`โ
Loaded command: ${command.data.name}`);
}
}
// Bot ready event
-client.once('ready', () => {
+client.once("ready", () => {
console.log(`โ
Bot logged in as ${client.user.tag}`);
console.log(`๐ก Listening in ${client.guilds.cache.size} server(s)`);
-
+
// Set bot status
- client.user.setActivity('/verify to link your AeThex account', { type: 'LISTENING' });
+ client.user.setActivity("/verify to link your AeThex account", {
+ type: "LISTENING",
+ });
});
// Slash command interaction handler
-client.on('interactionCreate', async interaction => {
+client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName);
if (!command) {
- console.warn(`โ ๏ธ No command matching ${interaction.commandName} was found.`);
+ console.warn(
+ `โ ๏ธ No command matching ${interaction.commandName} was found.`,
+ );
return;
}
@@ -53,12 +68,12 @@ client.on('interactionCreate', async interaction => {
await command.execute(interaction, supabase, client);
} catch (error) {
console.error(`โ Error executing ${interaction.commandName}:`, error);
-
+
const errorEmbed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Command Error')
- .setDescription('There was an error while executing this command.')
- .setFooter({ text: 'Contact support if this persists' });
+ .setColor(0xff0000)
+ .setTitle("โ Command Error")
+ .setDescription("There was an error while executing this command.")
+ .setFooter({ text: "Contact support if this persists" });
if (interaction.replied || interaction.deferred) {
await interaction.followUp({ embeds: [errorEmbed], ephemeral: true });
@@ -76,7 +91,9 @@ async function registerCommands() {
commands.push(command.data.toJSON());
}
- const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_BOT_TOKEN);
+ const rest = new REST({ version: "10" }).setToken(
+ process.env.DISCORD_BOT_TOKEN,
+ );
console.log(`๐ Registering ${commands.length} slash commands...`);
@@ -87,24 +104,24 @@ async function registerCommands() {
console.log(`โ
Successfully registered ${data.length} slash commands.`);
} catch (error) {
- console.error('โ Error registering commands:', error);
+ console.error("โ Error registering commands:", error);
}
}
// Login and register commands
client.login(process.env.DISCORD_BOT_TOKEN);
-client.once('ready', async () => {
+client.once("ready", async () => {
await registerCommands();
});
// Error handling
-process.on('unhandledRejection', error => {
- console.error('โ Unhandled Promise Rejection:', error);
+process.on("unhandledRejection", (error) => {
+ console.error("โ Unhandled Promise Rejection:", error);
});
-process.on('uncaughtException', error => {
- console.error('โ Uncaught Exception:', error);
+process.on("uncaughtException", (error) => {
+ console.error("โ Uncaught Exception:", error);
process.exit(1);
});
diff --git a/discord-bot/commands/profile.js b/discord-bot/commands/profile.js
index 314d96c9..035f251f 100644
--- a/discord-bot/commands/profile.js
+++ b/discord-bot/commands/profile.js
@@ -1,75 +1,92 @@
-const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
+const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
- .setName('profile')
- .setDescription('View your AeThex profile in Discord'),
+ .setName("profile")
+ .setDescription("View your AeThex profile in Discord"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
- .from('discord_links')
- .select('user_id, primary_arm')
- .eq('discord_id', interaction.user.id)
+ .from("discord_links")
+ .select("user_id, primary_arm")
+ .eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
- .setColor(0xFF6B6B)
- .setTitle('โ Not Linked')
- .setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
-
+ .setColor(0xff6b6b)
+ .setTitle("โ Not Linked")
+ .setDescription(
+ "You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
+ );
+
return await interaction.editReply({ embeds: [embed] });
}
const { data: profile } = await supabase
- .from('user_profiles')
- .select('*')
- .eq('id', link.user_id)
+ .from("user_profiles")
+ .select("*")
+ .eq("id", link.user_id)
.single();
if (!profile) {
const embed = new EmbedBuilder()
- .setColor(0xFF6B6B)
- .setTitle('โ Profile Not Found')
- .setDescription('Your AeThex profile could not be found.');
-
+ .setColor(0xff6b6b)
+ .setTitle("โ Profile Not Found")
+ .setDescription("Your AeThex profile could not be found.");
+
return await interaction.editReply({ embeds: [embed] });
}
const armEmojis = {
- labs: '๐งช',
- gameforge: '๐ฎ',
- corp: '๐ผ',
- foundation: '๐ค',
- devlink: '๐ป',
+ labs: "๐งช",
+ gameforge: "๐ฎ",
+ corp: "๐ผ",
+ foundation: "๐ค",
+ devlink: "๐ป",
};
const embed = new EmbedBuilder()
- .setColor(0x7289DA)
- .setTitle(`${profile.full_name || 'AeThex User'}'s Profile`)
- .setThumbnail(profile.avatar_url || 'https://aethex.dev/placeholder.svg')
- .addFields(
- { name: '๐ค Username', value: profile.username || 'N/A', inline: true },
- { name: `${armEmojis[link.primary_arm] || 'โ๏ธ'} Primary Realm`, value: link.primary_arm || 'Not set', inline: true },
- { name: '๐ Role', value: profile.user_type || 'community_member', inline: true },
- { name: '๐ Bio', value: profile.bio || 'No bio set', inline: false },
+ .setColor(0x7289da)
+ .setTitle(`${profile.full_name || "AeThex User"}'s Profile`)
+ .setThumbnail(
+ profile.avatar_url || "https://aethex.dev/placeholder.svg",
)
.addFields(
- { name: '๐ Links', value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})` },
+ {
+ name: "๐ค Username",
+ value: profile.username || "N/A",
+ inline: true,
+ },
+ {
+ name: `${armEmojis[link.primary_arm] || "โ๏ธ"} Primary Realm`,
+ value: link.primary_arm || "Not set",
+ inline: true,
+ },
+ {
+ name: "๐ Role",
+ value: profile.user_type || "community_member",
+ inline: true,
+ },
+ { name: "๐ Bio", value: profile.bio || "No bio set", inline: false },
)
- .setFooter({ text: 'AeThex | Your Web3 Creator Hub' });
+ .addFields({
+ name: "๐ Links",
+ value: `[Visit Full Profile](https://aethex.dev/creators/${profile.username})`,
+ })
+ .setFooter({ text: "AeThex | Your Web3 Creator Hub" });
await interaction.editReply({ embeds: [embed] });
} catch (error) {
- console.error('Profile command error:', error);
+ console.error("Profile command error:", error);
const embed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Error')
- .setDescription('Failed to fetch profile. Please try again.');
-
+ .setColor(0xff0000)
+ .setTitle("โ Error")
+ .setDescription("Failed to fetch profile. Please try again.");
+
await interaction.editReply({ embeds: [embed] });
}
},
diff --git a/discord-bot/commands/set-realm.js b/discord-bot/commands/set-realm.js
index 787eefc9..57573d48 100644
--- a/discord-bot/commands/set-realm.js
+++ b/discord-bot/commands/set-realm.js
@@ -1,42 +1,61 @@
-const { SlashCommandBuilder, EmbedBuilder, StringSelectMenuBuilder, ActionRowBuilder } = require('discord.js');
+const {
+ SlashCommandBuilder,
+ EmbedBuilder,
+ StringSelectMenuBuilder,
+ ActionRowBuilder,
+} = require("discord.js");
const REALMS = [
- { value: 'labs', label: '๐งช Labs', description: 'Research & Development' },
- { value: 'gameforge', label: '๐ฎ GameForge', description: 'Game Development' },
- { value: 'corp', label: '๐ผ Corp', description: 'Enterprise Solutions' },
- { value: 'foundation', label: '๐ค Foundation', description: 'Community & Education' },
- { value: 'devlink', label: '๐ป Dev-Link', description: 'Professional Networking' },
+ { value: "labs", label: "๐งช Labs", description: "Research & Development" },
+ {
+ value: "gameforge",
+ label: "๐ฎ GameForge",
+ description: "Game Development",
+ },
+ { value: "corp", label: "๐ผ Corp", description: "Enterprise Solutions" },
+ {
+ value: "foundation",
+ label: "๐ค Foundation",
+ description: "Community & Education",
+ },
+ {
+ value: "devlink",
+ label: "๐ป Dev-Link",
+ description: "Professional Networking",
+ },
];
module.exports = {
data: new SlashCommandBuilder()
- .setName('set-realm')
- .setDescription('Set your primary AeThex realm/arm'),
+ .setName("set-realm")
+ .setDescription("Set your primary AeThex realm/arm"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
- .from('discord_links')
- .select('user_id, primary_arm')
- .eq('discord_id', interaction.user.id)
+ .from("discord_links")
+ .select("user_id, primary_arm")
+ .eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
- .setColor(0xFF6B6B)
- .setTitle('โ Not Linked')
- .setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
-
+ .setColor(0xff6b6b)
+ .setTitle("โ Not Linked")
+ .setDescription(
+ "You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
+ );
+
return await interaction.editReply({ embeds: [embed] });
}
const select = new StringSelectMenuBuilder()
- .setCustomId('select_realm')
- .setPlaceholder('Choose your primary realm')
+ .setCustomId("select_realm")
+ .setPlaceholder("Choose your primary realm")
.addOptions(
- REALMS.map(realm => ({
+ REALMS.map((realm) => ({
label: realm.label,
description: realm.description,
value: realm.value,
@@ -47,48 +66,60 @@ module.exports = {
const row = new ActionRowBuilder().addComponents(select);
const embed = new EmbedBuilder()
- .setColor(0x7289DA)
- .setTitle('โ๏ธ Choose Your Realm')
- .setDescription('Select your primary AeThex realm. This determines your main Discord role.')
- .addFields(
- { name: 'Current Realm', value: link.primary_arm || 'Not set' },
- );
+ .setColor(0x7289da)
+ .setTitle("โ๏ธ Choose Your Realm")
+ .setDescription(
+ "Select your primary AeThex realm. This determines your main Discord role.",
+ )
+ .addFields({
+ name: "Current Realm",
+ value: link.primary_arm || "Not set",
+ });
await interaction.editReply({ embeds: [embed], components: [row] });
- const filter = i => i.user.id === interaction.user.id && i.customId === 'select_realm';
- const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
+ const filter = (i) =>
+ i.user.id === interaction.user.id && i.customId === "select_realm";
+ const collector = interaction.channel.createMessageComponentCollector({
+ filter,
+ time: 60000,
+ });
- collector.on('collect', async i => {
+ collector.on("collect", async (i) => {
const selectedRealm = i.values[0];
await supabase
- .from('discord_links')
+ .from("discord_links")
.update({ primary_arm: selectedRealm })
- .eq('discord_id', interaction.user.id);
+ .eq("discord_id", interaction.user.id);
- const realm = REALMS.find(r => r.value === selectedRealm);
+ const realm = REALMS.find((r) => r.value === selectedRealm);
const confirmEmbed = new EmbedBuilder()
- .setColor(0x00FF00)
- .setTitle('โ
Realm Set')
- .setDescription(`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`);
+ .setColor(0x00ff00)
+ .setTitle("โ
Realm Set")
+ .setDescription(
+ `Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`,
+ );
await i.update({ embeds: [confirmEmbed], components: [] });
});
- collector.on('end', collected => {
+ collector.on("end", (collected) => {
if (collected.size === 0) {
- interaction.editReply({ content: 'Realm selection timed out.', components: [] });
+ interaction.editReply({
+ content: "Realm selection timed out.",
+ components: [],
+ });
}
});
} catch (error) {
- console.error('Set-realm command error:', error);
+ console.error("Set-realm command error:", error);
const embed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Error')
- .setDescription('Failed to update realm. Please try again.');
-
+ .setColor(0xff0000)
+ .setTitle("โ Error")
+ .setDescription("Failed to update realm. Please try again.");
+
await interaction.editReply({ embeds: [embed] });
}
},
diff --git a/discord-bot/commands/unlink.js b/discord-bot/commands/unlink.js
index 7c88dab8..ac06d2a5 100644
--- a/discord-bot/commands/unlink.js
+++ b/discord-bot/commands/unlink.js
@@ -1,48 +1,49 @@
-const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
+const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
- .setName('unlink')
- .setDescription('Unlink your Discord account from AeThex'),
+ .setName("unlink")
+ .setDescription("Unlink your Discord account from AeThex"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
- .from('discord_links')
- .select('*')
- .eq('discord_id', interaction.user.id)
+ .from("discord_links")
+ .select("*")
+ .eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
- .setColor(0xFF6B6B)
- .setTitle('โน๏ธ Not Linked')
- .setDescription('Your Discord account is not linked to AeThex.');
-
+ .setColor(0xff6b6b)
+ .setTitle("โน๏ธ Not Linked")
+ .setDescription("Your Discord account is not linked to AeThex.");
+
return await interaction.editReply({ embeds: [embed] });
}
// Delete the link
await supabase
- .from('discord_links')
+ .from("discord_links")
.delete()
- .eq('discord_id', interaction.user.id);
+ .eq("discord_id", interaction.user.id);
// Remove Discord roles from user
const guild = interaction.guild;
const member = await guild.members.fetch(interaction.user.id);
-
+
// Find and remove all AeThex-related roles
- const rolesToRemove = member.roles.cache.filter(role =>
- role.name.includes('Labs') ||
- role.name.includes('GameForge') ||
- role.name.includes('Corp') ||
- role.name.includes('Foundation') ||
- role.name.includes('Dev-Link') ||
- role.name.includes('Premium') ||
- role.name.includes('Creator')
+ const rolesToRemove = member.roles.cache.filter(
+ (role) =>
+ role.name.includes("Labs") ||
+ role.name.includes("GameForge") ||
+ role.name.includes("Corp") ||
+ role.name.includes("Foundation") ||
+ role.name.includes("Dev-Link") ||
+ role.name.includes("Premium") ||
+ role.name.includes("Creator"),
);
for (const [, role] of rolesToRemove) {
@@ -54,18 +55,20 @@ module.exports = {
}
const embed = new EmbedBuilder()
- .setColor(0x00FF00)
- .setTitle('โ
Account Unlinked')
- .setDescription('Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.');
+ .setColor(0x00ff00)
+ .setTitle("โ
Account Unlinked")
+ .setDescription(
+ "Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.",
+ );
await interaction.editReply({ embeds: [embed] });
} catch (error) {
- console.error('Unlink command error:', error);
+ console.error("Unlink command error:", error);
const embed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Error')
- .setDescription('Failed to unlink account. Please try again.');
-
+ .setColor(0xff0000)
+ .setTitle("โ Error")
+ .setDescription("Failed to unlink account. Please try again.");
+
await interaction.editReply({ embeds: [embed] });
}
},
diff --git a/discord-bot/commands/verify-role.js b/discord-bot/commands/verify-role.js
index 7508397c..1b7e6b9f 100644
--- a/discord-bot/commands/verify-role.js
+++ b/discord-bot/commands/verify-role.js
@@ -1,71 +1,96 @@
-const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
+const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
- .setName('verify-role')
- .setDescription('Check your AeThex-assigned Discord roles'),
+ .setName("verify-role")
+ .setDescription("Check your AeThex-assigned Discord roles"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: link } = await supabase
- .from('discord_links')
- .select('user_id, primary_arm')
- .eq('discord_id', interaction.user.id)
+ .from("discord_links")
+ .select("user_id, primary_arm")
+ .eq("discord_id", interaction.user.id)
.single();
if (!link) {
const embed = new EmbedBuilder()
- .setColor(0xFF6B6B)
- .setTitle('โ Not Linked')
- .setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.');
-
+ .setColor(0xff6b6b)
+ .setTitle("โ Not Linked")
+ .setDescription(
+ "You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
+ );
+
return await interaction.editReply({ embeds: [embed] });
}
const { data: profile } = await supabase
- .from('user_profiles')
- .select('user_type')
- .eq('id', link.user_id)
+ .from("user_profiles")
+ .select("user_type")
+ .eq("id", link.user_id)
.single();
const { data: mappings } = await supabase
- .from('discord_role_mappings')
- .select('discord_role')
- .eq('arm', link.primary_arm)
- .eq('user_type', profile?.user_type || 'community_member');
+ .from("discord_role_mappings")
+ .select("discord_role")
+ .eq("arm", link.primary_arm)
+ .eq("user_type", profile?.user_type || "community_member");
const member = await interaction.guild.members.fetch(interaction.user.id);
- const aethexRoles = member.roles.cache.filter(role =>
- role.name.includes('Labs') ||
- role.name.includes('GameForge') ||
- role.name.includes('Corp') ||
- role.name.includes('Foundation') ||
- role.name.includes('Dev-Link') ||
- role.name.includes('Premium') ||
- role.name.includes('Creator')
+ const aethexRoles = member.roles.cache.filter(
+ (role) =>
+ role.name.includes("Labs") ||
+ role.name.includes("GameForge") ||
+ role.name.includes("Corp") ||
+ role.name.includes("Foundation") ||
+ role.name.includes("Dev-Link") ||
+ role.name.includes("Premium") ||
+ role.name.includes("Creator"),
);
const embed = new EmbedBuilder()
- .setColor(0x7289DA)
- .setTitle('๐ Your AeThex Roles')
+ .setColor(0x7289da)
+ .setTitle("๐ Your AeThex Roles")
.addFields(
- { name: 'โ๏ธ Primary Realm', value: link.primary_arm || 'Not set', inline: true },
- { name: '๐ค User Type', value: profile?.user_type || 'community_member', inline: true },
- { name: '๐ญ Discord Roles', value: aethexRoles.size > 0 ? aethexRoles.map(r => r.name).join(', ') : 'None assigned yet' },
- { name: '๐ Expected Roles', value: mappings?.length > 0 ? mappings.map(m => m.discord_role).join(', ') : 'No mappings found' },
+ {
+ name: "โ๏ธ Primary Realm",
+ value: link.primary_arm || "Not set",
+ inline: true,
+ },
+ {
+ name: "๐ค User Type",
+ value: profile?.user_type || "community_member",
+ inline: true,
+ },
+ {
+ name: "๐ญ Discord Roles",
+ value:
+ aethexRoles.size > 0
+ ? aethexRoles.map((r) => r.name).join(", ")
+ : "None assigned yet",
+ },
+ {
+ name: "๐ Expected Roles",
+ value:
+ mappings?.length > 0
+ ? mappings.map((m) => m.discord_role).join(", ")
+ : "No mappings found",
+ },
)
- .setFooter({ text: 'Roles are assigned automatically based on your AeThex profile' });
+ .setFooter({
+ text: "Roles are assigned automatically based on your AeThex profile",
+ });
await interaction.editReply({ embeds: [embed] });
} catch (error) {
- console.error('Verify-role command error:', error);
+ console.error("Verify-role command error:", error);
const embed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Error')
- .setDescription('Failed to verify roles. Please try again.');
-
+ .setColor(0xff0000)
+ .setTitle("โ Error")
+ .setDescription("Failed to verify roles. Please try again.");
+
await interaction.editReply({ embeds: [embed] });
}
},
diff --git a/discord-bot/commands/verify.js b/discord-bot/commands/verify.js
index f0436521..d5789079 100644
--- a/discord-bot/commands/verify.js
+++ b/discord-bot/commands/verify.js
@@ -1,35 +1,46 @@
-const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
+const {
+ SlashCommandBuilder,
+ EmbedBuilder,
+ ActionRowBuilder,
+ ButtonBuilder,
+ ButtonStyle,
+} = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
- .setName('verify')
- .setDescription('Link your Discord account to your AeThex account'),
+ .setName("verify")
+ .setDescription("Link your Discord account to your AeThex account"),
async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true });
try {
const { data: existingLink } = await supabase
- .from('discord_links')
- .select('*')
- .eq('discord_id', interaction.user.id)
+ .from("discord_links")
+ .select("*")
+ .eq("discord_id", interaction.user.id)
.single();
if (existingLink) {
const embed = new EmbedBuilder()
- .setColor(0x00FF00)
- .setTitle('โ
Already Linked')
- .setDescription(`Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`);
-
+ .setColor(0x00ff00)
+ .setTitle("โ
Already Linked")
+ .setDescription(
+ `Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`,
+ );
+
return await interaction.editReply({ embeds: [embed] });
}
// Generate verification code
- const verificationCode = Math.random().toString(36).substring(2, 8).toUpperCase();
+ const verificationCode = Math.random()
+ .toString(36)
+ .substring(2, 8)
+ .toUpperCase();
const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
// Store verification code
- await supabase.from('discord_verifications').insert({
+ await supabase.from("discord_verifications").insert({
discord_id: interaction.user.id,
verification_code: verificationCode,
expires_at: expiresAt.toISOString(),
@@ -38,30 +49,34 @@ module.exports = {
const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`;
const embed = new EmbedBuilder()
- .setColor(0x7289DA)
- .setTitle('๐ Link Your AeThex Account')
- .setDescription('Click the button below to link your Discord account to AeThex.')
- .addFields(
- { name: 'โฑ๏ธ Expires In', value: '15 minutes' },
- { name: '๐ Verification Code', value: `\`${verificationCode}\`` },
+ .setColor(0x7289da)
+ .setTitle("๐ Link Your AeThex Account")
+ .setDescription(
+ "Click the button below to link your Discord account to AeThex.",
)
- .setFooter({ text: 'Your security code will expire in 15 minutes' });
+ .addFields(
+ { name: "โฑ๏ธ Expires In", value: "15 minutes" },
+ { name: "๐ Verification Code", value: `\`${verificationCode}\`` },
+ )
+ .setFooter({ text: "Your security code will expire in 15 minutes" });
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder()
- .setLabel('Link Account')
+ .setLabel("Link Account")
.setStyle(ButtonStyle.Link)
.setURL(verifyUrl),
);
await interaction.editReply({ embeds: [embed], components: [row] });
} catch (error) {
- console.error('Verify command error:', error);
+ console.error("Verify command error:", error);
const embed = new EmbedBuilder()
- .setColor(0xFF0000)
- .setTitle('โ Error')
- .setDescription('Failed to generate verification code. Please try again.');
-
+ .setColor(0xff0000)
+ .setTitle("โ Error")
+ .setDescription(
+ "Failed to generate verification code. Please try again.",
+ );
+
await interaction.editReply({ embeds: [embed] });
}
},
diff --git a/docs/GAME-INTEGRATION-GUIDE.md b/docs/GAME-INTEGRATION-GUIDE.md
index b72e074a..97d4b57c 100644
--- a/docs/GAME-INTEGRATION-GUIDE.md
+++ b/docs/GAME-INTEGRATION-GUIDE.md
@@ -16,6 +16,7 @@ This guide covers how to integrate AeThex authentication with game engines: Robl
## Overview
AeThex provides a unified authentication system for games to:
+
- Authenticate players across different platforms
- Link player accounts to Roblox, Ethereum wallets, and other providers
- Store player profiles and achievements
@@ -53,13 +54,13 @@ function AethexAuth:authenticate(playerId, playerName)
player_name = playerName,
platform = "PC"
})
-
+
local response = game:GetService("HttpService"):PostAsync(
API_BASE .. "/games/game-auth",
body,
Enum.HttpContentType.ApplicationJson
)
-
+
local data = game:GetService("HttpService"):JSONDecode(response)
return data
end
@@ -73,7 +74,7 @@ function AethexAuth:verifyToken(sessionToken)
}),
Enum.HttpContentType.ApplicationJson
)
-
+
return game:GetService("HttpService"):JSONDecode(response)
end
@@ -88,7 +89,7 @@ local AethexAuth = require(game.ServerScriptService:WaitForChild("AethexAuth"))
Players.PlayerAdded:Connect(function(player)
local authResult = AethexAuth:authenticate(player.UserId, player.Name)
-
+
if authResult.success then
player:SetAttribute("AethexSessionToken", authResult.session_token)
player:SetAttribute("AethexUserId", authResult.user_id)
@@ -118,7 +119,7 @@ using System.Collections;
public class AethexAuth : MonoBehaviour
{
private const string API_BASE = "https://aethex.dev/api";
-
+
[System.Serializable]
public class AuthResponse
{
@@ -129,7 +130,7 @@ public class AethexAuth : MonoBehaviour
public int expires_in;
public string error;
}
-
+
public static IEnumerator AuthenticatePlayer(
string playerId,
string playerName,
@@ -139,7 +140,7 @@ public class AethexAuth : MonoBehaviour
$"{API_BASE}/games/game-auth",
"POST"
);
-
+
var requestBody = new AuthRequest
{
game = "unity",
@@ -147,14 +148,14 @@ public class AethexAuth : MonoBehaviour
player_name = playerName,
platform = "PC"
};
-
+
string jsonBody = JsonUtility.ToJson(requestBody);
request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
-
+
yield return request.SendWebRequest();
-
+
if (request.result == UnityWebRequest.Result.Success)
{
var response = JsonUtility.FromJson(request.downloadHandler.text);
@@ -165,7 +166,7 @@ public class AethexAuth : MonoBehaviour
callback(new AuthResponse { error = request.error });
}
}
-
+
[System.Serializable]
private class AuthRequest
{
@@ -186,10 +187,10 @@ public class GameManager : MonoBehaviour
{
string playerId = SystemInfo.deviceUniqueIdentifier;
string playerName = "UnityPlayer_" + Random.Range(1000, 9999);
-
+
StartCoroutine(AethexAuth.AuthenticatePlayer(playerId, playerName, OnAuthComplete));
}
-
+
void OnAuthComplete(AethexAuth.AuthResponse response)
{
if (response.success)
@@ -231,12 +232,12 @@ public:
int32 ExpiresIn;
FString Error;
};
-
+
static void AuthenticatePlayer(
const FString& PlayerId,
const FString& PlayerName,
TFunction OnComplete);
-
+
private:
static void OnAuthResponse(
FHttpRequestPtr Request,
@@ -259,30 +260,30 @@ void FAethexAuth::AuthenticatePlayer(
TFunction OnComplete)
{
FHttpModule& HttpModule = FHttpModule::Get();
- TSharedRef Request =
+ TSharedRef Request =
HttpModule.CreateRequest();
-
+
Request->SetURL(TEXT("https://aethex.dev/api/games/game-auth"));
Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
-
+
// Create JSON body
TSharedPtr JsonObject = MakeShareable(new FJsonObject());
JsonObject->SetStringField(TEXT("game"), TEXT("unreal"));
JsonObject->SetStringField(TEXT("player_id"), PlayerId);
JsonObject->SetStringField(TEXT("player_name"), PlayerName);
JsonObject->SetStringField(TEXT("platform"), TEXT("PC"));
-
+
FString OutputString;
TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
-
+
Request->SetContentAsString(OutputString);
-
+
Request->OnProcessRequestComplete().BindStatic(
&FAethexAuth::OnAuthResponse,
OnComplete);
-
+
Request->ProcessRequest();
}
@@ -293,12 +294,12 @@ void FAethexAuth::OnAuthResponse(
TFunction OnComplete)
{
FAuthResponse AuthResponse;
-
+
if (bWasSuccessful && Response.IsValid())
{
TSharedPtr JsonObject;
TSharedRef> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
-
+
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
AuthResponse.bSuccess = JsonObject->GetBoolField(TEXT("success"));
@@ -312,7 +313,7 @@ void FAethexAuth::OnAuthResponse(
{
AuthResponse.Error = TEXT("Authentication request failed");
}
-
+
OnComplete(AuthResponse);
}
```
@@ -331,68 +332,68 @@ const API_BASE = "https://aethex.dev/api"
func authenticate_player(player_id: String, player_name: String) -> Dictionary:
var http_request = HTTPRequest.new()
add_child(http_request)
-
+
var url = API_BASE + "/games/game-auth"
var headers = ["Content-Type: application/json"]
-
+
var body = {
"game": "godot",
"player_id": player_id,
"player_name": player_name,
"platform": OS.get_name()
}
-
+
var response = http_request.request(
url,
headers,
HTTPClient.METHOD_POST,
JSON.stringify(body)
)
-
+
if response != OK:
return {"error": "Request failed"}
-
+
var result = await http_request.request_completed
-
+
if result[1] != 200:
return {"error": "Authentication failed"}
-
+
var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free()
-
+
return response_data
func verify_token(session_token: String) -> Dictionary:
var http_request = HTTPRequest.new()
add_child(http_request)
-
+
var url = API_BASE + "/games/verify-token"
var headers = ["Content-Type: application/json"]
-
+
var body = {
"session_token": session_token,
"game": "godot"
}
-
+
var response = http_request.request(
url,
headers,
HTTPClient.METHOD_POST,
JSON.stringify(body)
)
-
+
var result = await http_request.request_completed
var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free()
-
+
return response_data
func _ready():
var player_id = OS.get_unique_id()
var player_name = "GodotPlayer_" + str(randi_range(1000, 9999))
-
+
var auth_result = await authenticate_player(player_id, player_name)
-
+
if auth_result.has("success") and auth_result["success"]:
print("Authenticated as: ", auth_result["username"])
# Store token
@@ -413,27 +414,29 @@ func _ready():
Authenticate a game player and create a session.
**Request:**
+
```json
{
- "game": "unity|unreal|godot|roblox|custom",
- "player_id": "unique-player-id",
- "player_name": "player-display-name",
- "device_id": "optional-device-id",
- "platform": "PC|Mobile|Console"
+ "game": "unity|unreal|godot|roblox|custom",
+ "player_id": "unique-player-id",
+ "player_name": "player-display-name",
+ "device_id": "optional-device-id",
+ "platform": "PC|Mobile|Console"
}
```
**Response:**
+
```json
{
- "success": true,
- "session_token": "token-string",
- "user_id": "uuid",
- "username": "username",
- "game": "unity",
- "expires_in": 604800,
- "api_base_url": "https://aethex.dev/api",
- "docs_url": "https://docs.aethex.dev/game-integration"
+ "success": true,
+ "session_token": "token-string",
+ "user_id": "uuid",
+ "username": "username",
+ "game": "unity",
+ "expires_in": 604800,
+ "api_base_url": "https://aethex.dev/api",
+ "docs_url": "https://docs.aethex.dev/game-integration"
}
```
@@ -442,24 +445,26 @@ Authenticate a game player and create a session.
Verify a game session token and get player data.
**Request:**
+
```json
{
- "session_token": "token-string",
- "game": "unity"
+ "session_token": "token-string",
+ "game": "unity"
}
```
**Response:**
+
```json
{
- "valid": true,
- "user_id": "uuid",
- "username": "username",
- "email": "user@example.com",
- "full_name": "Player Name",
- "game": "unity",
- "platform": "PC",
- "expires_at": "2025-01-15T10:30:00Z"
+ "valid": true,
+ "user_id": "uuid",
+ "username": "username",
+ "email": "user@example.com",
+ "full_name": "Player Name",
+ "game": "unity",
+ "platform": "PC",
+ "expires_at": "2025-01-15T10:30:00Z"
}
```
@@ -475,6 +480,7 @@ Verify a game session token and get player data.
## Support
For issues or questions, visit:
+
- Docs: https://docs.aethex.dev
- Discord: https://discord.gg/aethex
- Email: support@aethex.tech