diff --git a/scripts/export_resources.ts b/scripts/export_resources.ts index 48bd069..c56a253 100644 --- a/scripts/export_resources.ts +++ b/scripts/export_resources.ts @@ -17,36 +17,40 @@ if (!supabaseUrl || !supabaseKey) { const supabase = createClient(supabaseUrl, supabaseKey); -const MCICONS_URL = - "https://hamburger-api.powernplant101-c6b.workers.dev/mcicons"; +const MCICONS_BASE_URLS = [ + "https://assets.codersoft.xyz", + "https://hamburger-api.powernplant101-c6b.workers.dev", +]; const fetchMcicons = async () => { - try { - const response = await fetch(MCICONS_URL); - if (!response.ok) { - console.warn(`Failed to fetch mcicons: ${response.status}`); - return []; + for (const base of MCICONS_BASE_URLS) { + try { + const response = await fetch(`${base}/mcicons`); + if (!response.ok) { + console.warn(`Failed to fetch mcicons from ${base}: ${response.status}`); + continue; + } + const data = await response.json(); + const files = Array.isArray(data?.files) ? data.files : []; + return files + .map((file) => { + const rawTitle = String(file.title || "").replace(/_/g, " "); + const title = rawTitle.replace(/\.[^/.]+$/, "").trim(); + return { + id: `mcicons-${file.id ?? title ?? "unknown"}`, + title, + credit: file.credit || "", + filetype: file.ext || file.filetype, + download_url: file.url, + subcategory: file.subcategory, + }; + }) + .filter((item) => item.title); + } catch (error) { + console.warn(`Failed to fetch mcicons from ${base}:`, error); } - const data = await response.json(); - const files = Array.isArray(data?.files) ? data.files : []; - return files - .map((file) => { - const rawTitle = String(file.title || "").replace(/_/g, " "); - const title = rawTitle.replace(/\.[^/.]+$/, "").trim(); - return { - id: `mcicons-${file.id ?? title ?? "unknown"}`, - title, - credit: file.credit || "", - filetype: file.ext || file.filetype, - download_url: file.url, - subcategory: file.subcategory, - }; - }) - .filter((item) => item.title); - } catch (error) { - console.warn("Failed to fetch mcicons:", error); - return []; } + return []; }; async function exportResources() { diff --git a/src/components/profile/FontPicker.tsx b/src/components/profile/FontPicker.tsx index ad90cd6..3f29a92 100644 --- a/src/components/profile/FontPicker.tsx +++ b/src/components/profile/FontPicker.tsx @@ -9,6 +9,7 @@ import { SelectLabel } from "@/components/ui/select"; import { toast } from "sonner"; +import { fetchFromAssetsApi } from "@/lib/assetsApi"; interface FontOption { id: number; @@ -59,12 +60,11 @@ export const FontPicker: React.FC = ({ value, onFontChange }) = const timeoutId = setTimeout(() => controller.abort(), 10000); try { - const res = await fetch('https://hamburger-api.powernplant101-c6b.workers.dev/fonts', { + const res = await fetchFromAssetsApi('/fonts', { signal: controller.signal }); clearTimeout(timeoutId); - if (!res.ok) throw new Error(`Fetch failed: ${res.status}`); const data = await res.json(); if (data && Array.isArray(data.files)) { diff --git a/src/lib/api.ts b/src/lib/api.ts index 09a72af..d671fd9 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,7 +1,7 @@ import { readCache, writeCache, clearCache } from "@/lib/cache"; +import { fetchFromAssetsApi } from "@/lib/assetsApi"; import { Resource } from "@/types/resources"; -const API_BASE = "https://hamburger-api.powernplant101-c6b.workers.dev"; const ALL_RESOURCES_CACHE_KEY = "api:all-resources"; const CATEGORIES_CACHE_KEY = "api:categories"; const CATEGORY_CACHE_PREFIX = "api:category:"; @@ -28,21 +28,17 @@ export interface ApiAllResources { categories: Record; } -const fetchJson = async (url: string): Promise => { +const fetchJson = async (path: string): Promise => { try { - const res = await fetch(url, { + const res = await fetchFromAssetsApi(path, { cache: "default", headers: { Accept: "application/json", }, }); - if (!res.ok) { - console.error(`API error: ${res.status} ${res.statusText} for ${url}`); - return null; - } return (await res.json()) as T; } catch (error) { - console.error(`Fetch error for ${url}:`, error); + console.error(`Fetch error for ${path}:`, error); return null; } }; @@ -99,7 +95,7 @@ export const fetchCategories = async (): Promise => { const cached = readCache(CATEGORIES_CACHE_KEY); if (cached) return cached; - const data = await fetchJson(`${API_BASE}/categories`); + const data = await fetchJson("/categories"); if (data) { writeCache(CATEGORIES_CACHE_KEY, data); return data; @@ -119,7 +115,7 @@ export const fetchCategory = async (category: string): Promise => { const apiCategory = toApiCategory(normalized); const data = await fetchJson<{ category: string; files: ApiResource[] }>( - `${API_BASE}/category/${apiCategory}` + `/category/${apiCategory}` ); if (data?.files && data.files.length > 0) { try { @@ -151,7 +147,7 @@ export const fetchAllResources = async (): Promise => { ); } - const data = await fetchJson(`${API_BASE}/all`); + const data = await fetchJson("/all"); if (data?.categories && Object.keys(data.categories).length > 0) { try { writeCache(ALL_RESOURCES_CACHE_KEY, data); @@ -184,7 +180,7 @@ export const fetchAllResources = async (): Promise => { }; export const refreshAllResources = async (): Promise => { - const data = await fetchJson(`${API_BASE}/all`); + const data = await fetchJson("/all"); if (data?.categories) { writeCache(ALL_RESOURCES_CACHE_KEY, data); return Object.entries(data.categories).flatMap(([category, items]) => diff --git a/src/lib/assetsApi.ts b/src/lib/assetsApi.ts new file mode 100644 index 0000000..7e6010d --- /dev/null +++ b/src/lib/assetsApi.ts @@ -0,0 +1,22 @@ +export const ASSETS_API_BASE_URL = 'https://assets.codersoft.xyz'; +export const ASSETS_API_FALLBACK_BASE_URL = 'https://hamburger-api.powernplant101-c6b.workers.dev'; + +const ASSETS_API_BASE_URLS = [ASSETS_API_BASE_URL, ASSETS_API_FALLBACK_BASE_URL]; + +export async function fetchFromAssetsApi(path: string, init?: RequestInit): Promise { + let lastError: unknown; + + for (const base of ASSETS_API_BASE_URLS) { + try { + const response = await fetch(`${base}${path}`, init); + if (response.ok) return response; + lastError = new Error(`Assets API error ${response.status} ${response.statusText} for ${base}${path}`); + console.error(`Assets API error ${response.status} ${response.statusText} for ${base}${path}`); + } catch (error) { + lastError = error; + console.error(`Assets API fetch failed for ${base}${path}:`, error); + } + } + + throw lastError instanceof Error ? lastError : new Error('Assets API unavailable'); +} diff --git a/src/pages/BackgroundGenerator.tsx b/src/pages/BackgroundGenerator.tsx index 1c182a3..3399931 100644 --- a/src/pages/BackgroundGenerator.tsx +++ b/src/pages/BackgroundGenerator.tsx @@ -26,6 +26,7 @@ import { } from "@tabler/icons-react"; import { toast } from "sonner"; import { Helmet } from "react-helmet-async"; +import { fetchFromAssetsApi } from "@/lib/assetsApi"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; @@ -192,8 +193,7 @@ const BackgroundGenerator = () => { const fetchTextures = async () => { try { setIsLoadingTextures(true); - const response = await fetch('https://hamburger-api.powernplant101-c6b.workers.dev/mcicons'); - if (!response.ok) throw new Error('Failed to fetch textures'); + const response = await fetchFromAssetsApi('/mcicons'); const data = await response.json(); if (data && data.files) { const filteredTextures = Array.isArray(data.files) ? data.files.filter(isTexture) : [];