/** * ╔══════════════════════════════════════════════════════════════════╗ * ║ ║ * ║ +-+-+-+-+-+-+-+-+ ║ * ║ |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( endpoint: string, options: RequestInit & { next?: NextFetchRequestConfig } = {} ): Promise { 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("/bot/status"), getBotInfo: () => request("/bot/info"), // Guilds listGuilds: () => request("/guilds/"), getGuildDetails: (guildId: string) => request(`/guilds/${guildId}`), getChannels: (guildId: string) => request(`/guilds/${guildId}/channels`), getRoles: (guildId: string) => request(`/guilds/${guildId}/roles`), // Module Configs getPrefix: (guildId: string) => request(`/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(`/guilds/${guildId}/automod`), updateAutomod: (guildId: string, data: Partial) => request<{ status: string }>(`/guilds/${guildId}/automod`, { method: "PATCH", body: JSON.stringify(data), }), getTickets: (guildId: string) => request(`/guilds/${guildId}/tickets`), updateTickets: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/tickets`, { method: "PATCH", body: JSON.stringify(data), }), getLeveling: (guildId: string) => request(`/guilds/${guildId}/leveling`), updateLeveling: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/leveling`, { method: "PATCH", body: JSON.stringify(data), }), getLogging: (guildId: string) => request(`/guilds/${guildId}/logging`), updateLogging: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/logging`, { method: "PATCH", body: JSON.stringify(data), }), getLeaderboard: (guildId: string) => request(`/guilds/${guildId}/leveling/leaderboard`), getWelcome: (guildId: string) => request(`/guilds/${guildId}/welcome`), updateWelcome: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/welcome`, { method: "PATCH", body: JSON.stringify(data), }), getAntiNuke: (guildId: string) => request(`/guilds/${guildId}/antinuke`), updateAntiNuke: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/antinuke`, { method: "PATCH", body: JSON.stringify(data), }), getVerification: (guildId: string) => request(`/guilds/${guildId}/verification`), updateVerification: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/verification`, { method: "PATCH", body: JSON.stringify(data), }), getVanityRoles: (guildId: string) => request(`/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(`/guilds/${guildId}/autorole`), updateAutoRole: (guildId: string, data: AutoRoleUpdate) => request<{ status: string }>(`/guilds/${guildId}/autorole`, { method: "PATCH", body: JSON.stringify(data), }), getTracking: (guildId: string) => request(`/guilds/${guildId}/tracking`), updateTracking: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/tracking`, { method: "PATCH", body: JSON.stringify(data), }), getJ2C: (guildId: string) => request(`/guilds/${guildId}/j2c`), updateJ2C: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/j2c`, { method: "PATCH", body: JSON.stringify(data), }), getJoinDM: (guildId: string) => request(`/guilds/${guildId}/joindm`), updateJoinDM: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/joindm`, { method: "PATCH", body: JSON.stringify(data), }), getCustomRoles: (guildId: string) => request(`/guilds/${guildId}/customroles`), updateCustomRoles: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/customroles`, { method: "PATCH", body: JSON.stringify(data), }), getAutoReact: (guildId: string) => request(`/guilds/${guildId}/autoreact`), updateAutoReact: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/autoreact`, { method: "PATCH", body: JSON.stringify(data), }), getInvcRole: (guildId: string) => request(`/guilds/${guildId}/invcrole`), updateInvcRole: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/invcrole`, { method: "PATCH", body: JSON.stringify(data), }), getRR: (guildId: string) => request(`/guilds/${guildId}/reactionroles`), updateRR: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/reactionroles`, { method: "PATCH", body: JSON.stringify(data), }), getInvites: (guildId: string) => request(`/guilds/${guildId}/invites`), updateInvites: (guildId: string, data: any) => request<{ status: string }>(`/guilds/${guildId}/invites`, { method: "PATCH", body: JSON.stringify(data), }), // Admin getAdminStats: () => request("/admin/stats"), getAdminConfig: () => request("/admin/config"), updateAdminConfig: (data: AdminConfigUpdate) => request<{ status: string }>("/admin/config", { method: "PATCH", body: JSON.stringify(data), }), };