diff --git a/Docker/Dockerfile b/Docker/Dockerfile index c3c10e02..cedcdbab 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -42,7 +42,7 @@ COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer COPY fs_overlay/ / -RUN a2enmod rewrite && a2enconf short-url +RUN a2enmod rewrite proxy proxy_http && a2enconf short-url FROM configured-mw AS production-mw diff --git a/Docker/fs_overlay/etc/apache2/conf-available/short-url.conf b/Docker/fs_overlay/etc/apache2/conf-available/short-url.conf index 909c1868..e9806058 100644 --- a/Docker/fs_overlay/etc/apache2/conf-available/short-url.conf +++ b/Docker/fs_overlay/etc/apache2/conf-available/short-url.conf @@ -1,6 +1,13 @@ RewriteEngine on + # Serve the bare Subject concept URI ($wgNeoWikiRdfBaseUri/entity/{id}) that the RDF export mints, + # so it dereferences (303 to the Subject RDF or its page). MediaWiki's REST router matches on the + # request path, so the request must reach it as /w/rest.php/... A plain internal rewrite leaves + # REQUEST_URI unchanged and the router would 404, so this is an internal self-proxy: it re-requests + # the REST route without a client-visible redirect. Requires mod_proxy + mod_proxy_http. + RewriteRule ^/?entity/(.+)$ http://127.0.0.1/w/rest.php/neowiki/v0/entity/$1 [P,L] + RewriteRule ^/?wiki(/.*)?$ %{DOCUMENT_ROOT}/w/index.php [L] RewriteRule ^/?$ %{DOCUMENT_ROOT}/w/index.php [L] diff --git a/docs/api/rest-api.md b/docs/api/rest-api.md index 6ba76993..69672950 100644 --- a/docs/api/rest-api.md +++ b/docs/api/rest-api.md @@ -49,6 +49,7 @@ Read, change, and validate Subjects. New Subjects are created on a page — see |---|---| | `GET /neowiki/v0/subject/{subjectId}` | Fetch a Subject. Optional `revisionId`; `expand` with `page` or `relations`. | | `GET /neowiki/v0/subject/{subjectId}/rdf` | Export one Subject as RDF. `format` is `trig` (default) or `turtle`; `projection` is `native` (default) or an ontology target. See [RDF export](../rdf/rdf-export.md). | +| `GET /neowiki/v0/entity/{subjectId}` | Dereference a Subject's concept URI. `303` to the Subject's RDF (`Accept: application/trig` or `text/turtle`) or to the hosting page (otherwise). See [Dereferencing subject IRIs](../rdf/rdf-export.md#dereferencing-subject-iris). | | `PUT /neowiki/v0/subject/{subjectId}` | Replace a Subject's label and statements. | | `DELETE /neowiki/v0/subject/{subjectId}` | Delete a Subject. | | `POST /neowiki/v0/subject/validate` | Check whether a new Subject is valid, without saving it. | diff --git a/docs/rdf/rdf-export.md b/docs/rdf/rdf-export.md index 241c4792..3bb05fa5 100644 --- a/docs/rdf/rdf-export.md +++ b/docs/rdf/rdf-export.md @@ -102,6 +102,38 @@ for the requested ontology target projects to an empty graph — a `200`, not a curl 'https://wiki.example/rest.php/neowiki/v0/subject/s1demo8aaaaaab5/rdf?projection=EDM' ``` +### Dereferencing subject IRIs + +Every Subject's `neo-subj:` IRI — `$base/entity/{subjectId}` — is a dereferenceable concept URI. A `GET` +content-negotiates it and answers `303 See Other` with an absolute `Location`: + +| `Accept` | Redirects to | +|---|---| +| `application/trig` | the Subject's TriG RDF (`.../subject/{id}/rdf?format=trig`) | +| `text/turtle` | the Subject's Turtle RDF (`.../subject/{id}/rdf?format=turtle`) | +| `text/html`, `*/*`, absent, anything else | the Subject's hosting page | + +TriG wins when both RDF types are acceptable; the RDF redirects use the native projection. A Subject that +is absent or on a page the caller may not read returns one indistinguishable `404`; a malformed id `400`. + +The negotiator is always reachable at the REST path, which needs no server configuration: + +```sh +curl -H 'Accept: text/turtle' 'https://wiki.example/rest.php/neowiki/v0/entity/s1demo8aaaaaab5' +``` + +To make the bare `neo-subj:` IRI itself dereference, route `/entity/{id}` to that REST path. MediaWiki's +REST router matches on the request path, so an internal self-proxy (not a plain rewrite, which would leave +the path unchanged) presents it without a client-visible redirect. The dev image ships this, on Apache with +`mod_proxy` and `mod_proxy_http`: + +```apache +RewriteRule ^/?entity/(.+)$ http://127.0.0.1/w/rest.php/neowiki/v0/entity/$1 [P,L] +``` + +This applies when `$wgNeoWikiRdfBaseUri` is the wiki's own host (the default); an external or institutional +base URI is the operator's own routing concern. + ### Finding these exports These exports are surfaced in the UI: the Data tab (`?action=subjects`) links to each Subject's JSON and diff --git a/extension.json b/extension.json index 531b6f60..1c97bbf7 100644 --- a/extension.json +++ b/extension.json @@ -259,6 +259,11 @@ "method": [ "GET" ], "factory": "ProfessionalWiki\\NeoWiki\\NeoWikiExtension::newExportSubjectRdfApi" }, + { + "path": "/neowiki/v0/entity/{subjectId}", + "method": [ "GET" ], + "factory": "ProfessionalWiki\\NeoWiki\\NeoWikiExtension::newResolveSubjectIriApi" + }, { "path": "/neowiki/v0/schema/{schemaName}", "method": [ "GET" ], @@ -599,6 +604,10 @@ "neowiki-managesubjects-id-copy", "neowiki-managesubjects-id-copied", "neowiki-managesubjects-id-copy-error", + "neowiki-managesubjects-iri-label", + "neowiki-managesubjects-iri-copy", + "neowiki-managesubjects-iri-copied", + "neowiki-managesubjects-iri-copy-error", "neowiki-managesubjects-export-button", "neowiki-managesubjects-export-json", "neowiki-managesubjects-export-native", diff --git a/i18n/en.json b/i18n/en.json index fa0dafc0..ee26a0c1 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -307,6 +307,10 @@ "neowiki-managesubjects-id-copy": "Copy subject ID $1", "neowiki-managesubjects-id-copied": "Copied \"$1\" to clipboard.", "neowiki-managesubjects-id-copy-error": "Couldn't copy the subject ID.", + "neowiki-managesubjects-iri-label": "IRI:", + "neowiki-managesubjects-iri-copy": "Copy subject IRI $1", + "neowiki-managesubjects-iri-copied": "Copied \"$1\" to clipboard.", + "neowiki-managesubjects-iri-copy-error": "Couldn't copy the subject IRI.", "neowiki-managesubjects-export-button": "Export", "neowiki-managesubjects-export-json": "JSON", "neowiki-managesubjects-export-native": "Native", diff --git a/i18n/qqq.json b/i18n/qqq.json index 9f931bc3..6fe58cc7 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -238,6 +238,10 @@ "neowiki-managesubjects-id-copy": "Tooltip on the subject ID button. $1 is the Subject ID. Click copies the ID to the clipboard.", "neowiki-managesubjects-id-copied": "Notification shown after the subject ID was copied. $1 is the Subject ID.", "neowiki-managesubjects-id-copy-error": "Notification shown when copying the subject ID to the clipboard failed.", + "neowiki-managesubjects-iri-label": "Inline label shown before the Subject IRI (its RDF concept URI) in the footer of an expanded Subject row. Typically short, e.g. \"IRI:\".", + "neowiki-managesubjects-iri-copy": "Tooltip on the subject IRI button. $1 is the Subject IRI. Click copies the IRI to the clipboard.", + "neowiki-managesubjects-iri-copied": "Notification shown after the subject IRI was copied. $1 is the Subject IRI.", + "neowiki-managesubjects-iri-copy-error": "Notification shown when copying the subject IRI to the clipboard failed.", "neowiki-managesubjects-export-button": "Label and accessible name of the button that opens the data export menu on the Data tab (per Subject, and for the whole page in the header).", "neowiki-managesubjects-export-json": "Export menu item that downloads the raw JSON representation of the Subject or page.", "neowiki-managesubjects-export-native": "Display name of the native (NeoWiki-vocabulary) RDF projection, used as $1 in {{msg-neowiki|neowiki-managesubjects-export-turtle}} and {{msg-neowiki|neowiki-managesubjects-export-trig}}.", diff --git a/resources/ext.neowiki/src/components/SubjectsManager/SubjectsManagerPage.vue b/resources/ext.neowiki/src/components/SubjectsManager/SubjectsManagerPage.vue index dbdbb883..3cde0132 100644 --- a/resources/ext.neowiki/src/components/SubjectsManager/SubjectsManagerPage.vue +++ b/resources/ext.neowiki/src/components/SubjectsManager/SubjectsManagerPage.vue @@ -171,22 +171,40 @@