Files

258 lines
9.4 KiB
TypeScript

/**
* ╔══════════════════════════════════════════════════════════════════╗
* ║ ║
* ║ +-+-+-+-+-+-+-+-+ ║
* ║ |H|e|x|a|H|o|s|t| ║
* ║ +-+-+-+-+-+-+-+-+ ║
* ║ ║
* ║ © 2026 HexaHost — All Rights Reserved ║
* ║ ║
* ║ discord ── https://discord.gg/hexahost ║
* ║ github ── https://github.com/theoneandonlymace ║
* ║ ║
* ╚══════════════════════════════════════════════════════════════════╝
*/
import {
BotInfo,
BotStatus,
GuildSummary,
GuildDetails,
PrefixConfig,
AutomodConfig,
TicketConfig,
LevelingConfig,
LoggingConfig,
PrefixUpdate,
AutomodUpdate,
LevelingUpdate,
LoggingUpdate,
LeaderboardEntry,
DiscordChannel,
DiscordRole,
AutoRoleConfig,
AutoRoleUpdate,
AdminStats,
AdminConfig,
AdminConfigUpdate
} from "@/types/api";
const isServer = typeof window === "undefined";
/** Server: direct bot API with secret key. Browser: same-origin proxy route. */
const BASE_URL = isServer
? (process.env.DASHBOARD_API_URL ||
process.env.NEXT_PUBLIC_API_URL ||
"http://127.0.0.1:8000/api/v1")
: "/api/proxy";
class ApiError extends Error {
constructor(public status: number, message: string) {
super(message);
this.name = "ApiError";
}
}
async function request<T>(
endpoint: string,
options: RequestInit & { next?: NextFetchRequestConfig } = {}
): Promise<T> {
const url = `${BASE_URL}${endpoint}`;
const headers = new Headers(options.headers);
if (isServer) {
const apiKey = process.env.DASHBOARD_API_KEY;
if (apiKey) {
headers.set("Authorization", `Bearer ${apiKey}`);
}
const { getServerSession } = await import("next-auth/next");
const { authOptions } = await import("@/lib/auth");
const session = await getServerSession(authOptions);
if (session?.accessToken) {
headers.set("X-Discord-Access-Token", session.accessToken);
}
if (session?.user?.id) {
headers.set("X-Discord-User-Id", session.user.id);
}
}
headers.set("Content-Type", "application/json");
try {
const response = await fetch(url, {
...options,
headers,
// CRITICAL: Never cache mutation responses. For GETs, use revalidate: 0
// to always fetch fresh data from the bot API. Caching was causing
// saved data to "disappear" on reload because Next.js served stale cache.
next: options.next || { revalidate: 0 },
});
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch {
errorData = { detail: "An unknown error occurred" };
}
console.error(`[API HTTP Error] Status ${response.status} for ${url}:`, errorData);
throw new ApiError(response.status, errorData.detail || response.statusText);
}
return response.json();
} catch (error) {
console.error(`[API Network/Fetch Error] Failed to fetch ${url}:`, error);
throw error;
}
}
export const api = {
// Bot
getBotStatus: () => request<BotStatus>("/bot/status"),
getBotInfo: () => request<BotInfo>("/bot/info"),
// Guilds
listGuilds: () => request<GuildSummary[]>("/guilds/"),
getGuildDetails: (guildId: string) => request<any>(`/guilds/${guildId}`),
getChannels: (guildId: string) => request<DiscordChannel[]>(`/guilds/${guildId}/channels`),
getRoles: (guildId: string) => request<DiscordRole[]>(`/guilds/${guildId}/roles`),
// Module Configs
getPrefix: (guildId: string) => request<PrefixConfig>(`/guilds/${guildId}/prefix`),
updatePrefix: (guildId: string, prefix: string) =>
request<{ status: string; new_prefix: string }>(`/guilds/${guildId}/prefix`, {
method: "POST",
body: JSON.stringify({ prefix }),
}),
getAutomod: (guildId: string) => request<AutomodConfig>(`/guilds/${guildId}/automod`),
updateAutomod: (guildId: string, data: AutomodUpdate) =>
request<{ status: string }>(`/guilds/${guildId}/automod`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getTickets: (guildId: string) => request<TicketConfig>(`/guilds/${guildId}/tickets`),
updateTickets: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/tickets`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getLeveling: (guildId: string) => request<LevelingConfig>(`/guilds/${guildId}/leveling`),
updateLeveling: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/leveling`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getLogging: (guildId: string) => request<LoggingConfig>(`/guilds/${guildId}/logging`),
updateLogging: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/logging`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getLeaderboard: (guildId: string) => request<LeaderboardEntry[]>(`/guilds/${guildId}/leveling/leaderboard`),
getWelcome: (guildId: string) => request<any>(`/guilds/${guildId}/welcome`),
updateWelcome: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/welcome`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getAntiNuke: (guildId: string) => request<any>(`/guilds/${guildId}/antinuke`),
updateAntiNuke: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/antinuke`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getVerification: (guildId: string) => request<any>(`/guilds/${guildId}/verification`),
updateVerification: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/verification`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getVanityRoles: (guildId: string) => request<any[]>(`/guilds/${guildId}/vanityroles`),
addVanityRole: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/vanityroles`, {
method: "POST",
body: JSON.stringify(data),
}),
deleteVanityRole: (guildId: string, vanity: string) =>
request<{ status: string }>(`/guilds/${guildId}/vanityroles/${vanity}`, {
method: "DELETE",
}),
getAutoRole: (guildId: string) => request<AutoRoleConfig>(`/guilds/${guildId}/autorole`),
updateAutoRole: (guildId: string, data: AutoRoleUpdate) =>
request<{ status: string }>(`/guilds/${guildId}/autorole`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getTracking: (guildId: string) => request<any>(`/guilds/${guildId}/tracking`),
updateTracking: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/tracking`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getJ2C: (guildId: string) => request<any>(`/guilds/${guildId}/j2c`),
updateJ2C: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/j2c`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getJoinDM: (guildId: string) => request<any>(`/guilds/${guildId}/joindm`),
updateJoinDM: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/joindm`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getCustomRoles: (guildId: string) => request<any>(`/guilds/${guildId}/customroles`),
updateCustomRoles: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/customroles`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getAutoReact: (guildId: string) => request<any>(`/guilds/${guildId}/autoreact`),
updateAutoReact: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/autoreact`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getInvcRole: (guildId: string) => request<any>(`/guilds/${guildId}/invcrole`),
updateInvcRole: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/invcrole`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getRR: (guildId: string) => request<any>(`/guilds/${guildId}/reactionroles`),
updateRR: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/reactionroles`, {
method: "PATCH",
body: JSON.stringify(data),
}),
getInvites: (guildId: string) => request<any>(`/guilds/${guildId}/invites`),
updateInvites: (guildId: string, data: any) =>
request<{ status: string }>(`/guilds/${guildId}/invites`, {
method: "PATCH",
body: JSON.stringify(data),
}),
// Admin
getAdminStats: () => request<AdminStats>("/admin/stats"),
getAdminConfig: () => request<AdminConfig>("/admin/config"),
updateAdminConfig: (data: AdminConfigUpdate) =>
request<{ status: string }>("/admin/config", {
method: "PATCH",
body: JSON.stringify(data),
}),
};