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
28 changes: 28 additions & 0 deletions nginx/default.conf
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,32 @@ server {
autoindex on;
alias /usr/share/nginx/html/static/;
}

# Proxy for transitive query endpoint
location ~ ^/[^/]+/query/transitive/.* {
proxy_pass https://uri.olympiangods.org;
proxy_set_header Host uri.olympiangods.org;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# Pass through auth/cookies if present (mirrors Vite configure hook)
proxy_set_header Authorization $http_authorization;
proxy_set_header Cookie $http_cookie;

proxy_ssl_verify off;

# Helpful CORS for credentialed requests in dev
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Expose-Headers X-Redirect-Location always;

# (Optional but handy for preflight)
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers $http_access_control_request_headers always;
return 204;
}
}

}
28 changes: 28 additions & 0 deletions src/api/endpoints/apiService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createPostRequest, createGetRequest } from "./apiActions";
import { API_CONFIG } from "../../config";
import termParser from "../../parsers/termParser";
import { jsonldToTriplesAndEdges, PART_OF_IRI } from './hiearchies-parser'

export interface LoginRequest {
username: string
Expand Down Expand Up @@ -300,3 +301,30 @@ export const getTermDiscussions = async (group: string, variantID: string) => {
export const getVariant = (group: string, term: string) => {
return createGetRequest<any, any>(`/${group}/variant/${term}`, "application/json")();
};

export const getTermHierarchies = async ({
groupname,
termId,
objToSub = true,
}: {
groupname: string;
termId: string;
objToSub?: boolean;
}) => {
const base = `/${groupname}/query/transitive/${encodeURIComponent(termId)}/ilx.partOf:`;
const url1 = `${base}?obj-to-sub=${objToSub}`;
const url2 = `${base}.jsonld?obj-to-sub=${objToSub}`;

try {
const res1 = await createGetRequest<any, any>(url1, 'application/ld+json')();
return jsonldToTriplesAndEdges(res1);
} catch {
try {
const res2 = await createGetRequest<any, any>(url2, 'application/ld+json')();
return jsonldToTriplesAndEdges(res2);
} catch (e2: any) {
console.error('getTermHierarchies failed', e2);
return { error: true, message: e2?.message || String(e2) };
}
}
};
85 changes: 85 additions & 0 deletions src/api/endpoints/hiearchies-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Minimal JSON-LD → { triples, edges } converter used by getTermHierarchies

export const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
export const RDFS_LABEL = 'http://www.w3.org/2000/01/rdf-schema#label';
export const PART_OF_IRI = 'http://uri.interlex.org/base/ilx_0112785';

type JsonLdNode = {
'@id': string;
'@type'?: string[] | string;
[k: string]: any;
};

function firstString(o: any): string | undefined {
if (o == null) return;
if (typeof o === 'string') return o;
if (Array.isArray(o)) return firstString(o[0]);
if (typeof o === 'object') {
if ('@value' in o) return String(o['@value']);
if ('@id' in o) return String(o['@id']);
}
}

function ids(objs: any): string[] {
if (!objs) return [];
const arr = Array.isArray(objs) ? objs : [objs];
return arr.map(v => (typeof v === 'string' ? v : v?.['@id'])).filter(Boolean);
}

export type Triple = {
subject: { id: string; label: string };
predicate: { id: string; label: string };
object: { id: string; label: string };
};

export type Edge = {
from: { id: string; label: string };
to: { id: string; label: string };
};

export function jsonldToTriplesAndEdges(jsonld: any): { triples: Triple[]; edges: Edge[] } {
const graph: JsonLdNode[] =
Array.isArray(jsonld) ? jsonld :
Array.isArray(jsonld?.['@graph']) ? jsonld['@graph'] :
jsonld?.['@id'] ? [jsonld] : [];

// id → label map (prefer rdfs:label)
const labelById = new Map<string, string>();
for (const n of graph) {
const id = n['@id']; if (!id) continue;
const lbl = firstString(n['label']) ?? firstString(n['rdfs:label']) ?? firstString(n[RDFS_LABEL]);
if (lbl) labelById.set(id, lbl);
}

const triples: Triple[] = [];
const edges: Edge[] = [];

const addTriple = (s: string, p: string, oId?: string, oLabel?: string) => {
const subj = { id: s, label: labelById.get(s) ?? s };
const pred = { id: p, label: p };
const obj = oId ? { id: oId, label: labelById.get(oId) ?? oId } : { id: '', label: oLabel ?? '' };
triples.push({ subject: subj, predicate: pred, object: obj });
};

for (const n of graph) {
const s = n['@id']; if (!s) continue;

// rdf:type
for (const t of ids(n['@type'])) addTriple(s, RDF_TYPE, t);

// rdfs:label
const lbl = firstString(n['label']) ?? firstString(n['rdfs:label']) ?? firstString(n[RDFS_LABEL]);
if (lbl) addTriple(s, RDFS_LABEL, undefined, lbl);

// ilx.partOf (accept compact or expanded)
for (const o of [...ids(n['partOf']), ...ids(n[PART_OF_IRI])]) {
addTriple(s, PART_OF_IRI, o);
edges.push({
from: { id: s, label: labelById.get(s) ?? s },
to: { id: o, label: labelById.get(o) ?? o },
});
}
}

return { triples, edges };
}
Loading