Prettier format pending files

This commit is contained in:
Builder.io 2025-11-08 11:03:25 +00:00
parent ba233408ed
commit e5aacf6773
25 changed files with 817 additions and 551 deletions

View file

@ -64,18 +64,17 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
if (existingLink) { if (existingLink) {
return res.status(409).json({ 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 // Create the link
const { error: linkError } = await supabase const { error: linkError } = await supabase.from("discord_links").insert({
.from("discord_links") discord_id: verification.discord_id,
.insert({ user_id,
discord_id: verification.discord_id, primary_arm: "labs", // Default to labs
user_id, });
primary_arm: "labs", // Default to labs
});
if (linkError) { if (linkError) {
console.error("Failed to create discord link:", linkError); console.error("Failed to create discord link:", linkError);

View file

@ -24,7 +24,10 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Verify request is from Discord bot (simple verification) // Verify request is from Discord bot (simple verification)
const authorization = req.headers.authorization; 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" }); return res.status(401).json({ error: "Unauthorized" });
} }

View file

@ -31,7 +31,11 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const secret = process.env.ROBLOX_SHARED_SECRET || ""; const secret = process.env.ROBLOX_SHARED_SECRET || "";
// Verify signature for security // Verify signature for security
if (signature && secret && !verifyRobloxSignature(payload, signature, secret)) { if (
signature &&
secret &&
!verifyRobloxSignature(payload, signature, secret)
) {
console.warn("Invalid Roblox signature"); console.warn("Invalid Roblox signature");
return res.status(401).json({ error: "Invalid signature" }); return res.status(401).json({ error: "Invalid signature" });
} }

View file

@ -13,7 +13,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
} }
try { 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) { if (!session_token) {
return res.status(400).json({ error: "session_token is required" }); 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 // Find the session
const { data: sessionData, error: sessionError } = await supabase const { data: sessionData, error: sessionError } = await supabase
.from("game_sessions") .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)) .eq("session_token", String(session_token))
.single(); .single();
@ -38,7 +41,9 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
// Optional: Verify game matches if provided // Optional: Verify game matches if provided
if (game && sessionData.game !== String(game).toLowerCase()) { 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 // Update last activity

View file

@ -31,7 +31,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID; const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID;
const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET; const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET;
const redirectUri = 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) { if (!clientId || !clientSecret) {
return res.status(500).json({ error: "Roblox OAuth not configured" }); 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(); const tokenData: RobloxTokenResponse = await tokenResponse.json();
// Get user info with access token // Get user info with access token
const userResponse = await fetch("https://apis.roblox.com/oauth/v1/userinfo", { const userResponse = await fetch(
headers: { Authorization: `Bearer ${tokenData.access_token}` }, "https://apis.roblox.com/oauth/v1/userinfo",
}); {
headers: { Authorization: `Bearer ${tokenData.access_token}` },
},
);
if (!userResponse.ok) { if (!userResponse.ok) {
console.error("Failed to fetch Roblox user info"); console.error("Failed to fetch Roblox user info");

View file

@ -21,7 +21,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7); const token = authHeader.substring(7);
// Verify token with Supabase // 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) { if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" }); return res.status(401).json({ error: "Invalid token" });
} }

View file

@ -22,7 +22,8 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const token = authHeader.substring(7); const token = authHeader.substring(7);
// Verify token with Supabase // 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) { if (authError || !userData.user) {
return res.status(401).json({ error: "Invalid token" }); return res.status(401).json({ error: "Invalid token" });
} }
@ -66,15 +67,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
.eq("nonce", nonce); .eq("nonce", nonce);
// Link wallet to existing user // Link wallet to existing user
const { error: linkError } = await supabase const { error: linkError } = await supabase.from("web3_wallets").insert({
.from("web3_wallets") user_id: userData.user.id,
.insert({ wallet_address: normalizedAddress,
user_id: userData.user.id, chain_id: 1, // Ethereum mainnet
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); console.error("Failed to link wallet:", linkError);
return res.status(500).json({ error: "Failed to link wallet" }); return res.status(500).json({ error: "Failed to link wallet" });
} }

View file

@ -80,15 +80,16 @@ export default async function handler(req: VercelRequest, res: VercelResponse) {
const username = normalizedAddress.substring(2, 10); const username = normalizedAddress.substring(2, 10);
// Create Supabase auth user // Create Supabase auth user
const { data: authData, error: authError } = await supabase.auth.admin.createUser({ const { data: authData, error: authError } =
email, await supabase.auth.admin.createUser({
password: require("crypto").randomBytes(32).toString("hex"), email,
email_confirm: true, password: require("crypto").randomBytes(32).toString("hex"),
user_metadata: { email_confirm: true,
wallet_address: normalizedAddress, user_metadata: {
auth_method: "web3", wallet_address: normalizedAddress,
}, auth_method: "web3",
}); },
});
if (authError || !authData.user) { if (authError || !authData.user) {
console.error("Failed to create auth user:", authError); console.error("Failed to create auth user:", authError);

View file

@ -119,232 +119,247 @@ const App = () => (
<Web3Provider> <Web3Provider>
<DiscordProvider> <DiscordProvider>
<TooltipProvider> <TooltipProvider>
<Toaster /> <Toaster />
<Analytics /> <Analytics />
<BrowserRouter> <BrowserRouter>
<SkipAgentController /> <SkipAgentController />
<PageTransition> <PageTransition>
<Routes> <Routes>
<Route path="/" element={<Index />} /> <Route path="/" element={<Index />} />
<Route path="/onboarding" element={<Onboarding />} /> <Route path="/onboarding" element={<Onboarding />} />
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/dashboard" element={<Dashboard />} />
<Route path="/realms" element={<Realms />} /> <Route path="/realms" element={<Realms />} />
<Route path="/investors" element={<Investors />} /> <Route path="/investors" element={<Investors />} />
<Route path="/roadmap" element={<Roadmap />} /> <Route path="/roadmap" element={<Roadmap />} />
<Route path="/trust" element={<Trust />} /> <Route path="/trust" element={<Trust />} />
<Route path="/press" element={<PressKit />} /> <Route path="/press" element={<PressKit />} />
<Route path="/projects" element={<Projects />} /> <Route path="/projects" element={<Projects />} />
<Route path="/projects/admin" element={<ProjectsAdmin />} /> <Route path="/projects/admin" element={<ProjectsAdmin />} />
<Route path="/directory" element={<Directory />} /> <Route path="/directory" element={<Directory />} />
<Route path="/admin" element={<Admin />} /> <Route path="/admin" element={<Admin />} />
<Route path="/admin/docs-sync" element={<DocsSync />} /> <Route path="/admin/docs-sync" element={<DocsSync />} />
<Route path="/feed" element={<Feed />} /> <Route path="/feed" element={<Feed />} />
<Route path="/teams" element={<Teams />} /> <Route path="/teams" element={<Teams />} />
<Route path="/squads" element={<Squads />} /> <Route path="/squads" element={<Squads />} />
<Route path="/mentee-hub" element={<MenteeHub />} /> <Route path="/mentee-hub" element={<MenteeHub />} />
<Route path="/projects/new" element={<ProjectsNew />} /> <Route path="/projects/new" element={<ProjectsNew />} />
<Route
path="/projects/:projectId/board"
element={<ProjectBoard />}
/>
<Route path="/profile" element={<Profile />} />
<Route path="/profile/me" element={<Profile />} />
<Route
path="/profile/applications"
element={<MyApplications />}
/>
<Route path="/developers" element={<DevelopersDirectory />} />
<Route
path="/developers/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/developers/:id"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles"
element={<Navigate to="/developers" replace />}
/>
<Route
path="/profiles/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles/:id"
element={<LegacyPassportRedirect />}
/>
<Route
path="/passport"
element={<Navigate to="/passport/me" replace />}
/>
<Route path="/passport/me" element={<ProfilePassport />} />
<Route
path="/passport/:username"
element={<ProfilePassport />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignupRedirect />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/roblox-callback" element={<RobloxCallback />} />
<Route path="/web3-callback" element={<Web3Callback />} />
<Route path="/discord-verify" element={<DiscordVerify />} />
{/* Creator Network routes */}
<Route path="/creators" element={<CreatorDirectory />} />
<Route
path="/creators/:username"
element={<CreatorProfile />}
/>
<Route path="/opportunities" element={<OpportunitiesHub />} />
<Route
path="/opportunities/:id"
element={<OpportunityDetail />}
/>
{/* Service routes */}
<Route path="/game-development" element={<GameDevelopment />} />
<Route path="/consulting" element={<DevelopmentConsulting />} />
<Route path="/mentorship" element={<MentorshipPrograms />} />
<Route path="/engage" element={<Engage />} />
<Route
path="/pricing"
element={<Navigate to="/engage" replace />}
/>
<Route path="/research" element={<ResearchLabs />} />
{/* New Arm Landing Pages */}
<Route path="/labs" element={<Labs />} />
<Route
path="/labs/explore-research"
element={<LabsExploreResearch />}
/>
<Route path="/labs/join-team" element={<LabsJoinTeam />} />
<Route
path="/labs/get-involved"
element={<LabsGetInvolved />}
/>
<Route path="/gameforge" element={<GameForge />} />
<Route
path="/gameforge/start-building"
element={<GameForgeStartBuilding />}
/>
<Route
path="/gameforge/view-portfolio"
element={<GameForgeViewPortfolio />}
/>
<Route
path="/gameforge/join-gameforge"
element={<GameForgeJoinGameForge />}
/>
<Route path="/corp" element={<Corp />} />
<Route
path="/corp/schedule-consultation"
element={<CorpScheduleConsultation />}
/>
<Route
path="/corp/view-case-studies"
element={<CorpViewCaseStudies />}
/>
<Route path="/corp/contact-us" element={<CorpContactUs />} />
<Route path="/foundation" element={<Foundation />} />
<Route
path="/foundation/contribute"
element={<FoundationContribute />}
/>
<Route
path="/foundation/learn-more"
element={<FoundationLearnMore />}
/>
<Route
path="/foundation/get-involved"
element={<FoundationGetInvolved />}
/>
{/* Dev-Link routes */}
<Route path="/dev-link" element={<DevLink />} />
<Route
path="/dev-link/waitlist"
element={<DevLinkProfiles />}
/>
{/* Nexus routes */}
<Route path="/nexus" element={<Nexus />} />
{/* Resource routes */}
<Route path="/docs" element={<DocsLayout />}>
<Route index element={<DocsOverview />} />
<Route path="tutorials" element={<DocsTutorials />} />
<Route path="curriculum" element={<DocsCurriculum />} />
<Route <Route
path="getting-started" path="/projects/:projectId/board"
element={<DocsGettingStarted />} element={<ProjectBoard />}
/>
<Route path="/profile" element={<Profile />} />
<Route path="/profile/me" element={<Profile />} />
<Route
path="/profile/applications"
element={<MyApplications />}
/> />
<Route path="platform" element={<DocsPlatform />} />
<Route path="api" element={<DocsApiReference />} />
<Route path="cli" element={<DocsCli />} />
<Route path="examples" element={<DocsExamples />} />
<Route path="integrations" element={<DocsIntegrations />} />
</Route>
<Route path="/tutorials" element={<Tutorials />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/community" element={<Community />} />
<Route path="/community/teams" element={<FoundationTeams />} />
<Route path="/community/about" element={<FoundationAbout />} />
<Route
path="/community/mentorship"
element={<MentorshipRequest />}
/>
<Route
path="/community/mentorship/apply"
element={<MentorApply />}
/>
<Route
path="/community/mentor/:username"
element={<MentorProfile />}
/>
<Route path="/community/:tabId" element={<Community />} />
<Route path="/staff" element={<Staff />} />
<Route path="/support" element={<Support />} />
<Route path="/status" element={<Status />} />
<Route path="/changelog" element={<Changelog />} />
{/* Informational routes */} <Route path="/developers" element={<DevelopersDirectory />} />
<Route path="/wix" element={<Wix />} /> <Route
<Route path="/wix/case-studies" element={<WixCaseStudies />} /> path="/developers/me"
<Route path="/wix/faq" element={<WixFaq />} /> element={<LegacyPassportRedirect />}
<Route path="/about" element={<About />} /> />
<Route path="/contact" element={<Contact />} /> <Route
<Route path="/get-started" element={<GetStarted />} /> path="/developers/:id"
<Route path="/explore" element={<Explore />} /> element={<LegacyPassportRedirect />}
<Route path="/services" element={<Services />} /> />
<Route path="/careers" element={<Careers />} /> <Route
path="/profiles"
element={<Navigate to="/developers" replace />}
/>
<Route
path="/profiles/me"
element={<LegacyPassportRedirect />}
/>
<Route
path="/profiles/:id"
element={<LegacyPassportRedirect />}
/>
{/* Legal routes */} <Route
<Route path="/privacy" element={<Privacy />} /> path="/passport"
<Route path="/terms" element={<Terms />} /> element={<Navigate to="/passport/me" replace />}
/>
<Route path="/passport/me" element={<ProfilePassport />} />
<Route
path="/passport/:username"
element={<ProfilePassport />}
/>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignupRedirect />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/roblox-callback" element={<RobloxCallback />} />
<Route path="/web3-callback" element={<Web3Callback />} />
<Route path="/discord-verify" element={<DiscordVerify />} />
{/* Discord routes */} {/* Creator Network routes */}
<Route path="/discord" element={<DiscordActivity />} /> <Route path="/creators" element={<CreatorDirectory />} />
<Route <Route
path="/discord/callback" path="/creators/:username"
element={<DiscordOAuthCallback />} element={<CreatorProfile />}
/> />
<Route path="/opportunities" element={<OpportunitiesHub />} />
<Route
path="/opportunities/:id"
element={<OpportunityDetail />}
/>
{/* Explicit 404 route for static hosting fallbacks */} {/* Service routes */}
<Route path="/404" element={<FourOhFourPage />} /> <Route
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} path="/game-development"
<Route path="*" element={<FourOhFourPage />} /> element={<GameDevelopment />}
</Routes> />
</PageTransition> <Route
</BrowserRouter> path="/consulting"
</TooltipProvider> element={<DevelopmentConsulting />}
/>
<Route path="/mentorship" element={<MentorshipPrograms />} />
<Route path="/engage" element={<Engage />} />
<Route
path="/pricing"
element={<Navigate to="/engage" replace />}
/>
<Route path="/research" element={<ResearchLabs />} />
{/* New Arm Landing Pages */}
<Route path="/labs" element={<Labs />} />
<Route
path="/labs/explore-research"
element={<LabsExploreResearch />}
/>
<Route path="/labs/join-team" element={<LabsJoinTeam />} />
<Route
path="/labs/get-involved"
element={<LabsGetInvolved />}
/>
<Route path="/gameforge" element={<GameForge />} />
<Route
path="/gameforge/start-building"
element={<GameForgeStartBuilding />}
/>
<Route
path="/gameforge/view-portfolio"
element={<GameForgeViewPortfolio />}
/>
<Route
path="/gameforge/join-gameforge"
element={<GameForgeJoinGameForge />}
/>
<Route path="/corp" element={<Corp />} />
<Route
path="/corp/schedule-consultation"
element={<CorpScheduleConsultation />}
/>
<Route
path="/corp/view-case-studies"
element={<CorpViewCaseStudies />}
/>
<Route path="/corp/contact-us" element={<CorpContactUs />} />
<Route path="/foundation" element={<Foundation />} />
<Route
path="/foundation/contribute"
element={<FoundationContribute />}
/>
<Route
path="/foundation/learn-more"
element={<FoundationLearnMore />}
/>
<Route
path="/foundation/get-involved"
element={<FoundationGetInvolved />}
/>
{/* Dev-Link routes */}
<Route path="/dev-link" element={<DevLink />} />
<Route
path="/dev-link/waitlist"
element={<DevLinkProfiles />}
/>
{/* Nexus routes */}
<Route path="/nexus" element={<Nexus />} />
{/* Resource routes */}
<Route path="/docs" element={<DocsLayout />}>
<Route index element={<DocsOverview />} />
<Route path="tutorials" element={<DocsTutorials />} />
<Route path="curriculum" element={<DocsCurriculum />} />
<Route
path="getting-started"
element={<DocsGettingStarted />}
/>
<Route path="platform" element={<DocsPlatform />} />
<Route path="api" element={<DocsApiReference />} />
<Route path="cli" element={<DocsCli />} />
<Route path="examples" element={<DocsExamples />} />
<Route path="integrations" element={<DocsIntegrations />} />
</Route>
<Route path="/tutorials" element={<Tutorials />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/community" element={<Community />} />
<Route
path="/community/teams"
element={<FoundationTeams />}
/>
<Route
path="/community/about"
element={<FoundationAbout />}
/>
<Route
path="/community/mentorship"
element={<MentorshipRequest />}
/>
<Route
path="/community/mentorship/apply"
element={<MentorApply />}
/>
<Route
path="/community/mentor/:username"
element={<MentorProfile />}
/>
<Route path="/community/:tabId" element={<Community />} />
<Route path="/staff" element={<Staff />} />
<Route path="/support" element={<Support />} />
<Route path="/status" element={<Status />} />
<Route path="/changelog" element={<Changelog />} />
{/* Informational routes */}
<Route path="/wix" element={<Wix />} />
<Route
path="/wix/case-studies"
element={<WixCaseStudies />}
/>
<Route path="/wix/faq" element={<WixFaq />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/get-started" element={<GetStarted />} />
<Route path="/explore" element={<Explore />} />
<Route path="/services" element={<Services />} />
<Route path="/careers" element={<Careers />} />
{/* Legal routes */}
<Route path="/privacy" element={<Privacy />} />
<Route path="/terms" element={<Terms />} />
{/* Discord routes */}
<Route path="/discord" element={<DiscordActivity />} />
<Route
path="/discord/callback"
element={<DiscordOAuthCallback />}
/>
{/* Explicit 404 route for static hosting fallbacks */}
<Route path="/404" element={<FourOhFourPage />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<FourOhFourPage />} />
</Routes>
</PageTransition>
</BrowserRouter>
</TooltipProvider>
</DiscordProvider> </DiscordProvider>
</Web3Provider> </Web3Provider>
</AuthProvider> </AuthProvider>

View file

@ -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 { Button } from "@/components/ui/button";
import { CheckCircle2 } from "lucide-react"; import { CheckCircle2 } from "lucide-react";
@ -12,7 +18,8 @@ const REALMS = [
{ {
id: "labs", id: "labs",
title: "🧪 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", color: "from-yellow-500/20 to-yellow-600/20",
borderColor: "border-yellow-400", 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 ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h2 className="text-2xl font-bold text-white mb-2">Choose Your Primary Realm</h2> <h2 className="text-2xl font-bold text-white mb-2">
Choose Your Primary Realm
</h2>
<p className="text-gray-400"> <p className="text-gray-400">
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.
</p> </p>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{REALMS.map(realm => ( {REALMS.map((realm) => (
<div <div
key={realm.id} key={realm.id}
onClick={() => onSelect(realm.id)} onClick={() => onSelect(realm.id)}
@ -67,7 +81,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
> >
<Card <Card
className={`h-full border-2 ${realm.borderColor} ${ className={`h-full border-2 ${realm.borderColor} ${
selectedRealm === realm.id ? "ring-2 ring-offset-2 ring-white" : "" selectedRealm === realm.id
? "ring-2 ring-offset-2 ring-white"
: ""
} hover:shadow-lg transition-shadow`} } hover:shadow-lg transition-shadow`}
> >
<CardHeader> <CardHeader>
@ -81,7 +97,9 @@ export default function RealmSelection({ selectedRealm, onSelect, onNext }: Real
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<CardDescription className="text-sm">{realm.description}</CardDescription> <CardDescription className="text-sm">
{realm.description}
</CardDescription>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View file

@ -836,8 +836,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
window.localStorage.removeItem("onboarding_complete"); window.localStorage.removeItem("onboarding_complete");
window.localStorage.removeItem("aethex_onboarding_progress_v1"); window.localStorage.removeItem("aethex_onboarding_progress_v1");
Object.keys(window.localStorage) Object.keys(window.localStorage)
.filter(key => key.startsWith("sb-") || key.includes("supabase") || key.startsWith("mock_") || key.startsWith("demo_")) .filter(
.forEach(key => window.localStorage.removeItem(key)); (key) =>
key.startsWith("sb-") ||
key.includes("supabase") ||
key.startsWith("mock_") ||
key.startsWith("demo_"),
)
.forEach((key) => window.localStorage.removeItem(key));
console.log("localStorage cleared"); console.log("localStorage cleared");
} catch (e) { } catch (e) {
console.warn("localStorage clear failed:", e); console.warn("localStorage clear failed:", e);

View file

@ -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"; import { aethexToast } from "@/lib/aethex-toast";
export interface Web3ContextType { export interface Web3ContextType {
@ -13,7 +19,9 @@ export interface Web3ContextType {
const Web3Context = createContext<Web3ContextType | undefined>(undefined); const Web3Context = createContext<Web3ContextType | undefined>(undefined);
export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [account, setAccount] = useState<string | null>(null); const [account, setAccount] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
@ -100,7 +108,11 @@ export const Web3Provider: React.FC<{ children: React.ReactNode }> = ({ children
const signMessage = useCallback( const signMessage = useCallback(
async (message: string): Promise<string> => { async (message: string): Promise<string> => {
if (!account || typeof window === "undefined" || !(window as any).ethereum) { if (
!account ||
typeof window === "undefined" ||
!(window as any).ethereum
) {
throw new Error("Wallet not connected"); 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); (window as any).ethereum.on("chainChanged", handleChainChanged);
return () => { return () => {
(window as any).ethereum?.removeListener("accountsChanged", handleAccountsChanged); (window as any).ethereum?.removeListener(
(window as any).ethereum?.removeListener("chainChanged", handleChainChanged); "accountsChanged",
handleAccountsChanged,
);
(window as any).ethereum?.removeListener(
"chainChanged",
handleChainChanged,
);
}; };
}, []); }, []);

View file

@ -1513,27 +1513,59 @@ export default function Admin() {
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<div className="grid gap-4"> <div className="grid gap-4">
<div className="bg-background/40 rounded-lg p-4 border border-border/40"> <div className="bg-background/40 rounded-lg p-4 border border-border/40">
<h3 className="font-semibold mb-4">Realm to Discord Role Mappings</h3> <h3 className="font-semibold mb-4">
Realm to Discord Role Mappings
</h3>
<div className="space-y-2"> <div className="space-y-2">
<p className="text-sm text-gray-400"> <p className="text-sm text-gray-400">
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.
</p> </p>
<div className="mt-4 space-y-3"> <div className="mt-4 space-y-3">
{[ {[
{ realm: "Labs", role: "Labs Creator", members: 234 }, {
{ realm: "GameForge", role: "GameForge Creator", members: 456 }, realm: "Labs",
{ realm: "Corp", role: "Corp Member", members: 89 }, role: "Labs Creator",
{ realm: "Foundation", role: "Foundation Member", members: 145 }, members: 234,
{ realm: "Dev-Link", role: "Dev-Link Member", members: 78 }, },
{
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) => ( ].map((mapping, idx) => (
<div key={idx} className="flex items-center justify-between p-3 bg-background/30 rounded border border-border/20"> <div
key={idx}
className="flex items-center justify-between p-3 bg-background/30 rounded border border-border/20"
>
<div> <div>
<p className="font-medium">{mapping.realm}</p> <p className="font-medium">{mapping.realm}</p>
<p className="text-sm text-gray-400">{mapping.role}</p> <p className="text-sm text-gray-400">
{mapping.role}
</p>
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-lg font-bold text-purple-400">{mapping.members}</p> <p className="text-lg font-bold text-purple-400">
<p className="text-xs text-gray-500">assigned members</p> {mapping.members}
</p>
<p className="text-xs text-gray-500">
assigned members
</p>
</div> </div>
</div> </div>
))} ))}
@ -1542,19 +1574,31 @@ export default function Admin() {
</div> </div>
<div className="bg-background/40 rounded-lg p-4 border border-border/40"> <div className="bg-background/40 rounded-lg p-4 border border-border/40">
<h3 className="font-semibold mb-3">Bot Configuration</h3> <h3 className="font-semibold mb-3">
Bot Configuration
</h3>
<div className="space-y-2 text-sm"> <div className="space-y-2 text-sm">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-gray-400">Bot Status</span> <span className="text-gray-400">Bot Status</span>
<Badge variant="default" className="bg-green-500">Online</Badge> <Badge variant="default" className="bg-green-500">
Online
</Badge>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-gray-400">Servers Connected</span> <span className="text-gray-400">
<span className="font-mono text-purple-400">12 servers</span> Servers Connected
</span>
<span className="font-mono text-purple-400">
12 servers
</span>
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-gray-400">Linked Accounts</span> <span className="text-gray-400">
<span className="font-mono text-purple-400">1,234 users</span> Linked Accounts
</span>
<span className="font-mono text-purple-400">
1,234 users
</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -55,7 +55,8 @@ export default function DiscordVerify() {
toastSuccess({ toastSuccess({
title: "Discord Linked!", 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 // Redirect to profile settings

View file

@ -514,7 +514,10 @@ export default function Onboarding() {
selectedRealm={data.creatorProfile.primaryArm || ""} selectedRealm={data.creatorProfile.primaryArm || ""}
onSelect={(realm) => onSelect={(realm) =>
updateData({ updateData({
creatorProfile: { ...data.creatorProfile, primaryArm: realm }, creatorProfile: {
...data.creatorProfile,
primaryArm: realm,
},
}) })
} }
onNext={nextStep} onNext={nextStep}

View file

@ -47,7 +47,8 @@ export default function RobloxCallback() {
const errorData = await response.json(); const errorData = await response.json();
toastError({ toastError({
title: "Authentication failed", title: "Authentication failed",
description: errorData.error || "Could not authenticate with Roblox", description:
errorData.error || "Could not authenticate with Roblox",
}); });
navigate("/login"); navigate("/login");
return; return;

View file

@ -60,7 +60,8 @@ export default function Web3Callback() {
if (user && data.success) { if (user && data.success) {
toastSuccess({ toastSuccess({
title: "Wallet linked", 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"); navigate("/dashboard?tab=connections");
return; return;
@ -93,11 +94,21 @@ export default function Web3Callback() {
if (account && !authLoading) { if (account && !authLoading) {
handleWeb3Auth(); handleWeb3Auth();
} }
}, [account, authLoading, signMessage, user, navigate, toastError, toastSuccess]); }, [
account,
authLoading,
signMessage,
user,
navigate,
toastError,
toastSuccess,
]);
return ( return (
<LoadingScreen <LoadingScreen
message={isProcessing ? "Verifying your wallet..." : "Connecting wallet..."} message={
isProcessing ? "Verifying your wallet..." : "Connecting wallet..."
}
showProgress={true} showProgress={true}
duration={5000} duration={5000}
/> />

View file

@ -12,6 +12,7 @@
### Step 1: Prepare the Bot Directory ### Step 1: Prepare the Bot Directory
Ensure all bot files are committed: Ensure all bot files are committed:
``` ```
code/discord-bot/ code/discord-bot/
├── bot.js ├── bot.js
@ -53,16 +54,19 @@ NODE_ENV=production
In Spaceship Application Settings: In Spaceship Application Settings:
**Build Command:** **Build Command:**
```bash ```bash
npm install npm install
``` ```
**Start Command:** **Start Command:**
```bash ```bash
npm start npm start
``` ```
**Root Directory:** **Root Directory:**
``` ```
code/discord-bot code/discord-bot
``` ```
@ -80,6 +84,7 @@ code/discord-bot
### Step 6: Verify Bot is Online ### Step 6: Verify Bot is Online
Once deployed: Once deployed:
1. Go to your Discord server 1. Go to your Discord server
2. Type `/verify` - the command autocomplete should appear 2. Type `/verify` - the command autocomplete should appear
3. Bot should be online with status "Listening to /verify to link your AeThex account" 3. Bot should be online with status "Listening to /verify to link your AeThex account"
@ -87,6 +92,7 @@ Once deployed:
## 📡 Discord Bot Endpoints ## 📡 Discord Bot Endpoints
The bot will be accessible at: The bot will be accessible at:
``` ```
https://<your-spaceship-domain>/ https://<your-spaceship-domain>/
``` ```
@ -96,11 +102,13 @@ The bot uses Discord's WebSocket connection (not HTTP), so it doesn't need to ex
## 🔌 API Integration ## 🔌 API Integration
Frontend calls to link Discord accounts: Frontend calls to link Discord accounts:
- **Endpoint:** `POST /api/discord/link` - **Endpoint:** `POST /api/discord/link`
- **Body:** `{ verification_code, user_id }` - **Body:** `{ verification_code, user_id }`
- **Response:** `{ success: true, message: "..." }` - **Response:** `{ success: true, message: "..." }`
Discord Verify page (`/discord-verify?code=XXX`) will automatically: Discord Verify page (`/discord-verify?code=XXX`) will automatically:
1. Call `/api/discord/link` with the verification code 1. Call `/api/discord/link` with the verification code
2. Link the Discord ID to the AeThex user account 2. Link the Discord ID to the AeThex user account
3. Redirect to dashboard on success 3. Redirect to dashboard on success
@ -108,22 +116,26 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 🛠️ Debugging ## 🛠️ Debugging
### Check bot logs on Spaceship: ### Check bot logs on Spaceship:
- Application → Logs - Application → Logs
- Filter for "bot.js" or "error" - Filter for "bot.js" or "error"
### Common issues: ### Common issues:
**"Discord bot not responding to commands"** **"Discord bot not responding to commands"**
- Check: `DISCORD_BOT_TOKEN` is correct - Check: `DISCORD_BOT_TOKEN` is correct
- Check: Bot is added to the Discord server with "applications.commands" scope - Check: Bot is added to the Discord server with "applications.commands" scope
- Check: Spaceship logs show "✅ Logged in" - Check: Spaceship logs show "✅ Logged in"
**"Supabase verification fails"** **"Supabase verification fails"**
- Check: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` are correct - Check: `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` are correct
- Check: `discord_links` and `discord_verifications` tables exist - Check: `discord_links` and `discord_verifications` tables exist
- Run migration: `code/supabase/migrations/20250107_add_discord_integration.sql` - Run migration: `code/supabase/migrations/20250107_add_discord_integration.sql`
**"Slash commands not appearing in Discord"** **"Slash commands not appearing in Discord"**
- Check: Logs show "✅ Successfully registered X slash commands" - Check: Logs show "✅ Successfully registered X slash commands"
- Discord may need 1-2 minutes to sync commands - Discord may need 1-2 minutes to sync commands
- Try typing `/` in Discord to force refresh - Try typing `/` in Discord to force refresh
@ -132,12 +144,14 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 📊 Monitoring ## 📊 Monitoring
### Key metrics to monitor: ### Key metrics to monitor:
- Bot uptime (should be 24/7) - Bot uptime (should be 24/7)
- Command usage (in Supabase) - Command usage (in Supabase)
- Verification code usage (in Supabase) - Verification code usage (in Supabase)
- Discord role sync success rate - Discord role sync success rate
### View in Admin Dashboard: ### View in Admin Dashboard:
- AeThex Admin Panel → Discord Management tab - AeThex Admin Panel → Discord Management tab
- Shows: - Shows:
- Bot status - Bot status
@ -156,6 +170,7 @@ Discord Verify page (`/discord-verify?code=XXX`) will automatically:
## 🆘 Support ## 🆘 Support
For issues: For issues:
1. Check Spaceship logs 1. Check Spaceship logs
2. Review `/api/discord/link` endpoint response 2. Review `/api/discord/link` endpoint response
3. Verify all environment variables are set correctly 3. Verify all environment variables are set correctly
@ -177,6 +192,7 @@ Run this migration on your AeThex Supabase:
## 🎉 You're All Set! ## 🎉 You're All Set!
Once deployed, users can: Once deployed, users can:
1. Click "Link Discord" in their profile settings 1. Click "Link Discord" in their profile settings
2. Type `/verify` in Discord 2. Type `/verify` in Discord
3. Click the verification link 3. Click the verification link

View file

@ -1,11 +1,20 @@
const { Client, GatewayIntentBits, REST, Routes, Collection, EmbedBuilder } = require('discord.js'); const {
const { createClient } = require('@supabase/supabase-js'); Client,
const fs = require('fs'); GatewayIntentBits,
const path = require('path'); REST,
require('dotenv').config(); 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 // Initialize Discord client
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages] }); const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.DirectMessages],
});
// Initialize Supabase // Initialize Supabase
const supabase = createClient( const supabase = createClient(
@ -17,35 +26,41 @@ const supabase = createClient(
client.commands = new Collection(); client.commands = new Collection();
// Load commands from commands directory // Load commands from commands directory
const commandsPath = path.join(__dirname, 'commands'); const commandsPath = path.join(__dirname, "commands");
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); const commandFiles = fs
.readdirSync(commandsPath)
.filter((file) => file.endsWith(".js"));
for (const file of commandFiles) { for (const file of commandFiles) {
const filePath = path.join(commandsPath, file); const filePath = path.join(commandsPath, file);
const command = require(filePath); 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); client.commands.set(command.data.name, command);
console.log(`✅ Loaded command: ${command.data.name}`); console.log(`✅ Loaded command: ${command.data.name}`);
} }
} }
// Bot ready event // Bot ready event
client.once('ready', () => { client.once("ready", () => {
console.log(`✅ Bot logged in as ${client.user.tag}`); console.log(`✅ Bot logged in as ${client.user.tag}`);
console.log(`📡 Listening in ${client.guilds.cache.size} server(s)`); console.log(`📡 Listening in ${client.guilds.cache.size} server(s)`);
// Set bot status // 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 // Slash command interaction handler
client.on('interactionCreate', async interaction => { client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const command = client.commands.get(interaction.commandName); const command = client.commands.get(interaction.commandName);
if (!command) { if (!command) {
console.warn(`⚠️ No command matching ${interaction.commandName} was found.`); console.warn(
`⚠️ No command matching ${interaction.commandName} was found.`,
);
return; return;
} }
@ -53,12 +68,12 @@ client.on('interactionCreate', async interaction => {
await command.execute(interaction, supabase, client); await command.execute(interaction, supabase, client);
} catch (error) { } catch (error) {
console.error(`❌ Error executing ${interaction.commandName}:`, error); console.error(`❌ Error executing ${interaction.commandName}:`, error);
const errorEmbed = new EmbedBuilder() const errorEmbed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Command Error') .setTitle("❌ Command Error")
.setDescription('There was an error while executing this command.') .setDescription("There was an error while executing this command.")
.setFooter({ text: 'Contact support if this persists' }); .setFooter({ text: "Contact support if this persists" });
if (interaction.replied || interaction.deferred) { if (interaction.replied || interaction.deferred) {
await interaction.followUp({ embeds: [errorEmbed], ephemeral: true }); await interaction.followUp({ embeds: [errorEmbed], ephemeral: true });
@ -76,7 +91,9 @@ async function registerCommands() {
commands.push(command.data.toJSON()); 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...`); console.log(`📝 Registering ${commands.length} slash commands...`);
@ -87,24 +104,24 @@ async function registerCommands() {
console.log(`✅ Successfully registered ${data.length} slash commands.`); console.log(`✅ Successfully registered ${data.length} slash commands.`);
} catch (error) { } catch (error) {
console.error('❌ Error registering commands:', error); console.error("❌ Error registering commands:", error);
} }
} }
// Login and register commands // Login and register commands
client.login(process.env.DISCORD_BOT_TOKEN); client.login(process.env.DISCORD_BOT_TOKEN);
client.once('ready', async () => { client.once("ready", async () => {
await registerCommands(); await registerCommands();
}); });
// Error handling // Error handling
process.on('unhandledRejection', error => { process.on("unhandledRejection", (error) => {
console.error('❌ Unhandled Promise Rejection:', error); console.error("❌ Unhandled Promise Rejection:", error);
}); });
process.on('uncaughtException', error => { process.on("uncaughtException", (error) => {
console.error('❌ Uncaught Exception:', error); console.error("❌ Uncaught Exception:", error);
process.exit(1); process.exit(1);
}); });

View file

@ -1,75 +1,92 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('profile') .setName("profile")
.setDescription('View your AeThex profile in Discord'), .setDescription("View your AeThex profile in Discord"),
async execute(interaction, supabase) { async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
try { try {
const { data: link } = await supabase const { data: link } = await supabase
.from('discord_links') .from("discord_links")
.select('user_id, primary_arm') .select("user_id, primary_arm")
.eq('discord_id', interaction.user.id) .eq("discord_id", interaction.user.id)
.single(); .single();
if (!link) { if (!link) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF6B6B) .setColor(0xff6b6b)
.setTitle('❌ Not Linked') .setTitle("❌ Not Linked")
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.'); .setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
const { data: profile } = await supabase const { data: profile } = await supabase
.from('user_profiles') .from("user_profiles")
.select('*') .select("*")
.eq('id', link.user_id) .eq("id", link.user_id)
.single(); .single();
if (!profile) { if (!profile) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF6B6B) .setColor(0xff6b6b)
.setTitle('❌ Profile Not Found') .setTitle("❌ Profile Not Found")
.setDescription('Your AeThex profile could not be found.'); .setDescription("Your AeThex profile could not be found.");
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
const armEmojis = { const armEmojis = {
labs: '🧪', labs: "🧪",
gameforge: '🎮', gameforge: "🎮",
corp: '💼', corp: "💼",
foundation: '🤝', foundation: "🤝",
devlink: '💻', devlink: "💻",
}; };
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x7289DA) .setColor(0x7289da)
.setTitle(`${profile.full_name || 'AeThex User'}'s Profile`) .setTitle(`${profile.full_name || "AeThex User"}'s Profile`)
.setThumbnail(profile.avatar_url || 'https://aethex.dev/placeholder.svg') .setThumbnail(
.addFields( profile.avatar_url || "https://aethex.dev/placeholder.svg",
{ 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 },
) )
.addFields( .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] }); await interaction.editReply({ embeds: [embed] });
} catch (error) { } catch (error) {
console.error('Profile command error:', error); console.error("Profile command error:", error);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Error') .setTitle("❌ Error")
.setDescription('Failed to fetch profile. Please try again.'); .setDescription("Failed to fetch profile. Please try again.");
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }
}, },

View file

@ -1,42 +1,61 @@
const { SlashCommandBuilder, EmbedBuilder, StringSelectMenuBuilder, ActionRowBuilder } = require('discord.js'); const {
SlashCommandBuilder,
EmbedBuilder,
StringSelectMenuBuilder,
ActionRowBuilder,
} = require("discord.js");
const REALMS = [ const REALMS = [
{ value: 'labs', label: '🧪 Labs', description: 'Research & Development' }, { value: "labs", label: "🧪 Labs", description: "Research & Development" },
{ value: 'gameforge', label: '🎮 GameForge', description: 'Game Development' }, {
{ value: 'corp', label: '💼 Corp', description: 'Enterprise Solutions' }, value: "gameforge",
{ value: 'foundation', label: '🤝 Foundation', description: 'Community & Education' }, label: "🎮 GameForge",
{ value: 'devlink', label: '💻 Dev-Link', description: 'Professional Networking' }, 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 = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('set-realm') .setName("set-realm")
.setDescription('Set your primary AeThex realm/arm'), .setDescription("Set your primary AeThex realm/arm"),
async execute(interaction, supabase) { async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
try { try {
const { data: link } = await supabase const { data: link } = await supabase
.from('discord_links') .from("discord_links")
.select('user_id, primary_arm') .select("user_id, primary_arm")
.eq('discord_id', interaction.user.id) .eq("discord_id", interaction.user.id)
.single(); .single();
if (!link) { if (!link) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF6B6B) .setColor(0xff6b6b)
.setTitle('❌ Not Linked') .setTitle("❌ Not Linked")
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.'); .setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
const select = new StringSelectMenuBuilder() const select = new StringSelectMenuBuilder()
.setCustomId('select_realm') .setCustomId("select_realm")
.setPlaceholder('Choose your primary realm') .setPlaceholder("Choose your primary realm")
.addOptions( .addOptions(
REALMS.map(realm => ({ REALMS.map((realm) => ({
label: realm.label, label: realm.label,
description: realm.description, description: realm.description,
value: realm.value, value: realm.value,
@ -47,48 +66,60 @@ module.exports = {
const row = new ActionRowBuilder().addComponents(select); const row = new ActionRowBuilder().addComponents(select);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x7289DA) .setColor(0x7289da)
.setTitle('⚔️ Choose Your Realm') .setTitle("⚔️ Choose Your Realm")
.setDescription('Select your primary AeThex realm. This determines your main Discord role.') .setDescription(
.addFields( "Select your primary AeThex realm. This determines your main Discord role.",
{ name: 'Current Realm', value: link.primary_arm || 'Not set' }, )
); .addFields({
name: "Current Realm",
value: link.primary_arm || "Not set",
});
await interaction.editReply({ embeds: [embed], components: [row] }); await interaction.editReply({ embeds: [embed], components: [row] });
const filter = i => i.user.id === interaction.user.id && i.customId === 'select_realm'; const filter = (i) =>
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 }); 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]; const selectedRealm = i.values[0];
await supabase await supabase
.from('discord_links') .from("discord_links")
.update({ primary_arm: selectedRealm }) .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() const confirmEmbed = new EmbedBuilder()
.setColor(0x00FF00) .setColor(0x00ff00)
.setTitle('✅ Realm Set') .setTitle("✅ Realm Set")
.setDescription(`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`); .setDescription(
`Your primary realm is now **${realm.label}**\n\nYou'll be assigned the corresponding Discord role.`,
);
await i.update({ embeds: [confirmEmbed], components: [] }); await i.update({ embeds: [confirmEmbed], components: [] });
}); });
collector.on('end', collected => { collector.on("end", (collected) => {
if (collected.size === 0) { if (collected.size === 0) {
interaction.editReply({ content: 'Realm selection timed out.', components: [] }); interaction.editReply({
content: "Realm selection timed out.",
components: [],
});
} }
}); });
} catch (error) { } catch (error) {
console.error('Set-realm command error:', error); console.error("Set-realm command error:", error);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Error') .setTitle("❌ Error")
.setDescription('Failed to update realm. Please try again.'); .setDescription("Failed to update realm. Please try again.");
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }
}, },

View file

@ -1,48 +1,49 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('unlink') .setName("unlink")
.setDescription('Unlink your Discord account from AeThex'), .setDescription("Unlink your Discord account from AeThex"),
async execute(interaction, supabase) { async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
try { try {
const { data: link } = await supabase const { data: link } = await supabase
.from('discord_links') .from("discord_links")
.select('*') .select("*")
.eq('discord_id', interaction.user.id) .eq("discord_id", interaction.user.id)
.single(); .single();
if (!link) { if (!link) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF6B6B) .setColor(0xff6b6b)
.setTitle(' Not Linked') .setTitle(" Not Linked")
.setDescription('Your Discord account is not linked to AeThex.'); .setDescription("Your Discord account is not linked to AeThex.");
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
// Delete the link // Delete the link
await supabase await supabase
.from('discord_links') .from("discord_links")
.delete() .delete()
.eq('discord_id', interaction.user.id); .eq("discord_id", interaction.user.id);
// Remove Discord roles from user // Remove Discord roles from user
const guild = interaction.guild; const guild = interaction.guild;
const member = await guild.members.fetch(interaction.user.id); const member = await guild.members.fetch(interaction.user.id);
// Find and remove all AeThex-related roles // Find and remove all AeThex-related roles
const rolesToRemove = member.roles.cache.filter(role => const rolesToRemove = member.roles.cache.filter(
role.name.includes('Labs') || (role) =>
role.name.includes('GameForge') || role.name.includes("Labs") ||
role.name.includes('Corp') || role.name.includes("GameForge") ||
role.name.includes('Foundation') || role.name.includes("Corp") ||
role.name.includes('Dev-Link') || role.name.includes("Foundation") ||
role.name.includes('Premium') || role.name.includes("Dev-Link") ||
role.name.includes('Creator') role.name.includes("Premium") ||
role.name.includes("Creator"),
); );
for (const [, role] of rolesToRemove) { for (const [, role] of rolesToRemove) {
@ -54,18 +55,20 @@ module.exports = {
} }
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x00FF00) .setColor(0x00ff00)
.setTitle('✅ Account Unlinked') .setTitle("✅ Account Unlinked")
.setDescription('Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.'); .setDescription(
"Your Discord account has been unlinked from AeThex.\nAll associated roles have been removed.",
);
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} catch (error) { } catch (error) {
console.error('Unlink command error:', error); console.error("Unlink command error:", error);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Error') .setTitle("❌ Error")
.setDescription('Failed to unlink account. Please try again.'); .setDescription("Failed to unlink account. Please try again.");
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }
}, },

View file

@ -1,71 +1,96 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js'); const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('verify-role') .setName("verify-role")
.setDescription('Check your AeThex-assigned Discord roles'), .setDescription("Check your AeThex-assigned Discord roles"),
async execute(interaction, supabase) { async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
try { try {
const { data: link } = await supabase const { data: link } = await supabase
.from('discord_links') .from("discord_links")
.select('user_id, primary_arm') .select("user_id, primary_arm")
.eq('discord_id', interaction.user.id) .eq("discord_id", interaction.user.id)
.single(); .single();
if (!link) { if (!link) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF6B6B) .setColor(0xff6b6b)
.setTitle('❌ Not Linked') .setTitle("❌ Not Linked")
.setDescription('You must link your Discord account to AeThex first.\nUse `/verify` to get started.'); .setDescription(
"You must link your Discord account to AeThex first.\nUse `/verify` to get started.",
);
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
const { data: profile } = await supabase const { data: profile } = await supabase
.from('user_profiles') .from("user_profiles")
.select('user_type') .select("user_type")
.eq('id', link.user_id) .eq("id", link.user_id)
.single(); .single();
const { data: mappings } = await supabase const { data: mappings } = await supabase
.from('discord_role_mappings') .from("discord_role_mappings")
.select('discord_role') .select("discord_role")
.eq('arm', link.primary_arm) .eq("arm", link.primary_arm)
.eq('user_type', profile?.user_type || 'community_member'); .eq("user_type", profile?.user_type || "community_member");
const member = await interaction.guild.members.fetch(interaction.user.id); const member = await interaction.guild.members.fetch(interaction.user.id);
const aethexRoles = member.roles.cache.filter(role => const aethexRoles = member.roles.cache.filter(
role.name.includes('Labs') || (role) =>
role.name.includes('GameForge') || role.name.includes("Labs") ||
role.name.includes('Corp') || role.name.includes("GameForge") ||
role.name.includes('Foundation') || role.name.includes("Corp") ||
role.name.includes('Dev-Link') || role.name.includes("Foundation") ||
role.name.includes('Premium') || role.name.includes("Dev-Link") ||
role.name.includes('Creator') role.name.includes("Premium") ||
role.name.includes("Creator"),
); );
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x7289DA) .setColor(0x7289da)
.setTitle('🔐 Your AeThex Roles') .setTitle("🔐 Your AeThex Roles")
.addFields( .addFields(
{ name: '⚔️ Primary Realm', value: link.primary_arm || 'Not set', inline: true }, {
{ name: '👤 User Type', value: profile?.user_type || 'community_member', inline: true }, name: "⚔️ Primary Realm",
{ name: '🎭 Discord Roles', value: aethexRoles.size > 0 ? aethexRoles.map(r => r.name).join(', ') : 'None assigned yet' }, value: link.primary_arm || "Not set",
{ name: '📋 Expected Roles', value: mappings?.length > 0 ? mappings.map(m => m.discord_role).join(', ') : 'No mappings found' }, 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] }); await interaction.editReply({ embeds: [embed] });
} catch (error) { } catch (error) {
console.error('Verify-role command error:', error); console.error("Verify-role command error:", error);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Error') .setTitle("❌ Error")
.setDescription('Failed to verify roles. Please try again.'); .setDescription("Failed to verify roles. Please try again.");
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }
}, },

View file

@ -1,35 +1,46 @@
const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); const {
SlashCommandBuilder,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
module.exports = { module.exports = {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('verify') .setName("verify")
.setDescription('Link your Discord account to your AeThex account'), .setDescription("Link your Discord account to your AeThex account"),
async execute(interaction, supabase) { async execute(interaction, supabase) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
try { try {
const { data: existingLink } = await supabase const { data: existingLink } = await supabase
.from('discord_links') .from("discord_links")
.select('*') .select("*")
.eq('discord_id', interaction.user.id) .eq("discord_id", interaction.user.id)
.single(); .single();
if (existingLink) { if (existingLink) {
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x00FF00) .setColor(0x00ff00)
.setTitle('✅ Already Linked') .setTitle("✅ Already Linked")
.setDescription(`Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`); .setDescription(
`Your Discord account is already linked to AeThex (User ID: ${existingLink.user_id})`,
);
return await interaction.editReply({ embeds: [embed] }); return await interaction.editReply({ embeds: [embed] });
} }
// Generate verification code // 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 const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
// Store verification code // Store verification code
await supabase.from('discord_verifications').insert({ await supabase.from("discord_verifications").insert({
discord_id: interaction.user.id, discord_id: interaction.user.id,
verification_code: verificationCode, verification_code: verificationCode,
expires_at: expiresAt.toISOString(), expires_at: expiresAt.toISOString(),
@ -38,30 +49,34 @@ module.exports = {
const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`; const verifyUrl = `https://aethex.dev/discord-verify?code=${verificationCode}`;
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0x7289DA) .setColor(0x7289da)
.setTitle('🔗 Link Your AeThex Account') .setTitle("🔗 Link Your AeThex Account")
.setDescription('Click the button below to link your Discord account to AeThex.') .setDescription(
.addFields( "Click the button below to link your Discord account to AeThex.",
{ name: '⏱️ Expires In', value: '15 minutes' },
{ name: '📝 Verification Code', value: `\`${verificationCode}\`` },
) )
.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( const row = new ActionRowBuilder().addComponents(
new ButtonBuilder() new ButtonBuilder()
.setLabel('Link Account') .setLabel("Link Account")
.setStyle(ButtonStyle.Link) .setStyle(ButtonStyle.Link)
.setURL(verifyUrl), .setURL(verifyUrl),
); );
await interaction.editReply({ embeds: [embed], components: [row] }); await interaction.editReply({ embeds: [embed], components: [row] });
} catch (error) { } catch (error) {
console.error('Verify command error:', error); console.error("Verify command error:", error);
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(0xFF0000) .setColor(0xff0000)
.setTitle('❌ Error') .setTitle("❌ Error")
.setDescription('Failed to generate verification code. Please try again.'); .setDescription(
"Failed to generate verification code. Please try again.",
);
await interaction.editReply({ embeds: [embed] }); await interaction.editReply({ embeds: [embed] });
} }
}, },

View file

@ -16,6 +16,7 @@ This guide covers how to integrate AeThex authentication with game engines: Robl
## Overview ## Overview
AeThex provides a unified authentication system for games to: AeThex provides a unified authentication system for games to:
- Authenticate players across different platforms - Authenticate players across different platforms
- Link player accounts to Roblox, Ethereum wallets, and other providers - Link player accounts to Roblox, Ethereum wallets, and other providers
- Store player profiles and achievements - Store player profiles and achievements
@ -53,13 +54,13 @@ function AethexAuth:authenticate(playerId, playerName)
player_name = playerName, player_name = playerName,
platform = "PC" platform = "PC"
}) })
local response = game:GetService("HttpService"):PostAsync( local response = game:GetService("HttpService"):PostAsync(
API_BASE .. "/games/game-auth", API_BASE .. "/games/game-auth",
body, body,
Enum.HttpContentType.ApplicationJson Enum.HttpContentType.ApplicationJson
) )
local data = game:GetService("HttpService"):JSONDecode(response) local data = game:GetService("HttpService"):JSONDecode(response)
return data return data
end end
@ -73,7 +74,7 @@ function AethexAuth:verifyToken(sessionToken)
}), }),
Enum.HttpContentType.ApplicationJson Enum.HttpContentType.ApplicationJson
) )
return game:GetService("HttpService"):JSONDecode(response) return game:GetService("HttpService"):JSONDecode(response)
end end
@ -88,7 +89,7 @@ local AethexAuth = require(game.ServerScriptService:WaitForChild("AethexAuth"))
Players.PlayerAdded:Connect(function(player) Players.PlayerAdded:Connect(function(player)
local authResult = AethexAuth:authenticate(player.UserId, player.Name) local authResult = AethexAuth:authenticate(player.UserId, player.Name)
if authResult.success then if authResult.success then
player:SetAttribute("AethexSessionToken", authResult.session_token) player:SetAttribute("AethexSessionToken", authResult.session_token)
player:SetAttribute("AethexUserId", authResult.user_id) player:SetAttribute("AethexUserId", authResult.user_id)
@ -118,7 +119,7 @@ using System.Collections;
public class AethexAuth : MonoBehaviour public class AethexAuth : MonoBehaviour
{ {
private const string API_BASE = "https://aethex.dev/api"; private const string API_BASE = "https://aethex.dev/api";
[System.Serializable] [System.Serializable]
public class AuthResponse public class AuthResponse
{ {
@ -129,7 +130,7 @@ public class AethexAuth : MonoBehaviour
public int expires_in; public int expires_in;
public string error; public string error;
} }
public static IEnumerator AuthenticatePlayer( public static IEnumerator AuthenticatePlayer(
string playerId, string playerId,
string playerName, string playerName,
@ -139,7 +140,7 @@ public class AethexAuth : MonoBehaviour
$"{API_BASE}/games/game-auth", $"{API_BASE}/games/game-auth",
"POST" "POST"
); );
var requestBody = new AuthRequest var requestBody = new AuthRequest
{ {
game = "unity", game = "unity",
@ -147,14 +148,14 @@ public class AethexAuth : MonoBehaviour
player_name = playerName, player_name = playerName,
platform = "PC" platform = "PC"
}; };
string jsonBody = JsonUtility.ToJson(requestBody); string jsonBody = JsonUtility.ToJson(requestBody);
request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody)); request.uploadHandler = new UploadHandlerRaw(System.Text.Encoding.UTF8.GetBytes(jsonBody));
request.downloadHandler = new DownloadHandlerBuffer(); request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json"); request.SetRequestHeader("Content-Type", "application/json");
yield return request.SendWebRequest(); yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success) if (request.result == UnityWebRequest.Result.Success)
{ {
var response = JsonUtility.FromJson<AuthResponse>(request.downloadHandler.text); var response = JsonUtility.FromJson<AuthResponse>(request.downloadHandler.text);
@ -165,7 +166,7 @@ public class AethexAuth : MonoBehaviour
callback(new AuthResponse { error = request.error }); callback(new AuthResponse { error = request.error });
} }
} }
[System.Serializable] [System.Serializable]
private class AuthRequest private class AuthRequest
{ {
@ -186,10 +187,10 @@ public class GameManager : MonoBehaviour
{ {
string playerId = SystemInfo.deviceUniqueIdentifier; string playerId = SystemInfo.deviceUniqueIdentifier;
string playerName = "UnityPlayer_" + Random.Range(1000, 9999); string playerName = "UnityPlayer_" + Random.Range(1000, 9999);
StartCoroutine(AethexAuth.AuthenticatePlayer(playerId, playerName, OnAuthComplete)); StartCoroutine(AethexAuth.AuthenticatePlayer(playerId, playerName, OnAuthComplete));
} }
void OnAuthComplete(AethexAuth.AuthResponse response) void OnAuthComplete(AethexAuth.AuthResponse response)
{ {
if (response.success) if (response.success)
@ -231,12 +232,12 @@ public:
int32 ExpiresIn; int32 ExpiresIn;
FString Error; FString Error;
}; };
static void AuthenticatePlayer( static void AuthenticatePlayer(
const FString& PlayerId, const FString& PlayerId,
const FString& PlayerName, const FString& PlayerName,
TFunction<void(const FAuthResponse&)> OnComplete); TFunction<void(const FAuthResponse&)> OnComplete);
private: private:
static void OnAuthResponse( static void OnAuthResponse(
FHttpRequestPtr Request, FHttpRequestPtr Request,
@ -259,30 +260,30 @@ void FAethexAuth::AuthenticatePlayer(
TFunction<void(const FAuthResponse&)> OnComplete) TFunction<void(const FAuthResponse&)> OnComplete)
{ {
FHttpModule& HttpModule = FHttpModule::Get(); FHttpModule& HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request =
HttpModule.CreateRequest(); HttpModule.CreateRequest();
Request->SetURL(TEXT("https://aethex.dev/api/games/game-auth")); Request->SetURL(TEXT("https://aethex.dev/api/games/game-auth"));
Request->SetVerb(TEXT("POST")); Request->SetVerb(TEXT("POST"));
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
// Create JSON body // Create JSON body
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject()); TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
JsonObject->SetStringField(TEXT("game"), TEXT("unreal")); JsonObject->SetStringField(TEXT("game"), TEXT("unreal"));
JsonObject->SetStringField(TEXT("player_id"), PlayerId); JsonObject->SetStringField(TEXT("player_id"), PlayerId);
JsonObject->SetStringField(TEXT("player_name"), PlayerName); JsonObject->SetStringField(TEXT("player_name"), PlayerName);
JsonObject->SetStringField(TEXT("platform"), TEXT("PC")); JsonObject->SetStringField(TEXT("platform"), TEXT("PC"));
FString OutputString; FString OutputString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString); TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
Request->SetContentAsString(OutputString); Request->SetContentAsString(OutputString);
Request->OnProcessRequestComplete().BindStatic( Request->OnProcessRequestComplete().BindStatic(
&FAethexAuth::OnAuthResponse, &FAethexAuth::OnAuthResponse,
OnComplete); OnComplete);
Request->ProcessRequest(); Request->ProcessRequest();
} }
@ -293,12 +294,12 @@ void FAethexAuth::OnAuthResponse(
TFunction<void(const FAuthResponse&)> OnComplete) TFunction<void(const FAuthResponse&)> OnComplete)
{ {
FAuthResponse AuthResponse; FAuthResponse AuthResponse;
if (bWasSuccessful && Response.IsValid()) if (bWasSuccessful && Response.IsValid())
{ {
TSharedPtr<FJsonObject> JsonObject; TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid()) if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{ {
AuthResponse.bSuccess = JsonObject->GetBoolField(TEXT("success")); AuthResponse.bSuccess = JsonObject->GetBoolField(TEXT("success"));
@ -312,7 +313,7 @@ void FAethexAuth::OnAuthResponse(
{ {
AuthResponse.Error = TEXT("Authentication request failed"); AuthResponse.Error = TEXT("Authentication request failed");
} }
OnComplete(AuthResponse); OnComplete(AuthResponse);
} }
``` ```
@ -331,68 +332,68 @@ const API_BASE = "https://aethex.dev/api"
func authenticate_player(player_id: String, player_name: String) -> Dictionary: func authenticate_player(player_id: String, player_name: String) -> Dictionary:
var http_request = HTTPRequest.new() var http_request = HTTPRequest.new()
add_child(http_request) add_child(http_request)
var url = API_BASE + "/games/game-auth" var url = API_BASE + "/games/game-auth"
var headers = ["Content-Type: application/json"] var headers = ["Content-Type: application/json"]
var body = { var body = {
"game": "godot", "game": "godot",
"player_id": player_id, "player_id": player_id,
"player_name": player_name, "player_name": player_name,
"platform": OS.get_name() "platform": OS.get_name()
} }
var response = http_request.request( var response = http_request.request(
url, url,
headers, headers,
HTTPClient.METHOD_POST, HTTPClient.METHOD_POST,
JSON.stringify(body) JSON.stringify(body)
) )
if response != OK: if response != OK:
return {"error": "Request failed"} return {"error": "Request failed"}
var result = await http_request.request_completed var result = await http_request.request_completed
if result[1] != 200: if result[1] != 200:
return {"error": "Authentication failed"} return {"error": "Authentication failed"}
var response_data = JSON.parse_string(result[3].get_string_from_utf8()) var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free() http_request.queue_free()
return response_data return response_data
func verify_token(session_token: String) -> Dictionary: func verify_token(session_token: String) -> Dictionary:
var http_request = HTTPRequest.new() var http_request = HTTPRequest.new()
add_child(http_request) add_child(http_request)
var url = API_BASE + "/games/verify-token" var url = API_BASE + "/games/verify-token"
var headers = ["Content-Type: application/json"] var headers = ["Content-Type: application/json"]
var body = { var body = {
"session_token": session_token, "session_token": session_token,
"game": "godot" "game": "godot"
} }
var response = http_request.request( var response = http_request.request(
url, url,
headers, headers,
HTTPClient.METHOD_POST, HTTPClient.METHOD_POST,
JSON.stringify(body) JSON.stringify(body)
) )
var result = await http_request.request_completed var result = await http_request.request_completed
var response_data = JSON.parse_string(result[3].get_string_from_utf8()) var response_data = JSON.parse_string(result[3].get_string_from_utf8())
http_request.queue_free() http_request.queue_free()
return response_data return response_data
func _ready(): func _ready():
var player_id = OS.get_unique_id() var player_id = OS.get_unique_id()
var player_name = "GodotPlayer_" + str(randi_range(1000, 9999)) var player_name = "GodotPlayer_" + str(randi_range(1000, 9999))
var auth_result = await authenticate_player(player_id, player_name) var auth_result = await authenticate_player(player_id, player_name)
if auth_result.has("success") and auth_result["success"]: if auth_result.has("success") and auth_result["success"]:
print("Authenticated as: ", auth_result["username"]) print("Authenticated as: ", auth_result["username"])
# Store token # Store token
@ -413,27 +414,29 @@ func _ready():
Authenticate a game player and create a session. Authenticate a game player and create a session.
**Request:** **Request:**
```json ```json
{ {
"game": "unity|unreal|godot|roblox|custom", "game": "unity|unreal|godot|roblox|custom",
"player_id": "unique-player-id", "player_id": "unique-player-id",
"player_name": "player-display-name", "player_name": "player-display-name",
"device_id": "optional-device-id", "device_id": "optional-device-id",
"platform": "PC|Mobile|Console" "platform": "PC|Mobile|Console"
} }
``` ```
**Response:** **Response:**
```json ```json
{ {
"success": true, "success": true,
"session_token": "token-string", "session_token": "token-string",
"user_id": "uuid", "user_id": "uuid",
"username": "username", "username": "username",
"game": "unity", "game": "unity",
"expires_in": 604800, "expires_in": 604800,
"api_base_url": "https://aethex.dev/api", "api_base_url": "https://aethex.dev/api",
"docs_url": "https://docs.aethex.dev/game-integration" "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. Verify a game session token and get player data.
**Request:** **Request:**
```json ```json
{ {
"session_token": "token-string", "session_token": "token-string",
"game": "unity" "game": "unity"
} }
``` ```
**Response:** **Response:**
```json ```json
{ {
"valid": true, "valid": true,
"user_id": "uuid", "user_id": "uuid",
"username": "username", "username": "username",
"email": "user@example.com", "email": "user@example.com",
"full_name": "Player Name", "full_name": "Player Name",
"game": "unity", "game": "unity",
"platform": "PC", "platform": "PC",
"expires_at": "2025-01-15T10:30:00Z" "expires_at": "2025-01-15T10:30:00Z"
} }
``` ```
@ -475,6 +480,7 @@ Verify a game session token and get player data.
## Support ## Support
For issues or questions, visit: For issues or questions, visit:
- Docs: https://docs.aethex.dev - Docs: https://docs.aethex.dev
- Discord: https://discord.gg/aethex - Discord: https://discord.gg/aethex
- Email: support@aethex.tech - Email: support@aethex.tech