AleksanderObuchowski's picture
Initial commit for Hugging Face Spaces
e4f1db2
const express = require('express');
const axios = require('axios');
const xml2js = require('xml2js');
const NodeCache = require('node-cache');
const router = express.Router();
const cache = new NodeCache({ stdTTL: 3600 });
const PUBMED_BASE_URL = 'https://eutils.ncbi.nlm.nih.gov/entrez/eutils';
async function searchPubMed(query, maxResults = 100) {
try {
const cacheKey = `search_${query}_${maxResults}`;
const cached = cache.get(cacheKey);
if (cached) return cached;
// Add filters to exclude review papers, meta-analyses, and systematic reviews
const filteredQuery = `(${query}) NOT Review[Publication Type] NOT Meta-Analysis[Publication Type] NOT Systematic Review[Publication Type]`;
const searchUrl = `${PUBMED_BASE_URL}/esearch.fcgi?db=pubmed&term=${encodeURIComponent(filteredQuery)}&retmax=${maxResults}&retmode=json`;
const response = await axios.get(searchUrl);
const result = {
count: parseInt(response.data.esearchresult.count),
ids: response.data.esearchresult.idlist || []
};
cache.set(cacheKey, result);
return result;
} catch (error) {
throw new Error(`PubMed search failed: ${error.message}`);
}
}
async function fetchPaperDetails(ids) {
try {
if (!ids.length) return [];
const cacheKey = `details_${ids.join(',')}`;
const cached = cache.get(cacheKey);
if (cached) return cached;
const fetchUrl = `${PUBMED_BASE_URL}/efetch.fcgi?db=pubmed&id=${ids.join(',')}&retmode=xml`;
// Add timeout and retry logic for PubMed API
const response = await axios.get(fetchUrl, {
timeout: 10000,
headers: {
'User-Agent': 'PubMedAIExplorer/1.0'
}
});
const parser = new xml2js.Parser();
const result = await parser.parseStringPromise(response.data);
const papers = [];
const articles = result.PubmedArticleSet?.PubmedArticle || [];
articles.forEach(article => {
const medlineCitation = article.MedlineCitation?.[0];
if (medlineCitation) {
const articleData = medlineCitation.Article?.[0];
if (articleData) {
papers.push({
pmid: medlineCitation.PMID?.[0]?._,
title: articleData.ArticleTitle?.[0] || 'No title',
abstract: articleData.Abstract?.AbstractText?.[0]?._ || articleData.Abstract?.AbstractText?.[0] || '',
authors: articleData.AuthorList?.Author?.map(author => {
const lastName = author.LastName?.[0] || '';
const foreName = author.ForeName?.[0] || '';
return `${foreName} ${lastName}`.trim();
}) || [],
journal: articleData.Journal?.Title?.[0] || '',
pubDate: medlineCitation.DateCompleted?.[0]?.Year?.[0] ||
medlineCitation.DateRevised?.[0]?.Year?.[0] || ''
});
}
}
});
cache.set(cacheKey, papers);
return papers;
} catch (error) {
throw new Error(`Failed to fetch paper details: ${error.message}`);
}
}
router.get('/search', async (req, res) => {
try {
const { query, maxResults = 20 } = req.query;
if (!query) {
return res.status(400).json({ error: 'Query parameter is required' });
}
const searchResult = await searchPubMed(query, maxResults);
const papers = await fetchPaperDetails(searchResult.ids.slice(0, 10));
res.json({
totalCount: searchResult.count,
papers: papers
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/paper/:pmid', async (req, res) => {
try {
const { pmid } = req.params;
console.log(`Fetching paper details for PMID: ${pmid}`);
const papers = await fetchPaperDetails([pmid]);
console.log(`Found ${papers.length} papers for PMID: ${pmid}`);
if (papers.length === 0) {
console.log(`No paper found for PMID: ${pmid}`);
return res.status(404).json({ error: 'Paper not found' });
}
console.log(`Returning paper:`, papers[0].title);
res.json(papers[0]);
} catch (error) {
const { pmid } = req.params;
console.error(`Error fetching paper ${pmid}:`, error.message);
res.status(500).json({ error: error.message });
}
});
module.exports = router;