stihirus-reader / server.js
opex792's picture
Update server.js
fd21769 verified
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
import crypto from 'crypto';
import { getAuthorData, getAuthorFilters } from 'stihirus-reader';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const port = process.env.PORT || 7860;
const CACHE_DIR = __dirname; // Use current directory for cache files
const CACHE_DURATION_MS = 60 * 60 * 1000; // 1 hour
const swaggerDocument = YAML.load(path.join(__dirname, 'openapi.yaml'));
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// No need for ensureCacheDir anymore
function generateCacheKey(prefix, identifier, queryParams = {}) {
const identifierPart = String(identifier).replace(/[^a-zA-Z0-9_-]/g, '_');
let queryPart = '';
const sortedKeys = Object.keys(queryParams).sort();
sortedKeys.forEach(key => {
if (queryParams[key] !== undefined && queryParams[key] !== null) {
queryPart += `_${key}_${String(queryParams[key])}`;
} else if (queryParams[key] === null) {
queryPart += `_${key}_null`;
}
});
// Add prefix to avoid potential collisions with other files
return `_cache_${prefix}_${identifierPart}${queryPart}.json`;
}
async function readCache(key) {
const filePath = path.join(CACHE_DIR, key);
try {
const stats = await fs.stat(filePath);
const now = Date.now();
const mtime = stats.mtime.getTime();
const isStale = (now - mtime) > CACHE_DURATION_MS;
const content = await fs.readFile(filePath, 'utf-8');
const data = JSON.parse(content);
return { data, isStale, exists: true, mtime };
} catch (err) {
if (err.code === 'ENOENT') {
return { exists: false };
}
console.error(`Error reading cache file ${key}:`, err);
return { exists: false, error: true };
}
}
async function writeCache(key, data) {
const filePath = path.join(CACHE_DIR, key);
try {
const content = JSON.stringify(data);
await fs.writeFile(filePath, content, 'utf-8');
} catch (err) {
console.error(`Error writing cache file ${key}:`, err);
}
}
app.get('/author/:identifier', async (req, res) => {
const identifier = req.params.identifier;
let page = req.query.page;
let delay = req.query.delay;
let pageNum = null;
if (page !== undefined) {
const parsedPage = parseInt(page, 10);
if (!isNaN(parsedPage) && parsedPage >= 0) {
pageNum = parsedPage;
} else if (page === 'null' || page === '') {
pageNum = null;
} else {
return res.status(400).json({ status: 'error', error: { code: 400, message: 'Invalid page parameter. Use null, 0, or a positive integer.' } });
}
}
let delayMs = undefined;
if (delay !== undefined) {
const parsedDelay = parseInt(delay, 10);
if (!isNaN(parsedDelay) && parsedDelay >= 0) {
delayMs = parsedDelay;
} else {
return res.status(400).json({ status: 'error', error: { code: 400, message: 'Invalid delay parameter. Use a non-negative integer.' } });
}
}
const cacheKey = generateCacheKey('author', identifier, { page: pageNum });
let cacheEntry = null;
try {
cacheEntry = await readCache(cacheKey);
if (cacheEntry.exists && !cacheEntry.isStale) {
return res.json(cacheEntry.data);
}
const freshResult = await getAuthorData(identifier, pageNum, delayMs);
if (freshResult.status === 'success') {
await writeCache(cacheKey, freshResult);
return res.json(freshResult);
} else {
if (cacheEntry.exists) {
return res.json(cacheEntry.data);
} else {
return res.status(freshResult.error.code >= 400 && freshResult.error.code < 600 ? freshResult.error.code : 500).json(freshResult);
}
}
} catch (err) {
if (cacheEntry && cacheEntry.exists) {
return res.json(cacheEntry.data);
} else {
console.error(`Error processing /author/${identifier} (CacheKey: ${cacheKey}):`, err);
return res.status(500).json({ status: 'error', error: { code: 500, message: 'Internal Server Error', originalMessage: err.message } });
}
}
});
app.get('/author/:identifier/filters', async (req, res) => {
const identifier = req.params.identifier;
const cacheKey = generateCacheKey('filters', identifier);
let cacheEntry = null;
try {
cacheEntry = await readCache(cacheKey);
if (cacheEntry.exists && !cacheEntry.isStale) {
return res.json(cacheEntry.data);
}
const freshResult = await getAuthorFilters(identifier);
if (freshResult.status === 'success') {
await writeCache(cacheKey, freshResult);
return res.json(freshResult);
} else {
if (cacheEntry.exists) {
return res.json(cacheEntry.data);
} else {
return res.status(freshResult.error.code >= 400 && freshResult.error.code < 600 ? freshResult.error.code : 500).json(freshResult);
}
}
} catch (err) {
if (cacheEntry && cacheEntry.exists) {
return res.json(cacheEntry.data);
} else {
console.error(`Error processing /author/${identifier}/filters (CacheKey: ${cacheKey}):`, err);
return res.status(500).json({ status: 'error', error: { code: 500, message: 'Internal Server Error', originalMessage: err.message } });
}
}
});
app.get('/', (req, res) => {
res.redirect('/docs');
});
// No need to ensure cache dir here
app.listen(port, () => {
console.log(`StihiRus API wrapper listening on port ${port}`);
console.log(`Cache files will be stored in: ${CACHE_DIR}`);
console.log(`API Docs available at /docs`);
});