From 6a064d295eaae8257e49de3fd451eb1042c98211 Mon Sep 17 00:00:00 2001 From: neru Date: Thu, 18 Jun 2026 23:20:12 -0300 Subject: [PATCH] feat: use new db site, cache requests to avoid repeated unneeded fetch calls --- app/characters/page.tsx | 8 +++----- app/customizations/page.tsx | 13 ++++++------- app/customizations/types.ts | 13 ++++++++----- app/dlcs/page.tsx | 7 +++---- app/items/page.tsx | 13 ++++++------- app/items/types.ts | 6 ++++-- app/page.tsx | 11 ++++++----- lib/db.ts | 28 ++++++++++++++++++++++++++++ 8 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 lib/db.ts diff --git a/app/characters/page.tsx b/app/characters/page.tsx index 242b466..c29a2c7 100644 --- a/app/characters/page.tsx +++ b/app/characters/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from 'react'; import { useInventoryStore } from '@/store/useInventoryStore'; import { isKiller } from '../../lib/utils'; +import { DB_BASE_URL, fetchCharacters } from '../../lib/db'; import shared from '../../styles/shared.module.css'; import styles from '../../styles/Characters.module.css'; @@ -16,7 +17,7 @@ type Character = { const getIconUrl = (iconFilePath: string) => { const fileName = iconFilePath.split('/').pop()?.split('.')[0]; - return `/icons/character-icons/${fileName}.png`; + return `${DB_BASE_URL}/icons/character-icons/${fileName}.png`; }; type RoleFilter = 'all' | 'survivors' | 'killers'; @@ -29,10 +30,7 @@ export default function CharactersPage() { const [role, setRole] = useState('all'); useEffect(() => { - fetch('/data/characters.json') - .then(r => r.json()) - .then(setCharacters) - .catch(() => []); + fetchCharacters().then(setCharacters); }, []); const filtered = useMemo(() => { diff --git a/app/customizations/page.tsx b/app/customizations/page.tsx index 3d65ccd..74fd7f6 100644 --- a/app/customizations/page.tsx +++ b/app/customizations/page.tsx @@ -7,6 +7,7 @@ import shared from '../../styles/shared.module.css'; import styles from '../../styles/Customizations.module.css'; import { getFileName, cleanFolderName, isKiller } from '../../lib/utils'; +import { fetchCharacters, fetchCustomizations } from '../../lib/db'; import { Character, CustomizationItem, RoleFilter, Tab, TAB_CATEGORIES, CATEGORY_ORDER } from './types'; import CharacterPicker from './CharacterPicker'; import CharacterCosmetics from './CharacterCosmetics'; @@ -31,13 +32,11 @@ export default function CustomizationsPage() { const [page, setPage] = useState(1); useEffect(() => { - Promise.all([ - fetch('/data/customization_items.json').then(r => r.json()).catch(() => []), - fetch('/data/characters.json').then(r => r.json()).catch(() => []), - ]).then(([items, chars]) => { - setAllItems(items); - setCharacters(chars); - }); + Promise.all([fetchCustomizations(), fetchCharacters()]) + .then(([items, chars]) => { + setAllItems(items); + setCharacters(chars); + }); }, []); useEffect(() => { setPage(1); }, [tab, search]); diff --git a/app/customizations/types.ts b/app/customizations/types.ts index 84b1335..30eb018 100644 --- a/app/customizations/types.ts +++ b/app/customizations/types.ts @@ -26,17 +26,20 @@ export const TAB_CATEGORIES: Partial> = { charms: 8, badges: 9, banners: 10, portraits: 11, }; +import { DB_BASE_URL } from '../../lib/db'; + export const getCosmeticIconUrl = ( item: CustomizationItem, characterMap: Map ): string => { const file = (item.iconFilePath.split('/').pop() ?? '').split('.')[0]; + const base = DB_BASE_URL; switch (item.category) { - case 8: return `/icons/customization/charms/${file}.png`; - case 9: return `/icons/customization/badges/${file}.png`; - case 10: return `/icons/customization/banners/${file}.png`; - case 11: return `/icons/customization/portrait-backgrounds/${file}.png`; + case 8: return `${base}/icons/customization/charms/${file}.png`; + case 9: return `${base}/icons/customization/badges/${file}.png`; + case 10: return `${base}/icons/customization/banners/${file}.png`; + case 11: return `${base}/icons/customization/portrait-backgrounds/${file}.png`; } const subfolder = @@ -50,5 +53,5 @@ export const getCosmeticIconUrl = ( const charFolder = (charName ?? item.associatedCharacter.toString()) .replace(/[\\/:*?"<>|]/g, '_'); - return `/icons/customization/characters/${charFolder}/${subfolder}/${file}.png`; + return `${base}/icons/customization/characters/${charFolder}/${subfolder}/${file}.png`; }; \ No newline at end of file diff --git a/app/dlcs/page.tsx b/app/dlcs/page.tsx index 661517a..d2f07b1 100644 --- a/app/dlcs/page.tsx +++ b/app/dlcs/page.tsx @@ -6,6 +6,7 @@ import shared from '../../styles/shared.module.css'; import styles from '../../styles/Dlcs.module.css'; import { PlatformFilter, isOnSteam, isOnEpic, isOnXbox, matchesPlatform, } from './types'; import { DLC, isNamedDLC } from '@/lib/utils'; +import { fetchDLCs } from '@/lib/db'; const PLATFORM_FILTER_LABELS: Record = { all: 'All', steam: 'Steam', epic: 'Epic', xbox: 'Xbox' }; @@ -18,10 +19,8 @@ export default function DlcsPage() { const [statusFilter, setStatusFilter] = useState<'all' | 'unlocked' | 'locked'>('all'); useEffect(() => { - fetch('/data/dlcs.json') - .then(r => r.json()) - .then((data: DLC[]) => setAllDlcs(data.filter(isNamedDLC))) - .catch(() => []); + fetchDLCs() + .then((data: DLC[]) => setAllDlcs(data.filter(isNamedDLC))); }, []); const filtered = useMemo(() => { diff --git a/app/items/page.tsx b/app/items/page.tsx index c27eead..70cdf07 100644 --- a/app/items/page.tsx +++ b/app/items/page.tsx @@ -7,6 +7,7 @@ import shared from '../../styles/shared.module.css'; import styles from '../../styles/Items.module.css'; import { Item, Offering } from './types'; +import { fetchItems, fetchOfferings } from '../../lib/db'; import ItemGrid from './ItemGrid'; import OfferingGrid from './OfferingGrid'; @@ -23,13 +24,11 @@ export default function ItemsPage() { const [randMax, setRandMax] = useState(200); useEffect(() => { - Promise.all([ - fetch('/data/items.json').then(r => r.json()).catch(() => []), - fetch('/data/offerings.json').then(r => r.json()).catch(() => []), - ]).then(([i, o]) => { - setItems(i); - setOfferings(o); - }); + Promise.all([fetchItems(), fetchOfferings()]) + .then(([i, o]) => { + setItems(i); + setOfferings(o); + }); }, []); const handleClearAll = () => { diff --git a/app/items/types.ts b/app/items/types.ts index d11ef77..9416b1a 100644 --- a/app/items/types.ts +++ b/app/items/types.ts @@ -1,3 +1,5 @@ +import { DB_BASE_URL } from '../../lib/db'; + export type Item = { id: string; name: string; @@ -31,12 +33,12 @@ export const getItemType = (id: string): ItemType => { export const getItemIconUrl = (iconFilePath: string) => { const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; - return `/icons/item-icons/${file}.png`; + return `${DB_BASE_URL}/icons/item-icons/${file}.png`; }; export const getOfferingIconUrl = (iconFilePath: string) => { const file = (iconFilePath.split('/').pop() ?? '').split('.')[0]; - return `/icons/offering-icons/${file}.png`; + return `${DB_BASE_URL}/icons/offering-icons/${file}.png`; }; export const randInRange = (min: number, max: number) => { diff --git a/app/page.tsx b/app/page.tsx index 22d224a..cd4b7e1 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from 'react'; import { useInventoryStore } from '@/store/useInventoryStore'; import styles from '../styles/Home.module.css'; import { isNamedDLC } from '@/lib/utils'; +import { fetchCharacters, fetchItems, fetchOfferings, fetchCustomizations, fetchDLCs } from '@/lib/db'; export default function Home() { const store = useInventoryStore(); @@ -18,11 +19,11 @@ export default function Home() { useEffect(() => { Promise.all([ - fetch('/data/characters.json').then(r => r.json()).catch(() => []), - fetch('/data/items.json').then(r => r.json()).catch(() => []), - fetch('/data/offerings.json').then(r => r.json()).catch(() => []), - fetch('/data/customization_items.json').then(r => r.json()).catch(() => []), - fetch('/data/dlcs.json').then(r => r.json()).catch(() => []) + fetchCharacters(), + fetchItems(), + fetchOfferings(), + fetchCustomizations(), + fetchDLCs() ]).then(([chars, items, offerings, customizations, dlcs]) => { setCharCount(chars.length); setItemsCount(items.length); diff --git a/lib/db.ts b/lib/db.ts new file mode 100644 index 0000000..eff31b8 --- /dev/null +++ b/lib/db.ts @@ -0,0 +1,28 @@ +export const DB_BASE_URL = 'https://dbd-db.neru.rip'; + +const _cache = new Map>(); + +export function fetchDB(path: string): Promise { + if (!_cache.has(path)) { + _cache.set( + path, + fetch(`${DB_BASE_URL}${path}`) + .then(r => { + if (!r.ok) throw new Error(`[db] ${r.status} ${path}`); + return r.json(); + }) + .catch(err => { + _cache.delete(path); + console.error(err); + return []; + }) + ); + } + return _cache.get(path)!; +} + +export const fetchCharacters = () => fetchDB('/data/characters.json'); +export const fetchCustomizations = () => fetchDB('/data/customization_items.json'); +export const fetchItems = () => fetchDB('/data/items.json'); +export const fetchOfferings = () => fetchDB('/data/offerings.json'); +export const fetchDLCs = () => fetchDB('/data/dlcs.json');