254 lines
6.1 KiB
JavaScript
254 lines
6.1 KiB
JavaScript
/**
|
|
* 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;
|