Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions deploy/nginx/common/routes-core.conf
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ location ~ ^/ContentService/data/pathways/top/[^/]+/?$ {
# OUTGOING) as Java's Jackson writes it, in each of the views Java's converter
# offers (?view=nested|nested-aggregated, ?includeRef=true). The model is read
# from the deployed graph-core by snapshot-domain.ts, not restated. Only the
# plain path: /query/{id}/{attribute} and /query/enhanced|abridged/{id} are
# other endpoints. Byte-identical to Java over a corpus of 20,000 objects of
# plain path: /query/{id}/{attribute}, /query/enhanced|abridged/{id} and
# /query/{id}/more are other endpoints, below. Byte-identical to Java over a corpus of 20,000 objects of
# every class, bar the order of a few getter-only properties in the nested
# views, which Java itself does not keep from one start to the next.
# Including POST /query/ids, a batch of objects by id, and its map form
Expand Down Expand Up @@ -348,10 +348,20 @@ location ~ ^/ContentService/data/diseases(/doid)?/?$ {
# id forms -- versions, REACT_ ids,
# dbIds -- bar a 404 body's `url`, which names the
# service that answered (the same declared difference as /pathways/top/{id}).
# `abridged` and `enhanced` are not ids, nor `more` an attribute: Java's
# more specific /query/abridged/{id}, /query/enhanced/{id} and
# /query/{id}/more take those paths.
location ~ ^/ContentService/data/query/(?!(abridged|enhanced)/)[^/]+/(?!more/?$)[^/]+/?$ {
# Java's more specific /query/abridged/{id}, /query/enhanced/{id} and
# /query/{id}/more share this pattern, and node answers each in its own
# handler, so one location takes all three.
#
# /query/abridged/{id}, and its attributes: the object without its
# relationships (graph-core's findByIdNoRelations), written and read as
# above, a relationship's getter loading it as LazyFetchAspect does.
# /query/{id}/more: /query/enhanced/v2/{id} with its flags fixed. Java
# answers all three but leaves them out of its description. Their errors
# compared live with Java's (spec 006 T104), bar a 404 body's `url`.
location ~ ^/ContentService/data/query/[^/]+/[^/]+/?$ {
include /etc/nginx/common/content-node-proxy.conf;
}
location ~ ^/ContentService/data/query/abridged/[^/]+/[^/]+/?$ {
include /etc/nginx/common/content-node-proxy.conf;
}

Expand Down
6 changes: 3 additions & 3 deletions deploy/nginx/routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ const JAVAS = [
'/ContentService/data/content/toc/X1',
'/ContentService/data/diseases/X1',
'/ContentService/data/schema/model/X1',
'/ContentService/data/query/abridged/R-HSA-109581',
'/ContentService/data/query/R-HSA-109581/more',
'/ContentService/data/query/abridged/R-HSA-109581/stId/x',
'/ContentService/data/query/R-HSA-109581/more/x',
'/ContentService/interactors/psicquic/resourcesx',
'/ContentService/search/facet_queryx',
// An event's other exports, beside its SBML (spec 015 T066), SBGN and BioPAX (T071).
Expand Down Expand Up @@ -452,7 +452,7 @@ describe('a matrix parameter', () => {

it('is not an encoded one, nor one in the query string', async () => {
for (const url of [
'/ContentService/data/query/abridged/R-HSA-109581%3Bx',
'/ContentService/data/pathway/R-HSA-109606%3Bx',
'/ContentService/data/species/9606?a=1;b=2',
])
expect([url, nginxTarget(url), await proxySendsToNode(url)]).toEqual([url, 'tomcat', false]);
Expand Down
74 changes: 74 additions & 0 deletions projects/contract/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,54 @@
}
}
},
"/ContentService/data/query/abridged/{id}": {
"get": {
"x-contract": "not yet",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Not yet described by the contract."
}
}
}
},
"/ContentService/data/query/abridged/{id}/{attribute}": {
"get": {
"x-contract": "not yet",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "attribute",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Not yet described by the contract."
}
}
}
},
"/ContentService/data/query/enhanced/v2/{id}": {
"get": {
"description": "Based on the given identifier, i.e. stable id or database id, this method queries for an entry in Reactome knowledgebase providing more information. In particular, the retrieved database object has all its properties and direct relationships (relationships of depth 1) filled, while it also includes any second level relationships regarding regulations and catalysts.",
Expand Down Expand Up @@ -3366,6 +3414,32 @@
}
}
},
"/ContentService/data/query/{id}/more": {
"get": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "The answer.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GraphObject_JsogReference"
}
}
}
}
}
}
},
"/ContentService/data/query/{id}/normalPathway": {
"get": {
"x-contract": "not yet",
Expand Down
1 change: 1 addition & 0 deletions projects/contract/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface Routes {
'GET /ContentService/data/schema/{id}/min': SimpleDatabaseObject[];
'GET /ContentService/data/instance/{id}/referrers': InstanceReferrals[];
'GET /ContentService/data/query/enhanced/v2/{id}': GraphObject<JsogReference>;
'GET /ContentService/data/query/{id}/more': GraphObject<JsogReference>;
'GET /ContentService/data/detail/{id}/locationsInPWB': PathwayBrowserNode[];
'GET /ContentService/data/participants/{id}': Participant[];
'GET /ContentService/data/person/{id}/authoredPathways': SimpleEvent[];
Expand Down
18 changes: 10 additions & 8 deletions projects/website-angular/src/scripts/proxy-order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,15 @@ describe('the dev server proxy', () => {
'/ContentService/data/query/70486/NORMALPATHWAY',
'/ContentService/data/query/70486/normalPathways',
'/ContentService/data/query/R-HSA-109581/hasEvent/',
// Java's more specific /query/abridged/{id} takes this, reading the
// attribute's name as the id, as /query/enhanced/{id} does.
'/ContentService/data/query/abridged/stId',
'/ContentService/data/query/abridged/70486',
'/ContentService/data/query/abridged/70486/hasEvent',
'/ContentService/data/query/abridged/70486/more/',
// Java's own /query/{id}/more, not an attribute called more.
'/ContentService/data/query/70486/more',
'/ContentService/data/query/70486/more/',
'/ContentService/data/person/68285/firstname',
'/ContentService/data/person/68285/authoredPathwaysAndMore',
]) {
Expand Down Expand Up @@ -332,16 +341,9 @@ describe('the dev server proxy', () => {
}

for (const other of [
// Java's more specific /query/abridged/{id} takes this, reading the
// attribute's name as the id. (/query/enhanced/{id} does the same, and
// is node's now: see above.)
'/ContentService/data/query/abridged/stId',
'/ContentService/data/query/abridged/70486',
// Java's own /query/{id}/more, not an attribute called more.
'/ContentService/data/query/70486/more',
'/ContentService/data/query/70486/more/',
// Below an attribute: no mapping.
'/ContentService/data/query/70486/displayName/x',
'/ContentService/data/query/abridged/70486/displayName/x',
'/ContentService/data/person/68285/firstname/x',
// The search endpoints node does not serve yet.
'/ContentService/search/query/x?query=apoptosis',
Expand Down
11 changes: 7 additions & 4 deletions projects/website-angular/src/scripts/serve-prod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,10 @@ describe('serve-prod and the API roots', () => {
['/ContentService/data/person/68285/authoredPathwaysAndMore', 'content-node'],
['/ContentService/data/person/68285/displayName/x', 'backend'],
['/ContentService/data/query/70486/displayName', 'content-node'],
['/ContentService/data/query/70486/more', 'backend'],
['/ContentService/data/query/70486/more', 'content-node'],
['/ContentService/data/query/abridged/70486', 'content-node'],
['/ContentService/data/query/abridged/70486/displayName', 'content-node'],
['/ContentService/data/query/abridged/70486/displayName/x', 'backend'],
['/ContentService/data/query/70486/moreover', 'content-node'],
['/ContentService/data/query/R-HSA-70486', 'content-node'],
['/ContentService/data/query/R-HSA-70486?view=nested', 'content-node'],
Expand Down Expand Up @@ -358,9 +361,9 @@ describe('serve-prod and the API roots', () => {

for (const api of [
// A path still answered by Java. /database/version, then
// /query/{id}/displayName, were the example until each moved to the node
// content service.
'/ContentService/data/query/abridged/70486',
// /query/{id}/displayName, then /query/abridged/{id}, were the example
// until each moved to the node content service.
'/ContentService/data/pathways/top/9606/curated',
'/ContentService/v3/api-docs',
'/AnalysisService/v3/api-docs',
'/ContentService/swagger-ui/index.html',
Expand Down
12 changes: 6 additions & 6 deletions proxy.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,18 @@ module.exports = {
'^/ContentService/data/people/name/[^/?]+(/exact)?/?(\\?|$)': contentNode,
// One object with everything it points to, in any of Java's views
// (?view=, ?includeRef=). Only the plain path: /query/{id}/... and Java's
// /query/enhanced|abridged/{id} are other endpoints.
// /query/enhanced|abridged/{id} are other endpoints, below.
// Including POST /query/ids, a batch of objects by id, and its map form.
'^/ContentService/data/query/[^/?]+/?(\\?|$)': contentNode,
'^/ContentService/data/query/ids/map/?(\\?|$)': contentNode,
// Every disease, whole and as a DOID list.
'^/ContentService/data/diseases(/doid)?/?(\\?|$)': contentNode,
// Any attribute of an object, as Java's /data/query/{id}/{attributeName}
// writes it. `abridged` and `enhanced` are not ids, nor `more` an attribute:
// Java's more specific /query/abridged/{id}, /query/enhanced/{id} and
// /query/{id}/more take those paths.
'^/ContentService/data/query/(?!(abridged|enhanced)/)[^/?]+/(?!more/?(\\?|$))[^/?]+/?(\\?|$)':
contentNode,
// writes it, and Java's more specific /query/abridged/{id},
// /query/enhanced/{id} and /query/{id}/more, which node answers too. Then an
// attribute of an object without its relationships. Same patterns as nginx's.
'^/ContentService/data/query/[^/?]+/[^/?]+/?(\\?|$)': contentNode,
'^/ContentService/data/query/abridged/[^/?]+/[^/?]+/?(\\?|$)': contentNode,
// Every route from an event up to a top-level pathway; the Pathway Browser
// asks on every pathway it opens.
'^/ContentService/data/event/[^/?]+/ancestors/?(\\?|$)': contentNode,
Expand Down
2 changes: 1 addition & 1 deletion specs/006-content-service-to-node/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ These were held in Java because their substance is a Java library or Java numeri

Found 19 Oct 2026 by comparing every mapping in the Java source with the published description (which the phases above follow). The site calls none of them; outside users of the API may. Each still answered by Java.

- [ ] T104 Graph reads: `GET /data/query/abridged/{id}`, `/data/query/abridged/{id}/{attributeName}`, `/data/query/{id}/more`
- [x] T104 Graph reads: `GET /data/query/abridged/{id}`, `/data/query/abridged/{id}/{attributeName}`, `/data/query/{id}/more`. Node answers all three (data-query family), routed in nginx and the dev proxy. The abridged object is graph-core's findByIdNoRelations: the node alone, by the id as getIdentifier reads it, written as `/query/{id}` writes an object; its attribute is ControllerUtils.getProperty on it, a relationship getter loading the relationship, outgoing ones too, as LazyFetchAspect does. `/more` is `/query/enhanced/v2/{id}` with its flags fixed (incoming relationships, no summary, disease data only if nothing is found without), reading no parameter. Spring's order kept: `/query/abridged/more` and `/query/enhanced/more` are the object called more. Dots alone are Java's 500 on all three, and on `/enhanced/v2/{id}`, which answered a 404 before. Compared with Java on 7 Nov 2026, spaced: its abridged answers for 26 objects of 14 classes (no Person or InstanceEdit) written by node's writer from their own properties, identical in all three views; its errors (404s, the attribute's 404, the 500s for dots and a dbId past a long) the same but for `url`. Node was not run against the graph here (no credentials in this environment): its queries were run against a stand-in in `query-abridged.spec.ts`. — #897
- [ ] T105 `GET /data/referenceMolecules`, `/data/referenceMolecules/identifiers`, `/data/referenceSequences`, `/data/referenceSequences/identifiers`
- [ ] T106 `GET /data/pathways/top/{species}/curated`, `/data/pathways/low/diagram/identifier/{id}/allForms`, `/data/diagram/{id}/entities/{instance}`, `/data/database/info`
- [ ] T107 `GET /data/detail/{id}/direction/…` (four forms), `/data/detail/{id}/getSubGraph`, `/data/detail/{id}/getReverseGraph`
Expand Down
2 changes: 2 additions & 0 deletions tools/api-types/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ describe('recorded answers against the API description', () => {
'not in the recordings, which never cite a pathway (calls.ts names the gap)',
'GET /ContentService/data/instance/{id}/referrers':
'not in the recordings, which never open an instance’s referrers (calls.ts names the gap)',
'GET /ContentService/data/query/{id}/more':
'no call of the site reads it: Java keeps it for old callers (spec 006 T104)',
'GET /ContentService/data/schema/{id}/min':
'not in the recordings, which never open a class’s instances (calls.ts names the gap)',
'GET /ContentService/interactors/static/molecule/{id}/details':
Expand Down
30 changes: 16 additions & 14 deletions tools/content-node/classify.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,28 @@ describe('classify', () => {

it('leaves out writes and what Java still answers', () => {
expect(classify('POST', '/ContentService/data/query/ids')).toBe(undefined);
expect(classify('GET', '/ContentService/data/query/abridged/R-HSA-1')).toBe(undefined);
expect(classify('GET', '/ContentService/data/query/R-HSA-1/more')).toBe(undefined);
expect(classify('GET', '/ContentService/data/pathways/top/9606/curated')).toBe(undefined);
expect(classify('GET', '/ContentService/data/query/enhanced/v2/R-HSA-1')?.endpoint).toBe(
'/ContentService/data/query/enhanced/v2/{id}'
);
expect(classify('GET', '/AnalysisService/token/x')).toBe(undefined);
});

it("leaves out every form nginx keeps on Java's /query, not node's own", () => {
for (const path of [
'/ContentService/data/query/abridged/R-HSA-1',
'/ContentService/data/query/abridged/stId',
'/ContentService/data/query/abridged/R-HSA-1/stId',
'/ContentService/data/query/enhanced/R-HSA-1/stId',
'/ContentService/data/query/R-HSA-1/more/',
])
expect([path, classify('GET', path)]).toEqual([path, undefined]);
expect(classify('GET', '/ContentService/data/query/enhanced/R-HSA-1')?.endpoint).toBe(
'/ContentService/data/query/enhanced/{id}'
);
it("takes Java's more specific /query paths as their own endpoints, as Express routes them", () => {
const cases: [path: string, endpoint: string][] = [
['/ContentService/data/query/abridged/R-HSA-1', '/ContentService/data/query/abridged/{id}'],
['/ContentService/data/query/abridged/stId', '/ContentService/data/query/abridged/{id}'],
[
'/ContentService/data/query/abridged/R-HSA-1/stId',
'/ContentService/data/query/abridged/{id}/{attribute}',
],
['/ContentService/data/query/R-HSA-1/more/', '/ContentService/data/query/{id}/more'],
['/ContentService/data/query/enhanced/R-HSA-1', '/ContentService/data/query/enhanced/{id}'],
];
for (const [path, endpoint] of cases)
expect([path, classify('GET', path)?.endpoint]).toEqual([path, endpoint]);
// No node route matches the rest of Java's /query.
expect(classify('GET', '/ContentService/data/query/enhanced/R-HSA-1/stId')).toBe(undefined);
});

it('counts a HEAD as its GET', () => {
Expand Down
6 changes: 0 additions & 6 deletions tools/content-node/classify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ export function classify(method: string, url: string) {
const [path = ''] = sent.split('?');
const found = PATTERNS().find(([, pattern]) => pattern.test(path));
if (!found) return undefined;
// Java's own, which nginx keeps from node's /query/{id}/{attribute},
// /query/{id}/stId and /query/{id}/normalPathway
// (deploy/nginx/common/routes-core.conf): /query/abridged/{id} and
// /query/{id}/more. No node route matches the rest of Java's /query.
if (/^\/ContentService\/data\/query\/(abridged\/[^/]+|[^/]+\/more)\/?$/.test(path))
return undefined;
const form = standardForm(found[0], sent);
return { endpoint: found[0], ok: form.ok, rule: form.ok ? 'standard' : form.rule };
}
Expand Down
Loading
Loading