Spaces:
Sleeping
Sleeping
| import { Router, type Request, type Response } from "express"; | |
| import { Cite } from "@citation-js/core"; | |
| import "@citation-js/plugin-bibtex"; | |
| import "@citation-js/plugin-doi"; | |
| import "@citation-js/plugin-csl"; | |
| const router = Router(); | |
| /** | |
| * POST /api/citations/resolve | |
| * Body: { input: string } – DOI, DOI URL, or BibTeX string | |
| * Returns: { entries: CSL-JSON[] } | |
| */ | |
| router.post("/resolve", async (req: Request, res: Response) => { | |
| try { | |
| const { input } = req.body; | |
| if (!input || typeof input !== "string") { | |
| res.status(400).json({ error: "Missing 'input' string" }); | |
| return; | |
| } | |
| const cite = await Cite.async(input.trim()); | |
| const entries = cite.get({ format: "real", type: "json", style: "csl" }); | |
| if (!entries.length) { | |
| res.status(404).json({ error: "Could not resolve any reference" }); | |
| return; | |
| } | |
| const enriched = entries.map((entry: any) => ({ | |
| ...entry, | |
| id: entry.id || generateKey(entry), | |
| })); | |
| res.json({ entries: enriched }); | |
| } catch (err: any) { | |
| console.error("[citations] resolve error:", err.message); | |
| res.status(422).json({ error: err.message || "Failed to resolve" }); | |
| } | |
| }); | |
| /** | |
| * POST /api/citations/format | |
| * Body: { entries: CSL-JSON[], style?: string, locale?: string } | |
| * Returns: { html: string } | |
| */ | |
| router.post("/format", async (req: Request, res: Response) => { | |
| try { | |
| const { entries, style = "apa", locale = "en-US" } = req.body; | |
| if (!Array.isArray(entries) || !entries.length) { | |
| res.status(400).json({ error: "Missing 'entries' array" }); | |
| return; | |
| } | |
| const cite = new Cite(entries); | |
| const html = cite.format("bibliography", { | |
| format: "html", | |
| template: style, | |
| lang: locale, | |
| }); | |
| res.json({ html }); | |
| } catch (err: any) { | |
| console.error("[citations] format error:", err.message); | |
| res.status(422).json({ error: err.message || "Failed to format" }); | |
| } | |
| }); | |
| /** | |
| * POST /api/citations/import-bib | |
| * Body: { bibtex: string } | |
| * Returns: { entries: CSL-JSON[] } | |
| */ | |
| router.post("/import-bib", async (req: Request, res: Response) => { | |
| try { | |
| const { bibtex } = req.body; | |
| if (!bibtex || typeof bibtex !== "string") { | |
| res.status(400).json({ error: "Missing 'bibtex' string" }); | |
| return; | |
| } | |
| const cite = new Cite(bibtex.trim()); | |
| const entries = cite.get({ format: "real", type: "json", style: "csl" }); | |
| const enriched = entries.map((entry: any) => ({ | |
| ...entry, | |
| id: entry.id || generateKey(entry), | |
| })); | |
| res.json({ entries: enriched }); | |
| } catch (err: any) { | |
| console.error("[citations] import-bib error:", err.message); | |
| res.status(422).json({ error: err.message || "Failed to parse BibTeX" }); | |
| } | |
| }); | |
| function generateKey(entry: any): string { | |
| const firstAuthor = entry.author?.[0]?.family || "unknown"; | |
| const year = entry.issued?.["date-parts"]?.[0]?.[0] || "nd"; | |
| const base = `${firstAuthor.toLowerCase()}${year}`; | |
| return base.replace(/[^a-z0-9]/gi, ""); | |
| } | |
| export { router as citationsRouter }; | |