Enhance Discord authentication and guild management by implementing caching for user and guild data to reduce API calls and improve performance. Update error handling for Discord API responses and refactor the guilds listing logic in the dashboard to streamline data retrieval. Adjust retry logic for rate limits and improve session error handling in the authentication flow.
Some checks failed
CI / Bot (Python) (push) Successful in 12s
CI / Dashboard (Next.js) (push) Failing after 9s

This commit is contained in:
TheOnlyMace
2026-07-21 22:33:30 +02:00
parent 60390009d1
commit f15c869993
6 changed files with 131 additions and 73 deletions

View File

@@ -51,46 +51,23 @@ export default async function GuildsPage() {
redirect("/");
}
let botGuilds: GuildSummary[] = [];
let userGuilds: any[] = [];
let userDiscordError: string | null = null;
// Bot API already filters to guilds the user can manage — no second Discord @me/guilds call
let guilds: GuildSummary[] = [];
let botError: string | null = null;
try {
botGuilds = await api.listGuilds();
guilds = await api.listGuilds();
} catch (err: any) {
console.error("Failed to fetch bot guilds:", err);
botError = err.message || "Failed to load bot servers.";
}
try {
const { fetchUserGuilds } = await import("@/lib/guild-auth");
userGuilds = await fetchUserGuilds(session.accessToken);
} catch (err: any) {
console.error("Discord API Error:", err);
userDiscordError = err?.message || "Error connecting to Discord.";
}
const MANAGE_GUILD = BigInt(0x20);
const ADMINISTRATOR = BigInt(0x8);
const adminUserGuilds = userGuilds.filter((g) => {
try {
const perms = BigInt(g.permissions);
return (
(perms & ADMINISTRATOR) === ADMINISTRATOR ||
(perms & MANAGE_GUILD) === MANAGE_GUILD ||
g.owner === true
);
} catch {
return g.owner === true;
const msg = typeof err?.message === "string" ? err.message : "Failed to load bot servers.";
botError = msg;
// Expired OAuth — force re-login
if (err?.status === 401 || /expired|sign in again/i.test(msg)) {
redirect("/");
}
});
const adminGuildIds = new Set(adminUserGuilds.map((g) => String(g.id)));
const guilds = botGuilds.filter((g) => adminGuildIds.has(String(g.id)));
}
const inviteConfigured = Boolean(getBotInviteUrl());
// Hard failure only when bot API is down — Discord sync issues still show invite CTA
const hardError = botError;
return (
@@ -132,11 +109,6 @@ export default async function GuildsPage() {
Invite Axiom to a Discord server where you have <span className="text-slate-200">Manage Server</span>{" "}
permissions, then come back here to configure it.
</p>
{userDiscordError && (
<p className="text-amber-400/90 text-sm mt-4 max-w-md mx-auto">
Note: {userDiscordError}
</p>
)}
<div className="mt-8 flex flex-col sm:flex-row gap-3 justify-center items-center">
{inviteConfigured ? (
<AddBotButton className="px-8 h-12 text-base shadow-lg shadow-primary/25" />

View File

@@ -79,8 +79,14 @@ export const authOptions: AuthOptions = {
}
const expiresAt = typeof token.expiresAt === "number" ? token.expiresAt : 0;
// Refresh ~60s before expiry
if (Date.now() < expiresAt * 1000 - 60_000) {
// Refresh when expired, missing expiry, or previous refresh failed
const stillValid =
Boolean(token.accessToken) &&
expiresAt > 0 &&
Date.now() < expiresAt * 1000 - 60_000 &&
!token.error;
if (stillValid) {
return token;
}
@@ -100,6 +106,11 @@ export const authOptions: AuthOptions = {
// @ts-ignore
session.error = token.error;
}
// Surface refresh failures so pages can redirect to sign-in
if (token.error) {
// @ts-ignore
session.error = token.error;
}
return session;
},
},

View File

@@ -14,8 +14,8 @@ export interface DiscordGuild {
const MANAGE_GUILD = BigInt(0x20);
const ADMINISTRATOR = BigInt(0x8);
const CACHE_TTL_MS = 60_000;
const MAX_RETRIES = 3;
const CACHE_TTL_MS = 90_000;
const MAX_RETRIES = 4;
type CacheEntry = { guilds: DiscordGuild[]; expiresAt: number };
@@ -70,19 +70,19 @@ async function fetchUserGuildsUncached(accessToken: string): Promise<DiscordGuil
const header = res.headers.get("retry-after");
retryAfter = header ? Number(header) : 1;
}
const waitMs = Math.min(Math.max(retryAfter * 1000, 300), 5000);
const waitMs = Math.min(Math.max(retryAfter * 1000, 500), 15_000);
console.warn(`Discord guilds rate-limited — retry in ${waitMs}ms (attempt ${attempt}/${MAX_RETRIES})`);
await new Promise((r) => setTimeout(r, waitMs));
continue;
}
if (res.status === 401 || res.status === 403) {
throw new Error("Discord session expired — please sign in again.");
}
const body = await res.text().catch(() => "");
console.error("Discord guilds fetch failed:", res.status, body);
throw new Error(
res.status === 401
? "Discord session expired — please sign in again."
: `Failed to fetch your Discord servers. (${res.status})`
);
throw new Error(`Failed to fetch your Discord servers. (${res.status})`);
}
throw new Error("Failed to fetch your Discord servers. (rate limited)");