Add API endpoint for retrieving Pokémon data

This commit is contained in:
2026-01-28 18:58:45 +00:00
parent 4596112762
commit 76f42cacc7

View File

@@ -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;