Frontend updates: caching basic data (json) from CDragon
Implement caching in the patch_detector, consume the cache from API routes in frontend
This commit is contained in:
@@ -9,7 +9,8 @@
|
|||||||
"import-matches": "node scripts/setup-db.js import-matches",
|
"import-matches": "node scripts/setup-db.js import-matches",
|
||||||
"import-patches": "node scripts/setup-db.js import-patches",
|
"import-patches": "node scripts/setup-db.js import-patches",
|
||||||
"generate-stats": "node scripts/setup-db.js generate-stats",
|
"generate-stats": "node scripts/setup-db.js generate-stats",
|
||||||
"status": "node scripts/setup-db.js status"
|
"status": "node scripts/setup-db.js status",
|
||||||
|
"fetch-cdragon": "node scripts/fetch-cdragon.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mongodb": "^6.10.0"
|
"mongodb": "^6.10.0"
|
||||||
|
|||||||
34
dev/scripts/fetch-cdragon.js
Normal file
34
dev/scripts/fetch-cdragon.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Cache directory - use dev/cdragon by default
|
||||||
|
const cacheDir = process.env.CDRAGON_CACHE_DIR || path.join(__dirname, '..', 'data', 'cdragon');
|
||||||
|
|
||||||
|
// Dev MongoDB credentials (matching docker-compose.yml defaults)
|
||||||
|
const mongoUser = process.env.MONGO_USER || 'root';
|
||||||
|
const mongoPass = process.env.MONGO_PASS || 'password';
|
||||||
|
const mongoHost = process.env.MONGO_HOST || 'localhost:27017';
|
||||||
|
|
||||||
|
// Run patch_detector with the cache directory and dev MongoDB credentials
|
||||||
|
const patchDetector = spawn('npx', ['tsx', '../patch_detector/index.ts'], {
|
||||||
|
cwd: path.join(__dirname, '..'),
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
CDRAGON_CACHE_DIR: cacheDir,
|
||||||
|
MONGO_USER: mongoUser,
|
||||||
|
MONGO_PASS: mongoPass,
|
||||||
|
MONGO_HOST: mongoHost
|
||||||
|
},
|
||||||
|
stdio: 'inherit'
|
||||||
|
});
|
||||||
|
|
||||||
|
patchDetector.on('close', (code) => {
|
||||||
|
process.exit(code || 0);
|
||||||
|
});
|
||||||
@@ -23,7 +23,7 @@ async function setupDatabase() {
|
|||||||
const patchFile = path.join(dataDir, "patches.json");
|
const patchFile = path.join(dataDir, "patches.json");
|
||||||
if(!fs.existsSync(dataDir) || !fs.existsSync(patchFile)) {
|
if(!fs.existsSync(dataDir) || !fs.existsSync(patchFile)) {
|
||||||
fs.mkdirSync(dataDir, { recursive: true });
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
console.log('📥 No data files found. Downloading latest snapshot...');
|
console.log('🚫 No data files found. Downloading latest snapshot...');
|
||||||
await downloadAndExtractSnapshot();
|
await downloadAndExtractSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,11 @@ async function setupDatabase() {
|
|||||||
console.log('✅ Skipping matches import - sufficient data already present');
|
console.log('✅ Skipping matches import - sufficient data already present');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Run match collector to generate stats
|
// 7. Fetch CDragon data for the current patch
|
||||||
|
console.log('🎮 Fetching CDragon data...');
|
||||||
|
await fetchCDragonData();
|
||||||
|
|
||||||
|
// 8. Run match collector to generate stats
|
||||||
console.log('📊 Generating champion stats...');
|
console.log('📊 Generating champion stats...');
|
||||||
await generateChampionStats();
|
await generateChampionStats();
|
||||||
|
|
||||||
@@ -322,6 +326,24 @@ async function generateChampionStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchCDragonData() {
|
||||||
|
try {
|
||||||
|
console.log('🔄 Running CDragon fetcher...');
|
||||||
|
|
||||||
|
// Run the fetch-cdragon script
|
||||||
|
const fetchCDragonPath = path.join(__dirname, 'fetch-cdragon.js');
|
||||||
|
execSync(`node ${fetchCDragonPath}`, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
cwd: path.join(__dirname, '..')
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ CDragon data fetched');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to fetch CDragon data:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getMatchCount(patchVersion) {
|
async function getMatchCount(patchVersion) {
|
||||||
const client = new MongoClient(getMongoUri());
|
const client = new MongoClient(getMongoUri());
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
import { debounce, isEmpty } from '~/utils/helpers'
|
import { debounce, isEmpty } from '~/utils/helpers'
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const CDRAGON_CHAMPIONS_URL =
|
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
|
||||||
const CHAMPIONS_API_URL = '/api/champions'
|
const CHAMPIONS_API_URL = '/api/champions'
|
||||||
|
|
||||||
// State
|
// State
|
||||||
@@ -11,7 +9,7 @@ const {
|
|||||||
data: championsData,
|
data: championsData,
|
||||||
pending: loadingChampions,
|
pending: loadingChampions,
|
||||||
error: championsError
|
error: championsError
|
||||||
} = useFetch(CDRAGON_CHAMPIONS_URL, {
|
} = useFetch('/api/cdragon/champion-summary', {
|
||||||
key: 'champions-data',
|
key: 'champions-data',
|
||||||
lazy: false,
|
lazy: false,
|
||||||
server: false // Disable server-side fetching to avoid hydration issues
|
server: false // Disable server-side fetching to avoid hydration issues
|
||||||
|
|||||||
@@ -13,16 +13,11 @@ const props = defineProps<{
|
|||||||
summonerSpells?: Array<{ id: number; count: number; pickrate: number }> // API data when available
|
summonerSpells?: Array<{ id: number; count: number; pickrate: number }> // API data when available
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Constants
|
|
||||||
const ITEMS_API_URL = CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
|
||||||
const SUMMONER_SPELLS_URL =
|
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json'
|
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const currentlySelectedBuild = ref(0)
|
const currentlySelectedBuild = ref(0)
|
||||||
|
|
||||||
// Fetch items
|
// Fetch items from cached API
|
||||||
const { data: items } = useFetch<Array<Item>>(ITEMS_API_URL, {
|
const { data: items } = useFetch<Array<Item>>('/api/cdragon/items', {
|
||||||
lazy: true,
|
lazy: true,
|
||||||
server: false
|
server: false
|
||||||
})
|
})
|
||||||
@@ -44,11 +39,14 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fetch summoner spells
|
// Fetch summoner spells from cached API
|
||||||
const { data: summonerSpellsData } = useFetch<Array<SummonerSpell>>(SUMMONER_SPELLS_URL, {
|
const { data: summonerSpellsData } = useFetch<Array<SummonerSpell>>(
|
||||||
|
'/api/cdragon/summoner-spells',
|
||||||
|
{
|
||||||
lazy: true,
|
lazy: true,
|
||||||
server: false
|
server: false
|
||||||
})
|
}
|
||||||
|
)
|
||||||
const summonerSpellMap = ref<Map<number, SummonerSpell>>(new Map())
|
const summonerSpellMap = ref<Map<number, SummonerSpell>>(new Map())
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -143,17 +141,13 @@ const primaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
|||||||
const secondaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
const secondaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
||||||
const keystoneIds: Ref<Array<number>> = ref(Array(props.runes.length))
|
const keystoneIds: Ref<Array<number>> = ref(Array(props.runes.length))
|
||||||
|
|
||||||
const { data: perks_data }: PerksResponse = await useFetch(
|
const { data: perks_data }: PerksResponse = await useFetch('/api/cdragon/perks')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perks.json'
|
|
||||||
)
|
|
||||||
const perks = reactive(new Map())
|
const perks = reactive(new Map())
|
||||||
for (const perk of perks_data.value) {
|
for (const perk of perks_data.value) {
|
||||||
perks.set(perk.id, perk)
|
perks.set(perk.id, perk)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: stylesData }: PerkStylesResponse = await useFetch(
|
const { data: stylesData }: PerkStylesResponse = await useFetch('/api/cdragon/perkstyles')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json'
|
|
||||||
)
|
|
||||||
|
|
||||||
function refreshStylesKeystones() {
|
function refreshStylesKeystones() {
|
||||||
for (const style of stylesData.value.styles) {
|
for (const style of stylesData.value.styles) {
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ const emit = defineEmits<{
|
|||||||
refresh: []
|
refresh: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { data: items } = useFetch<Array<{ id: number; iconPath: string }>>(
|
const { data: items } = useFetch<Array<{ id: number; iconPath: string }>>('/api/cdragon/items', {
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/items.json',
|
|
||||||
{
|
|
||||||
lazy: true, // Don't block rendering
|
lazy: true, // Don't block rendering
|
||||||
server: false // Client-side only
|
server: false // Client-side only
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Create item map reactively
|
// Create item map reactively
|
||||||
const itemMap = reactive(new Map<number, { id: number; iconPath: string }>())
|
const itemMap = reactive(new Map<number, { id: number; iconPath: string }>())
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ const props = defineProps<{
|
|||||||
error?: boolean
|
error?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// Constants
|
// State - use cached API endpoint instead of direct CDragon fetch
|
||||||
const ITEMS_API_URL = CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
|
||||||
|
|
||||||
// State
|
|
||||||
const {
|
const {
|
||||||
data: items,
|
data: items,
|
||||||
pending: loadingItems,
|
pending: loadingItems,
|
||||||
error: itemsError
|
error: itemsError
|
||||||
} = useFetch(ITEMS_API_URL, {
|
} = useFetch('/api/cdragon/items', {
|
||||||
lazy: true, // Don't block rendering
|
lazy: true, // Don't block rendering
|
||||||
server: false // Client-side only
|
server: false // Client-side only
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,17 +8,13 @@ const props = defineProps<{
|
|||||||
const primaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
|
const primaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
|
||||||
const secondaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
|
const secondaryStyle: Ref<PerkStyle> = ref({ id: 0, name: '', iconPath: '', slots: [] })
|
||||||
|
|
||||||
const { data: perks_data }: PerksResponse = await useFetch(
|
const { data: perks_data }: PerksResponse = await useFetch('/api/cdragon/perks')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perks.json'
|
|
||||||
)
|
|
||||||
const perks = reactive(new Map())
|
const perks = reactive(new Map())
|
||||||
for (const perk of perks_data.value) {
|
for (const perk of perks_data.value) {
|
||||||
perks.set(perk.id, perk)
|
perks.set(perk.id, perk)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: stylesData }: PerkStylesResponse = await useFetch(
|
const { data: stylesData }: PerkStylesResponse = await useFetch('/api/cdragon/perkstyles')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json'
|
|
||||||
)
|
|
||||||
watch(
|
watch(
|
||||||
() => props.primaryStyleId,
|
() => props.primaryStyleId,
|
||||||
async (_newP, _oldP) => {
|
async (_newP, _oldP) => {
|
||||||
|
|||||||
@@ -14,17 +14,14 @@ const primaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
|||||||
const secondaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
const secondaryStyles: Ref<Array<PerkStyle>> = ref(Array(props.runes.length))
|
||||||
const keystoneIds: Ref<Array<number>> = ref(Array(props.runes.length))
|
const keystoneIds: Ref<Array<number>> = ref(Array(props.runes.length))
|
||||||
|
|
||||||
const { data: perks_data }: PerksResponse = await useFetch(
|
// Use cached API endpoints instead of direct CDragon fetch
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perks.json'
|
const { data: perks_data }: PerksResponse = await useFetch('/api/cdragon/perks')
|
||||||
)
|
|
||||||
const perks = reactive(new Map())
|
const perks = reactive(new Map())
|
||||||
for (const perk of perks_data.value) {
|
for (const perk of perks_data.value) {
|
||||||
perks.set(perk.id, perk)
|
perks.set(perk.id, perk)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: stylesData }: PerkStylesResponse = await useFetch(
|
const { data: stylesData }: PerkStylesResponse = await useFetch('/api/cdragon/perkstyles')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json'
|
|
||||||
)
|
|
||||||
watch(
|
watch(
|
||||||
() => props.runes,
|
() => props.runes,
|
||||||
(_newRunes, _oldRunes) => {
|
(_newRunes, _oldRunes) => {
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import { LANE_IMAGES, lanePositionToIndex, POSITIONS_STR } from '~/utils/cdragon
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const lane = route.params.lane as string
|
const lane = route.params.lane as string
|
||||||
|
|
||||||
const { data: championsData }: ChampionsResponse = await useFetch(
|
const { data: championsData }: ChampionsResponse = await useFetch('/api/cdragon/champion-summary')
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
|
||||||
)
|
|
||||||
|
|
||||||
const { data: championsLanes }: { data: Ref<Array<ChampionData>> } =
|
const { data: championsLanes }: { data: Ref<Array<ChampionData>> } =
|
||||||
await useFetch('/api/champions')
|
await useFetch('/api/champions')
|
||||||
|
|||||||
14
frontend/server/api/cdragon/champion-summary.ts
Normal file
14
frontend/server/api/cdragon/champion-summary.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { getChampionSummary } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
|
export default defineEventHandler(async () => {
|
||||||
|
try {
|
||||||
|
const championSummary = await getChampionSummary()
|
||||||
|
return championSummary
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching champion summary:', error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fetch champion summary'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
14
frontend/server/api/cdragon/items.ts
Normal file
14
frontend/server/api/cdragon/items.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { getItems } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
|
export default defineEventHandler(async () => {
|
||||||
|
try {
|
||||||
|
const items = await getItems()
|
||||||
|
return items
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching items:', error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fetch items'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
14
frontend/server/api/cdragon/perks.ts
Normal file
14
frontend/server/api/cdragon/perks.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { getPerks } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
|
export default defineEventHandler(async () => {
|
||||||
|
try {
|
||||||
|
const perks = await getPerks()
|
||||||
|
return perks
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching perks:', error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fetch perks'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
14
frontend/server/api/cdragon/perkstyles.ts
Normal file
14
frontend/server/api/cdragon/perkstyles.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { getPerkStyles } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
|
export default defineEventHandler(async () => {
|
||||||
|
try {
|
||||||
|
const perkStyles = await getPerkStyles()
|
||||||
|
return perkStyles
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching perk styles:', error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fetch perk styles'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
14
frontend/server/api/cdragon/summoner-spells.ts
Normal file
14
frontend/server/api/cdragon/summoner-spells.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { getSummonerSpells } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
|
export default defineEventHandler(async () => {
|
||||||
|
try {
|
||||||
|
const summonerSpells = await getSummonerSpells()
|
||||||
|
return summonerSpells
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching summoner spells:', error)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'Failed to fetch summoner spells'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { CDRAGON_BASE } from '~/utils/cdragon'
|
import { getChampionSummary } from '~/server/utils/cdragon-cache'
|
||||||
|
|
||||||
async function championRoutes() {
|
async function championRoutes() {
|
||||||
const championsData: Array<Champion> = await (
|
const championsData = await getChampionSummary()
|
||||||
await fetch(
|
|
||||||
CDRAGON_BASE + 'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
|
||||||
)
|
|
||||||
).json()
|
|
||||||
|
|
||||||
const routes: Array<string> = []
|
const routes: Array<string> = []
|
||||||
for (const champion of championsData) {
|
for (const champion of championsData) {
|
||||||
|
|||||||
205
frontend/server/utils/cdragon-cache.ts
Normal file
205
frontend/server/utils/cdragon-cache.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import { readFile, existsSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
|
||||||
|
const readFileAsync = promisify(readFile)
|
||||||
|
|
||||||
|
// CDragon base URL for fallback
|
||||||
|
const CDRAGON_BASE = 'https://raw.communitydragon.org/'
|
||||||
|
|
||||||
|
// Cache directory - can be configured via environment variable
|
||||||
|
// Default to dev/cdragon for development
|
||||||
|
const getCacheDir = () => {
|
||||||
|
if (process.env.CDRAGON_CACHE_DIR) {
|
||||||
|
return process.env.CDRAGON_CACHE_DIR
|
||||||
|
}
|
||||||
|
// Default to dev/cdragon relative to project root
|
||||||
|
return join(process.cwd(), '..', 'dev', 'data', 'cdragon')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current patch from the patch.txt file or fallback to 'latest'
|
||||||
|
* Converts patch format for CDragon: "16.4.1" -> "16.4"
|
||||||
|
*/
|
||||||
|
async function getCurrentPatch(): Promise<string> {
|
||||||
|
const cacheDir = getCacheDir()
|
||||||
|
const patchFile = join(cacheDir, 'latest', 'patch.txt')
|
||||||
|
|
||||||
|
if (existsSync(patchFile)) {
|
||||||
|
const patch = await readFileAsync(patchFile, 'utf-8')
|
||||||
|
const trimmedPatch = patch.trim()
|
||||||
|
// Convert patch format for CDragon: "16.4.1" -> "16.4"
|
||||||
|
return trimmedPatch.split('.').slice(0, 2).join('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to 'latest' if no patch file exists
|
||||||
|
return 'latest'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached CDragon data types
|
||||||
|
*/
|
||||||
|
interface CDragonCacheOptions {
|
||||||
|
patch?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch data from local cache with CDragon fallback
|
||||||
|
*/
|
||||||
|
async function fetchFromCache<T>(
|
||||||
|
assetName: string,
|
||||||
|
cdragonPath: string,
|
||||||
|
options?: CDragonCacheOptions
|
||||||
|
): Promise<T> {
|
||||||
|
const cacheDir = getCacheDir()
|
||||||
|
const patch = options?.patch || (await getCurrentPatch())
|
||||||
|
const cachePath = join(cacheDir, patch, assetName)
|
||||||
|
|
||||||
|
// Try to read from cache first
|
||||||
|
if (existsSync(cachePath)) {
|
||||||
|
try {
|
||||||
|
const data = await readFileAsync(cachePath, 'utf-8')
|
||||||
|
return JSON.parse(data) as T
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reading cache file ${cachePath}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to CDragon
|
||||||
|
console.log(`Cache miss for ${assetName}, fetching from CDragon...`)
|
||||||
|
const url = `${CDRAGON_BASE}${patch}/${cdragonPath}`
|
||||||
|
const response = await fetch(url)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch ${assetName} from CDragon: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await response.json()) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get items data from cache
|
||||||
|
*/
|
||||||
|
async function getItems(patch?: string): Promise<CDragonItem[]> {
|
||||||
|
return fetchFromCache<CDragonItem[]>(
|
||||||
|
'items.json',
|
||||||
|
'plugins/rcp-be-lol-game-data/global/default/v1/items.json',
|
||||||
|
{ patch }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get perks (runes) data from cache
|
||||||
|
*/
|
||||||
|
async function getPerks(patch?: string): Promise<CDragonPerk[]> {
|
||||||
|
return fetchFromCache<CDragonPerk[]>(
|
||||||
|
'perks.json',
|
||||||
|
'plugins/rcp-be-lol-game-data/global/default/v1/perks.json',
|
||||||
|
{ patch }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get perk styles data from cache
|
||||||
|
*/
|
||||||
|
async function getPerkStyles(patch?: string): Promise<CDragonPerkStyles> {
|
||||||
|
return fetchFromCache<CDragonPerkStyles>(
|
||||||
|
'perkstyles.json',
|
||||||
|
'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json',
|
||||||
|
{ patch }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get summoner spells data from cache
|
||||||
|
*/
|
||||||
|
async function getSummonerSpells(patch?: string): Promise<CDragonSummonerSpell[]> {
|
||||||
|
return fetchFromCache<CDragonSummonerSpell[]>(
|
||||||
|
'summoner-spells.json',
|
||||||
|
'plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json',
|
||||||
|
{ patch }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get champion summary data from cache
|
||||||
|
*/
|
||||||
|
async function getChampionSummary(patch?: string): Promise<CDragonChampionSummary[]> {
|
||||||
|
return fetchFromCache<CDragonChampionSummary[]>(
|
||||||
|
'champion-summary.json',
|
||||||
|
'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json',
|
||||||
|
{ patch }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type definitions for CDragon data
|
||||||
|
interface CDragonItem {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
iconPath: string
|
||||||
|
description?: string
|
||||||
|
plaintext?: string
|
||||||
|
into?: number[]
|
||||||
|
from?: number[]
|
||||||
|
gold?: {
|
||||||
|
base: number
|
||||||
|
total: number
|
||||||
|
sell: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CDragonPerk {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
iconPath: string
|
||||||
|
shortDesc?: string
|
||||||
|
longDesc?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CDragonPerkStyle {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
iconPath: string
|
||||||
|
slots: Array<{
|
||||||
|
type: string
|
||||||
|
perks: number[]
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CDragonPerkStyles {
|
||||||
|
styles: CDragonPerkStyle[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CDragonSummonerSpell {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
iconPath: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CDragonChampionSummary {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
alias: string
|
||||||
|
squarePortraitPath: string
|
||||||
|
roles?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getItems,
|
||||||
|
getPerks,
|
||||||
|
getPerkStyles,
|
||||||
|
getSummonerSpells,
|
||||||
|
getChampionSummary,
|
||||||
|
getCurrentPatch,
|
||||||
|
CDRAGON_BASE
|
||||||
|
}
|
||||||
|
|
||||||
|
export type {
|
||||||
|
CDragonItem,
|
||||||
|
CDragonPerk,
|
||||||
|
CDragonPerkStyle,
|
||||||
|
CDragonPerkStyles,
|
||||||
|
CDragonSummonerSpell,
|
||||||
|
CDragonChampionSummary
|
||||||
|
}
|
||||||
@@ -1,18 +1,66 @@
|
|||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
|
import { writeFile, mkdir } from 'fs/promises'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
// CDragon base URL for specific patch
|
||||||
|
const CDRAGON_BASE = 'https://raw.communitydragon.org/'
|
||||||
|
|
||||||
|
// Assets to cache for each patch
|
||||||
|
const CDRAGON_ASSETS = [
|
||||||
|
{
|
||||||
|
name: 'items.json',
|
||||||
|
path: 'plugins/rcp-be-lol-game-data/global/default/v1/items.json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'perks.json',
|
||||||
|
path: 'plugins/rcp-be-lol-game-data/global/default/v1/perks.json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'perkstyles.json',
|
||||||
|
path: 'plugins/rcp-be-lol-game-data/global/default/v1/perkstyles.json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'summoner-spells.json',
|
||||||
|
path: 'plugins/rcp-be-lol-game-data/global/default/v1/summoner-spells.json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'champion-summary.json',
|
||||||
|
path: 'plugins/rcp-be-lol-game-data/global/default/v1/champion-summary.json'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
// In dev mode, get patch from database (the one we have match data for)
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('Development mode: downloading cache for database patch')
|
||||||
const client = await connectToDatabase()
|
const client = await connectToDatabase()
|
||||||
const newPatch = await fetchLatestPatch()
|
const dbPatch = await getLatestPatchFromDatabase(client)
|
||||||
|
await client.close()
|
||||||
|
|
||||||
console.log('Latest patch is: ' + newPatch)
|
if (dbPatch) {
|
||||||
|
console.log('Latest patch in database: ' + dbPatch)
|
||||||
const newDate = new Date()
|
await downloadAssets(dbPatch)
|
||||||
if (!(await compareLatestSavedPatch(client, newPatch, newDate))) {
|
} else {
|
||||||
downloadAssets()
|
console.log('No patch found in database, fetching latest from game...')
|
||||||
|
const latestPatch = await fetchLatestPatch()
|
||||||
|
console.log('Latest game patch: ' + latestPatch)
|
||||||
|
await downloadAssets(latestPatch)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Production mode: check database and update if new patch
|
||||||
|
const newPatch = await fetchLatestPatch()
|
||||||
|
console.log('Latest patch is: ' + newPatch)
|
||||||
|
|
||||||
|
const client = await connectToDatabase()
|
||||||
|
const newDate = new Date()
|
||||||
|
if (!(await compareLatestSavedPatch(client, newPatch, newDate))) {
|
||||||
|
await downloadAssets(newPatch)
|
||||||
|
}
|
||||||
await client.close()
|
await client.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,4 +106,66 @@ async function compareLatestSavedPatch(client: MongoClient, newPatch: string, ne
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadAssets() {}
|
async function getLatestPatchFromDatabase(client: MongoClient): Promise<string | null> {
|
||||||
|
const database = client.db('patches')
|
||||||
|
const patches = database.collection('patches')
|
||||||
|
const latestPatch = await patches.find().limit(1).sort({ date: -1 }).next()
|
||||||
|
|
||||||
|
if (latestPatch == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestPatch.patch
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAssets(patch: string) {
|
||||||
|
// Convert patch format for CDragon: "16.4.1" -> "16.4"
|
||||||
|
// CDragon uses patch format without the last minor version
|
||||||
|
const cdragonPatch = patch.split('.').slice(0, 2).join('.')
|
||||||
|
console.log(`Downloading CDragon assets for patch ${cdragonPatch} (from ${patch})...`)
|
||||||
|
|
||||||
|
// Get cache directory from environment or use default
|
||||||
|
const cacheDir = process.env.CDRAGON_CACHE_DIR || '/cdragon'
|
||||||
|
const patchDir = join(cacheDir, cdragonPatch)
|
||||||
|
|
||||||
|
// Create patch directory if it doesn't exist
|
||||||
|
if (!existsSync(patchDir)) {
|
||||||
|
await mkdir(patchDir, { recursive: true })
|
||||||
|
console.log(`Created directory: ${patchDir}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download each asset
|
||||||
|
for (const asset of CDRAGON_ASSETS) {
|
||||||
|
const url = `${CDRAGON_BASE}${cdragonPatch}/${asset.path}`
|
||||||
|
const filePath = join(patchDir, asset.name)
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Downloading ${asset.name}...`)
|
||||||
|
const response = await fetch(url)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to download ${asset.name}: ${response.status} ${response.statusText}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
await writeFile(filePath, JSON.stringify(data, null, 2))
|
||||||
|
console.log(`Saved ${asset.name} to ${filePath}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error downloading ${asset.name}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a symlink or copy to 'latest' directory for easy access
|
||||||
|
const latestDir = join(cacheDir, 'latest')
|
||||||
|
const latestFile = join(latestDir, 'patch.txt')
|
||||||
|
|
||||||
|
if (!existsSync(latestDir)) {
|
||||||
|
await mkdir(latestDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(latestFile, patch)
|
||||||
|
console.log(`Updated latest patch reference to ${patch}`)
|
||||||
|
|
||||||
|
console.log('CDragon assets download complete!')
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user