From 3c94bf128b997a4e771f0cb48316e8fc546801ef Mon Sep 17 00:00:00 2001 From: jrmartin Date: Mon, 23 Jun 2025 08:03:01 -0700 Subject: [PATCH 1/4] #116 - feat: refactor createNewOntology to handle manual redirects and improve error handling --- src/api/endpoints/apiService.ts | 76 +++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/src/api/endpoints/apiService.ts b/src/api/endpoints/apiService.ts index 3e2f0387..d76138d2 100644 --- a/src/api/endpoints/apiService.ts +++ b/src/api/endpoints/apiService.ts @@ -141,49 +141,61 @@ export const createNewOntology = async ({ const endpoint = `/${groupname}/ontologies/uris/${ontologyName}/spec`; const data = { - title : title, - subjects : subjects, + title: title, + subjects: subjects, }; const headers = { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + 'Authorization': `Bearer ${token}`, }; try { - const postResponse = await createPostRequest(endpoint, headers)(data); - - // If the POST creates a new location, try fetching it (simulate follow-up GETs from the test) - if (postResponse?.location) { - const getResponse = await fetch(postResponse.location, { - headers: { - Accept: 'application/json', - }, - }); - - // Optionally fetch HTML if needed (like the .html equivalent in the Python test) - const htmlResponse = await fetch(endpoint, { - headers: { - Accept: 'text/html', - }, - }); - - return { - created: true, - data: postResponse, - jsonResponse: await getResponse.json(), - htmlAvailable: htmlResponse.ok, - }; - } + // Use fetch directly for manual redirect handling + const postResponse = await fetch(endpoint, { + method: 'POST', + headers, + body: JSON.stringify(data), + redirect: 'manual', // let us handle the 303 ourselves + }); + + if (postResponse.status === 303) { + const location = postResponse.headers.get('Location'); + if (location) { + // Fetch the ontology resource at the redirected location (JSON) + const getResponse = await fetch(location, { + headers: { + Accept: 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); - return { - created: true, - data: postResponse, - }; + // Optionally, also fetch the HTML representation + const htmlResponse = await fetch(location, { + headers: { + Accept: 'text/html', + 'Authorization': `Bearer ${token}`, + }, + }); + + return { + created: true, + location, + jsonResponse: getResponse.ok ? await getResponse.json() : null, + htmlAvailable: htmlResponse.ok, + }; + } else { + throw new Error('No Location header in 303 response'); + } + } else { + // If not a 303, handle as error or unexpected case + const errorBody = await postResponse.text(); + throw new Error(`Unexpected response status: ${postResponse.status} - ${errorBody}`); + } } catch (error: any) { return { created: false, - error: error?.response?.data || error.message, + error: error?.message ?? String(error), }; } }; From a99646523c0531f77c76f67c92f2f552dcd5d75f Mon Sep 17 00:00:00 2001 From: jrmartin Date: Tue, 1 Jul 2025 20:36:10 -0700 Subject: [PATCH 2/4] feat: enhance createNewOntology to support custom redirects and improve response handling --- src/api/endpoints/apiService.ts | 73 ++++++++++--------- .../AddNewOntologyDialog.jsx | 8 +- vite.config.js | 29 +++++++- 3 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/api/endpoints/apiService.ts b/src/api/endpoints/apiService.ts index d76138d2..a5260435 100644 --- a/src/api/endpoints/apiService.ts +++ b/src/api/endpoints/apiService.ts @@ -1,5 +1,6 @@ import { createPostRequest, createGetRequest } from "./apiActions"; import { API_CONFIG } from "../../config"; +import { Term } from "../../model/frontend/terms"; export interface LoginRequest { username: string @@ -151,51 +152,55 @@ export const createNewOntology = async ({ }; try { - // Use fetch directly for manual redirect handling const postResponse = await fetch(endpoint, { method: 'POST', headers, + credentials: "include", body: JSON.stringify(data), - redirect: 'manual', // let us handle the 303 ourselves + redirect: 'manual', }); - if (postResponse.status === 303) { - const location = postResponse.headers.get('Location'); - if (location) { - // Fetch the ontology resource at the redirected location (JSON) - const getResponse = await fetch(location, { - headers: { - Accept: 'application/json', - 'Authorization': `Bearer ${token}`, - }, - }); - - // Optionally, also fetch the HTML representation - const htmlResponse = await fetch(location, { - headers: { - Accept: 'text/html', - 'Authorization': `Bearer ${token}`, - }, - }); + // Check for custom redirect header (all lowercase in fetch) + const redirectLocation = postResponse.headers.get('x-redirect-location'); + + if (redirectLocation) { + const olympianRedirectLocation = redirectLocation.replace('http://uri.interlex.org','').replace('html', 'jsonld') + + const getResponse = await fetch(olympianRedirectLocation, { headers: { Authorization: `Bearer ${token}` } }); + const jsonResponse = await getResponse.json(); + + let newOntologyID = null; + jsonResponse?.["@graph"]?.forEach((object) => { + if (object["@type"] === "owl:Ontology") { + newOntologyID = object["@id"]; + } + }); + + return { + created: true, + location: olympianRedirectLocation, + newOntologyID: newOntologyID + }; + } - return { - created: true, - location, - jsonResponse: getResponse.ok ? await getResponse.json() : null, - htmlAvailable: htmlResponse.ok, - }; - } else { - throw new Error('No Location header in 303 response'); - } - } else { - // If not a 303, handle as error or unexpected case - const errorBody = await postResponse.text(); - throw new Error(`Unexpected response status: ${postResponse.status} - ${errorBody}`); + // Try to parse the response as JSON (if present) + let jsonResponse: any = null; + try { + jsonResponse = await postResponse.json(); + } catch (e) { + // No JSON body, ignore } + + return { + created: postResponse.ok, + location: endpoint, + jsonResponse, + }; } catch (error: any) { + let errMsg = error?.message ?? String(error); return { created: false, - error: error?.message ?? String(error), + error: errMsg, }; } }; diff --git a/src/components/SingleOrganization/AddNewOntologyDialog.jsx b/src/components/SingleOrganization/AddNewOntologyDialog.jsx index dbc76619..37b4cb6b 100644 --- a/src/components/SingleOrganization/AddNewOntologyDialog.jsx +++ b/src/components/SingleOrganization/AddNewOntologyDialog.jsx @@ -76,12 +76,8 @@ const AddNewOntologyDialog = ({ open, handleClose }) => { if (result.created) { console.log('Ontology details:', result.data); - if (result.jsonResponse) { - console.log('Retrieved JSON:', result.jsonResponse); - } - - if (result.htmlAvailable !== undefined) { - console.log('HTML version available:', result.htmlAvailable); + if (result.newOntologyID) { + console.log('Created new Ontology:', result.newOntologyID); } } else { ontologyResponseMessage = "Failed to create ontology" diff --git a/vite.config.js b/vite.config.js index c2ef94f0..ba37f416 100644 --- a/vite.config.js +++ b/vite.config.js @@ -67,7 +67,7 @@ export default defineConfig({ if (proxyRes.statusCode === 303 && location) { // Prevent browser from seeing the actual Location delete proxyRes.headers['location']; - + console.log('Status code 303 ', proxyRes); // Inject the location into a custom header we can use in Axios res.setHeader('X-Redirect-Location', location); } @@ -98,6 +98,23 @@ export default defineConfig({ }); }, }, + '^/[^/]+/ontologies/uris/.*\\.(html|jsonld)$': { + target: 'https://uri.olympiangods.org', + changeOrigin: true, + secure: false, + rewrite: path => path, // Keep the full path + configure: (proxy) => { + proxy.on('proxyRes', (proxyRes, req, res) => { + const origin = req.headers.origin; + console.log('Received response for new ontology', res); + if (origin) { + console.log('Setting CORS header for origin:', origin); + res.setHeader('Access-Control-Allow-Origin', origin); + } + res.setHeader('Access-Control-Allow-Credentials', 'true'); + }); + }, + }, '^/[^/]+/ontologies/uris/.*/spec': { target: 'https://uri.olympiangods.org', changeOrigin: true, @@ -119,11 +136,15 @@ export default defineConfig({ console.log('Received location', location); if (proxyRes.statusCode === 303 && location) { - // Prevent browser from seeing the actual Location delete proxyRes.headers['location']; - - // Inject the location into a custom header we can use in Axios + res.statusCode = 200; // Prevent browser redirect res.setHeader('X-Redirect-Location', location); + res.setHeader('Access-Control-Allow-Origin', req.headers.origin || '*'); + res.setHeader('Access-Control-Allow-Credentials', 'true'); + res.setHeader('Access-Control-Expose-Headers', 'X-Redirect-Location'); + // Send a JSON body (for fetch, etc.) + res.end(JSON.stringify({ location })); + return; } // Required for credentialed CORS From 29bf67c2f91120acaac1465b67c2fc7462060b43 Mon Sep 17 00:00:00 2001 From: jrmartin Date: Tue, 1 Jul 2025 20:55:58 -0700 Subject: [PATCH 3/4] feat: streamline ontology creation response handling and remove unnecessary logs --- src/api/endpoints/apiService.ts | 3 +-- .../SingleOrganization/AddNewOntologyDialog.jsx | 8 +------- vite.config.js | 8 -------- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/api/endpoints/apiService.ts b/src/api/endpoints/apiService.ts index a5260435..096f4178 100644 --- a/src/api/endpoints/apiService.ts +++ b/src/api/endpoints/apiService.ts @@ -1,6 +1,5 @@ import { createPostRequest, createGetRequest } from "./apiActions"; import { API_CONFIG } from "../../config"; -import { Term } from "../../model/frontend/terms"; export interface LoginRequest { username: string @@ -164,7 +163,7 @@ export const createNewOntology = async ({ const redirectLocation = postResponse.headers.get('x-redirect-location'); if (redirectLocation) { - const olympianRedirectLocation = redirectLocation.replace('http://uri.interlex.org','').replace('html', 'jsonld') + const olympianRedirectLocation = redirectLocation.replace('http://uri.interlex.org','').replace(/\.html$/, '.jsonld'); const getResponse = await fetch(olympianRedirectLocation, { headers: { Authorization: `Bearer ${token}` } }); const jsonResponse = await getResponse.json(); diff --git a/src/components/SingleOrganization/AddNewOntologyDialog.jsx b/src/components/SingleOrganization/AddNewOntologyDialog.jsx index 37b4cb6b..ba1963c0 100644 --- a/src/components/SingleOrganization/AddNewOntologyDialog.jsx +++ b/src/components/SingleOrganization/AddNewOntologyDialog.jsx @@ -73,13 +73,7 @@ const AddNewOntologyDialog = ({ open, handleClose }) => { let ontologyResponseMessage = "Ontology created successfully!" - if (result.created) { - console.log('Ontology details:', result.data); - - if (result.newOntologyID) { - console.log('Created new Ontology:', result.newOntologyID); - } - } else { + if (!result.created) { ontologyResponseMessage = "Failed to create ontology" console.error('❌ Failed to create ontology:', result.error); } diff --git a/vite.config.js b/vite.config.js index ba37f416..41829e13 100644 --- a/vite.config.js +++ b/vite.config.js @@ -67,7 +67,6 @@ export default defineConfig({ if (proxyRes.statusCode === 303 && location) { // Prevent browser from seeing the actual Location delete proxyRes.headers['location']; - console.log('Status code 303 ', proxyRes); // Inject the location into a custom header we can use in Axios res.setHeader('X-Redirect-Location', location); } @@ -106,9 +105,7 @@ export default defineConfig({ configure: (proxy) => { proxy.on('proxyRes', (proxyRes, req, res) => { const origin = req.headers.origin; - console.log('Received response for new ontology', res); if (origin) { - console.log('Setting CORS header for origin:', origin); res.setHeader('Access-Control-Allow-Origin', origin); } res.setHeader('Access-Control-Allow-Credentials', 'true'); @@ -122,18 +119,13 @@ export default defineConfig({ rewrite: path => path, // Keep full path configure: (proxy) => { proxy.on('proxyReq', (proxyReq, req) => { - console.log('Proxying ontology spec request:', req.method, req.url); - console.log('Headers:', proxyReq.getHeaders()); if (req.headers.authorization) { proxyReq.setHeader('Authorization', req.headers.authorization); } }); proxy.on('proxyRes', (proxyRes, req, res) => { - console.log('Received response', res); - console.log('Received Response from the Target:', proxyRes.statusCode, req.url); const location = proxyRes.headers['location']; - console.log('Received location', location); if (proxyRes.statusCode === 303 && location) { delete proxyRes.headers['location']; From e0974ddb97f2606d3eda235f65869962c2eea289 Mon Sep 17 00:00:00 2001 From: Jesus M Date: Tue, 1 Jul 2025 21:08:55 -0700 Subject: [PATCH 4/4] Update src/api/endpoints/apiService.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/api/endpoints/apiService.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/api/endpoints/apiService.ts b/src/api/endpoints/apiService.ts index 096f4178..9770d797 100644 --- a/src/api/endpoints/apiService.ts +++ b/src/api/endpoints/apiService.ts @@ -168,12 +168,7 @@ export const createNewOntology = async ({ const getResponse = await fetch(olympianRedirectLocation, { headers: { Authorization: `Bearer ${token}` } }); const jsonResponse = await getResponse.json(); - let newOntologyID = null; - jsonResponse?.["@graph"]?.forEach((object) => { - if (object["@type"] === "owl:Ontology") { - newOntologyID = object["@id"]; - } - }); + const newOntologyID = jsonResponse?.["@graph"]?.find((object) => object["@type"] === "owl:Ontology")?.["@id"] || null; return { created: true,