From 76f42cacc798563f62417e84b4653c6e87642e30 Mon Sep 17 00:00:00 2001 From: FragginWagon Date: Wed, 28 Jan 2026 18:58:45 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20API=20endpoint=20for=20retrie?= =?UTF-8?q?ving=20Pok=C3=A9mon=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pokedex.online/server/gamemaster-api.js | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 code/websites/pokedex.online/server/gamemaster-api.js diff --git a/code/websites/pokedex.online/server/gamemaster-api.js b/code/websites/pokedex.online/server/gamemaster-api.js new file mode 100644 index 0000000..4082e0c --- /dev/null +++ b/code/websites/pokedex.online/server/gamemaster-api.js @@ -0,0 +1,253 @@ +/** + * Gamemaster API Server + * Provides endpoints for accessing processed gamemaster data + * Allows other apps to fetch unmodified and modified pokemon data + */ + +import express from 'express'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const router = express.Router(); +const DATA_DIR = path.join(__dirname, 'data', 'gamemaster'); + +/** + * Ensure data directory exists + */ +function ensureDataDir() { + if (!fs.existsSync(DATA_DIR)) { + fs.mkdirSync(DATA_DIR, { recursive: true }); + } +} + +/** + * Get path to a gamemaster file + * @param {string} filename + * @returns {string} Full file path + */ +function getFilePath(filename) { + return path.join(DATA_DIR, filename); +} + +/** + * Check if a file exists + * @param {string} filename + * @returns {boolean} + */ +function fileExists(filename) { + return fs.existsSync(getFilePath(filename)); +} + +/** + * Get file metadata (size, modified time) + * @param {string} filename + * @returns {Object|null} File info or null if doesn't exist + */ +function getFileInfo(filename) { + const filepath = getFilePath(filename); + if (!fs.existsSync(filepath)) return null; + + const stats = fs.statSync(filepath); + return { + filename, + size: stats.size, + sizeKb: (stats.size / 1024).toFixed(2), + modified: stats.mtime.toISOString() + }; +} + +/** + * Save gamemaster data to file + * @param {string} filename + * @param {Object|Array} data + */ +function saveFile(filename, data) { + ensureDataDir(); + const filepath = getFilePath(filename); + fs.writeFileSync(filepath, JSON.stringify(data, null, 2)); + return getFileInfo(filename); +} + +/** + * Load gamemaster data from file + * @param {string} filename + * @returns {Object|Array|null} + */ +function loadFile(filename) { + const filepath = getFilePath(filename); + if (!fs.existsSync(filepath)) return null; + const content = fs.readFileSync(filepath, 'utf-8'); + return JSON.parse(content); +} + +// ============================================================================ +// ROUTES +// ============================================================================ + +/** + * GET /api/gamemaster/status + * Get status of available gamemaster files + */ +router.get('/status', (req, res) => { + ensureDataDir(); + + const files = { + pokemon: getFileInfo('pokemon.json'), + pokemonAllForms: getFileInfo('pokemon-allFormsCostumes.json'), + moves: getFileInfo('pokemon-moves.json'), + raw: getFileInfo('latest-raw.json') + }; + + res.json({ + available: Object.values(files).filter(f => f !== null), + lastUpdate: files.pokemon?.modified || 'Never', + totalFiles: Object.values(files).filter(f => f !== null).length + }); +}); + +/** + * GET /api/gamemaster/pokemon + * Get filtered pokemon data (base forms + regional variants) + */ +router.get('/pokemon', (req, res) => { + const data = loadFile('pokemon.json'); + if (!data) { + return res.status(404).json({ + error: 'Pokemon data not available. Generate from GamemasterManager.' + }); + } + res.json(data); +}); + +/** + * GET /api/gamemaster/pokemon/allForms + * Get all pokemon forms including costumes + */ +router.get('/pokemon/allForms', (req, res) => { + const data = loadFile('pokemon-allFormsCostumes.json'); + if (!data) { + return res.status(404).json({ + error: 'All forms data not available. Generate from GamemasterManager.' + }); + } + res.json(data); +}); + +/** + * GET /api/gamemaster/moves + * Get all pokemon moves + */ +router.get('/moves', (req, res) => { + const data = loadFile('pokemon-moves.json'); + if (!data) { + return res.status(404).json({ + error: 'Moves data not available. Generate from GamemasterManager.' + }); + } + res.json(data); +}); + +/** + * GET /api/gamemaster/raw + * Get raw unmodified gamemaster data + */ +router.get('/raw', (req, res) => { + const data = loadFile('latest-raw.json'); + if (!data) { + return res.status(404).json({ + error: 'Raw gamemaster data not available. Fetch from GamemasterManager.' + }); + } + res.json(data); +}); + +/** + * POST /api/gamemaster/save + * Save processed gamemaster data (called by GamemasterManager) + * Body: {pokemon, pokemonAllForms, moves, raw} + */ +router.post('/save', express.json({ limit: '50mb' }), (req, res) => { + try { + const { pokemon, pokemonAllForms, moves, raw } = req.body; + + if (!pokemon || !pokemonAllForms || !moves) { + return res.status(400).json({ + error: 'Missing required data: pokemon, pokemonAllForms, moves' + }); + } + + const results = {}; + + if (pokemon) { + results.pokemon = saveFile('pokemon.json', pokemon); + } + if (pokemonAllForms) { + results.pokemonAllForms = saveFile( + 'pokemon-allFormsCostumes.json', + pokemonAllForms + ); + } + if (moves) { + results.moves = saveFile('pokemon-moves.json', moves); + } + if (raw) { + results.raw = saveFile('latest-raw.json', raw); + } + + res.json({ + message: 'Files saved successfully', + files: results, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('Error saving gamemaster data:', error); + res.status(500).json({ + error: 'Failed to save files', + details: error.message + }); + } +}); + +/** + * GET /api/gamemaster/download/:filename + * Download a specific gamemaster file + */ +router.get('/download/:filename', (req, res) => { + const filename = req.params.filename; + + // Validate filename to prevent path traversal + const allowedFiles = [ + 'pokemon.json', + 'pokemon-allFormsCostumes.json', + 'pokemon-moves.json', + 'latest-raw.json' + ]; + + if (!allowedFiles.includes(filename)) { + return res.status(400).json({ error: 'Invalid filename' }); + } + + const filepath = getFilePath(filename); + if (!fs.existsSync(filepath)) { + return res.status(404).json({ error: 'File not found' }); + } + + res.setHeader( + 'Content-Disposition', + `attachment; filename="${filename}"` + ); + res.setHeader('Content-Type', 'application/json'); + res.sendFile(filepath); +}); + +// ============================================================================ +// INITIALIZATION +// ============================================================================ + +ensureDataDir(); + +export default router;