From d3c2acf02e61b302e0d0bd9e52a80199be2607fb Mon Sep 17 00:00:00 2001 From: DominikMartinat Date: Sat, 22 Nov 2025 11:36:53 +0100 Subject: [PATCH 1/2] rdf endpoint draft --- app/Http/Controllers/RdfController.php | 78 ++++++++++++++++++++++++++ routes/api.php | 7 +++ 2 files changed, 85 insertions(+) create mode 100644 app/Http/Controllers/RdfController.php diff --git a/app/Http/Controllers/RdfController.php b/app/Http/Controllers/RdfController.php new file mode 100644 index 0000000..4fc7f5d --- /dev/null +++ b/app/Http/Controllers/RdfController.php @@ -0,0 +1,78 @@ +json(['error' => 'Invalid RDF suffix'], 400); + } + + // Build the full URI — ensure there is a single slash between prefix + // and suffix and validate the resulting URL. + $fullUri = 'https://rdf.molmedb.upol.cz/' . ltrim($suffix, '/'); + + // If the client prefers HTML (likely a browser), redirect to the + // SPARQL UI where the fragment contains the query (browsers keep + // fragments client-side). For machine clients requesting RDF/Turtle/etc., + // we'll proxy the request and forward the Accept header. + if ($request->accepts('text/html')) { + $uiUrl = 'https://idsm.elixir-czech.cz/sparql/endpoint/molmedb#query=DESCRIBE%20%3C' . $fullUri . '%3E'; + return redirect()->away($uiUrl); + } + + + + if (!filter_var($fullUri, FILTER_VALIDATE_URL)) { + return response()->json(['error' => 'Constructed URI is invalid'], 400); + } + + // Incoming Accept header (default to */* if absent) + $accept = $request->header('Accept', '*/*'); + + // External SPARQL endpoint and the DESCRIBE query for the constructed URI + $endpoint = 'https://idsm.elixir-czech.cz/sparql/endpoint/molmedb'; + $sparql = 'DESCRIBE <' . $fullUri . '>'; + + try { + $resp = Http::withHeaders(['Accept' => $accept]) + ->timeout(10) + ->get($endpoint, ['query' => $sparql]); + + $status = $resp->status(); + $body = $resp->body(); + $contentType = $resp->header('Content-Type', $accept); + + return response($body, $status) + ->header('Content-Type', $contentType); + } catch (\Throwable $e) { + return response()->json([ + 'error' => 'Upstream request failed', + 'message' => $e->getMessage(), + ], 502); + } + } +} diff --git a/routes/api.php b/routes/api.php index 30ca20e..d285bdc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -6,6 +6,7 @@ use App\Http\Controllers\MethodController; use App\Http\Controllers\ProteinController; use App\Http\Controllers\PublicationController; +use App\Http\Controllers\RdfController; use App\Http\Controllers\SearchController; use App\Http\Controllers\StatsController; use App\Http\Controllers\StructureController; @@ -97,4 +98,10 @@ Route::get('/{identifier}/form/select/methods', 'formSelectMethods'); Route::get('/{identifier}/similarities', 'similarities'); }); + + // Accept any suffix (including slashes) after /api/rdf/ and forward it to + // RdfController::simple_rdf as `$rdf_suffix`. Use a `where` rule to allow + // slashes in the parameter (match `.*`). Example: `/api/rdf/substance/123`. + Route::get('rdf/{rdf_suffix}', [RdfController::class, 'simple_rdf']) + ->where('rdf_suffix', '.*'); }); \ No newline at end of file From e3e5b3d95cc852a7b55560612a1db5c3a1080b46 Mon Sep 17 00:00:00 2001 From: DominikMartinat Date: Sat, 22 Nov 2025 17:08:32 +0100 Subject: [PATCH 2/2] basic api RDF endpoint for URI dereference --- .env.example | 7 ++++ app/Helpers/SPARQLurl.php | 17 ++++++++ app/Http/Controllers/RdfController.php | 58 ++++---------------------- routes/api.php | 3 -- 4 files changed, 33 insertions(+), 52 deletions(-) create mode 100644 app/Helpers/SPARQLurl.php diff --git a/.env.example b/.env.example index 8a35a87..065fa4e 100644 --- a/.env.example +++ b/.env.example @@ -84,3 +84,10 @@ AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false VITE_APP_NAME="${APP_NAME}" + +###################### +#### RDF SETTINGS #### +###################### +SPARQL_ENDPOINT='https://idsm.elixir-czech.cz/sparql/endpoint/molmedb' +SPARQL_QUERY_PREFIX='?query=' +DB_RDF_PREFIX='https://rdf.molmedb.upol.cz/' \ No newline at end of file diff --git a/app/Helpers/SPARQLurl.php b/app/Helpers/SPARQLurl.php new file mode 100644 index 0000000..88851b3 --- /dev/null +++ b/app/Helpers/SPARQLurl.php @@ -0,0 +1,17 @@ +'); + $uiUrl = $ep.$sparql_prefix.$query; + return $uiUrl; + } +} diff --git a/app/Http/Controllers/RdfController.php b/app/Http/Controllers/RdfController.php index 4fc7f5d..dcc88a4 100644 --- a/app/Http/Controllers/RdfController.php +++ b/app/Http/Controllers/RdfController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Helpers\SPARQLurl; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; @@ -20,59 +21,18 @@ public function index() */ public function simple_rdf($rdf_suffix) { - $request = request(); + // Validate the suffix after URL-decoding so we can return a custom + // status code when the suffix contains disallowed characters. + $decoded = urldecode($rdf_suffix); - // Decode suffix and validate allowed characters. The suffix may contain - // letters, numbers and slashes (per your specification). Reject anything - // suspicious to avoid injection into the SPARQL query. - $suffix = urldecode($rdf_suffix); - - if (!preg_match('/^[A-Za-z0-9\/\-_.]+$/', $suffix)) { + // Allowed characters: letters, numbers, slash, dash, underscore and dot + $allowedPattern = '/^[A-Za-z0-9\/\-_.]+$/'; + if (!preg_match($allowedPattern, $decoded)) { return response()->json(['error' => 'Invalid RDF suffix'], 400); } - // Build the full URI — ensure there is a single slash between prefix - // and suffix and validate the resulting URL. - $fullUri = 'https://rdf.molmedb.upol.cz/' . ltrim($suffix, '/'); - - // If the client prefers HTML (likely a browser), redirect to the - // SPARQL UI where the fragment contains the query (browsers keep - // fragments client-side). For machine clients requesting RDF/Turtle/etc., - // we'll proxy the request and forward the Accept header. - if ($request->accepts('text/html')) { - $uiUrl = 'https://idsm.elixir-czech.cz/sparql/endpoint/molmedb#query=DESCRIBE%20%3C' . $fullUri . '%3E'; - return redirect()->away($uiUrl); - } - - - - if (!filter_var($fullUri, FILTER_VALIDATE_URL)) { - return response()->json(['error' => 'Constructed URI is invalid'], 400); - } - - // Incoming Accept header (default to */* if absent) - $accept = $request->header('Accept', '*/*'); - - // External SPARQL endpoint and the DESCRIBE query for the constructed URI - $endpoint = 'https://idsm.elixir-czech.cz/sparql/endpoint/molmedb'; - $sparql = 'DESCRIBE <' . $fullUri . '>'; + $uiUrl = SPARQLurl::rdfDescribeUrl($rdf_suffix); + return redirect()->away($uiUrl, 303); - try { - $resp = Http::withHeaders(['Accept' => $accept]) - ->timeout(10) - ->get($endpoint, ['query' => $sparql]); - - $status = $resp->status(); - $body = $resp->body(); - $contentType = $resp->header('Content-Type', $accept); - - return response($body, $status) - ->header('Content-Type', $contentType); - } catch (\Throwable $e) { - return response()->json([ - 'error' => 'Upstream request failed', - 'message' => $e->getMessage(), - ], 502); - } } } diff --git a/routes/api.php b/routes/api.php index d285bdc..e25a761 100644 --- a/routes/api.php +++ b/routes/api.php @@ -99,9 +99,6 @@ Route::get('/{identifier}/similarities', 'similarities'); }); - // Accept any suffix (including slashes) after /api/rdf/ and forward it to - // RdfController::simple_rdf as `$rdf_suffix`. Use a `where` rule to allow - // slashes in the parameter (match `.*`). Example: `/api/rdf/substance/123`. Route::get('rdf/{rdf_suffix}', [RdfController::class, 'simple_rdf']) ->where('rdf_suffix', '.*'); }); \ No newline at end of file