Skip to content

Port the Pathway Browser's citation endpoints to content-node - #497

Merged
adamjohnwright merged 6 commits into
mainfrom
port/citation
Oct 17, 2026
Merged

adamjohnwright merged 6 commits into
mainfrom
port/citation

Conversation

@adamjohnwright

Copy link
Copy Markdown
Contributor

These are the endpoints behind "cite this" in the Pathway Browser and the person page's citation downloads: /citation/pathway/{id}, /citation/static/{id} and /citation/export.

citation.mjs is content-service's EventCitation and StaticCitation. It writes text, BibTeX and RIS exactly as Java does, quirks included:

  • three or more authors are joined with " ,", then " & ";
  • the month comes from the wrong character of the release date, so a November release cites as January;
  • a missing name is written null, as Java's string concatenation writes it;
  • a paper with no journal or volume gets Java's placeholder text.

A pathway's citation uses graph-core's findEnhancedObjectById (node's existing enhancedQuery):

  • the authors come from its authored edits, else from its creator, else from its reviewers;
  • the link is its DOI, else its page;
  • it carries the release number.

A paper's citation comes from Europe PMC by PubMed id. EUROPE_PMC_URL overrides the address for the specs. Only a failure from Europe PMC becomes Java's "Unable to fetch static citation" 500; a programming error is not swallowed into it.

The endpoints:

  • /pathway/{id} returns its two citations in HashMap order, or {} for an id that is no event.
  • /export uses Java's content types and attachment filename, and answers an empty 200 when there is nothing to cite.
  • /citation/export/{id} and /citation/download aren't called by the site, so they stay with Java.

Routing: nginx and proxy.conf.js, with proxy-order tests.

Checked:

  • The specs, with stand-ins for the graph and Europe PMC, failed before the code existed, and the route spec fails without the route.
  • The live comparison caught two bugs of mine before review, and both are fixed with tests: the id was read from the wrong path segment, and missing names came out as undefined instead of Java's null.
  • Eleven live cases now give identical bytes from Java and node: the JSON, all three formats, a pathway with a DOI, a reaction, a paper by PubMed id including a collective author, and the error cases.

🤖 Generated with Claude Code

adamjohnwright and others added 6 commits October 17, 2026 03:58
/citation/pathway/{id}, /citation/static/{id} and /citation/export -- "cite
this". citation.mjs is content-service's EventCitation and StaticCitation:
text, BibTeX and RIS as Java writes them, its quirks too (three authors
joined " ," then " & ", the month from the wrong character of the release
date, a missing name written "null", the placeholders of a paper with no
journal or volume).

- A pathway's citation from graph-core's findEnhancedObjectById (node's
  enhancedQuery): the authors of its authored edits, else its creator, else
  its reviewers; its DOI or else its page; the release.
- A paper's from Europe PMC by PubMed id (EUROPE_PMC_URL for the specs);
  only Europe PMC failing is Java's "unable to fetch" -- a mistake of ours
  is not swallowed into it.
- /pathway/{id}'s two citations in HashMap order; {} for no event. Export's
  content types, attachment name, and empty 200 when there is nothing.

Byte-identical to Java over eleven live cases, a paper with a collective
author among them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… side

- The event citation is a pure citationOfEvent, unit-tested: the year and
  the month from the date's seventh character, a pathway's DOI over its
  page, the release, the authors-creator-reviewers fallback and the dotted
  initials.
- An event Java cannot cite -- no release date, too short a one, a person
  with no initial -- is GlobalExceptionHandler's 500, not node's own.
- Europe PMC's answer is read as Java casts it: a non-map journalInfo, a
  null field, a month past int's range, a null authorList or a non-list
  author is "unable to fetch". Maps and lists print as Java's toString.
- A non-200 from Europe PMC has its body cancelled.
- The export's file name is written as Tomcat writes a header: past
  Latin-1 a '?', a control character a space.
- /pathway/{id}'s key order is pinned to Java's HashMap, image first.

Each new test shown failing by mutation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ile name

- A short or non-numeric release date, or an event with no name, is
  GlobalExceptionHandler's 500 with Java's message (JDK 11's
  StringIndexOutOfBounds text, the NumberFormatException's, the Citation
  constructor's); only the NullPointerExceptions get "Something
  unexpected happened".
- StaticCitation formats outside Java's try, so an author it cannot cast
  -- not a map, a null, a name that is no String -- is that 500 with the
  ClassCastException's text, not "unable to fetch" and not a citation.
- A Latin-1 character in the export's file name reaches the wire as the
  byte Tomcat writes. Node re-reads a Content-Disposition value as UTF-8
  when the response has a body (nodejs/node#46528), which turned é into
  U+FFFD.

Checked against the graph: no event lacks a name or a YYYY-MM-DD date,
and no event's edit lacks an author. Each new test shown failing by
mutation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… end()

- StaticCitation.toText casts each author's full name to String inside
  its loop, so the first author that misfits decides the answer, and the
  class named is String, not CharSequence as the last pass had it.
- Whether Node re-reads a Content-Disposition value (nodejs/node#46528)
  is decided at end(), on the bytes it is given, not guessed from the
  result: a 304 and a bodiless HEAD are left alone, a HEAD's Buffer is
  not. Tested with node:http for the 304, since fetch's conditional
  request carries no-cache and is never answered 304.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The end() wrapper is installed once per response, so an error answered
  after a first writeResult does not convert the header twice; tested.
- writeResult's own doc is back above it, and the wrapper's says when the
  conversion holds: the head written on its own, as Express does when it
  computes an ETag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	deploy/nginx/common/routes-core.conf
#	projects/website-angular/src/scripts/proxy-order.spec.ts
#	proxy.conf.js
#	tools/content-node/service.mjs
@adamjohnwright
adamjohnwright merged commit d7fc9bd into main Oct 17, 2026
6 checks passed
@adamjohnwright
adamjohnwright deleted the port/citation branch October 17, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant