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;