✨ Add API endpoint for retrieving Pokémon data
This commit is contained in:
253
code/websites/pokedex.online/server/gamemaster-api.js
Normal file
253
code/websites/pokedex.online/server/gamemaster-api.js
Normal 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;
|
||||
Reference in New Issue
Block a user