Spaces:
Sleeping
Sleeping
File size: 6,132 Bytes
81094c3 8f05c3b 81094c3 fd21769 81094c3 fd21769 8f05c3b fd21769 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 8f05c3b 81094c3 fd21769 81094c3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
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`);
}); |