@igea/oac_backend 1.0.38 → 1.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igea/oac_backend",
3
- "version": "1.0.38",
3
+ "version": "1.0.40",
4
4
  "description": "Backend service for the OAC project",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -36,7 +36,8 @@
36
36
  "n3": "1.26.0",
37
37
  "nodemailer": "7.0.6",
38
38
  "pg": "8.16.3",
39
- "strip-bom": "5.0.0"
39
+ "strip-bom": "5.0.0",
40
+ "tmp": "0.2.5"
40
41
  },
41
42
  "devDependencies": {
42
43
  "chai": "5.2.1",
@@ -116,6 +116,121 @@ router.post('/upload/vocabularies', upload.array('files'), (req, res) => {
116
116
 
117
117
  });
118
118
 
119
+ //---------------------------------------------------------------
120
+ router.get('/get-vocabolary-terms/:key', (req, res) => {
121
+ const key = req.params.key;
122
+ const rootIRI = `<http://diagnostica/vocabularies/${key}>`;
123
+
124
+ let _query = `PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
125
+ CONSTRUCT {
126
+ ?concept ?p ?o .
127
+ }
128
+ WHERE {
129
+ ?concept (crm:P127_has_broader_term*) ${rootIRI} .
130
+ ?concept ?p ?o .
131
+ }`;
132
+
133
+ let query = `PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/>
134
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
135
+ PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
136
+ PREFIX owl: <http://www.w3.org/2002/07/owl#>
137
+
138
+ CONSTRUCT {
139
+
140
+ ?concept a owl:Class ;
141
+ rdfs:subClassOf ?parent ;
142
+ skos:prefLabel ?label ;
143
+ skos:closeMatch ?mappedConcept .
144
+
145
+ }
146
+ WHERE {
147
+
148
+ # Trovo tutti i concetti del vocabolario
149
+ ?concept (crm:P127_has_broader_term*) ${rootIRI} .
150
+
151
+ # Recupero l'etichetta
152
+ OPTIONAL { ?concept rdfs:label ?label . }
153
+
154
+ # Prelevo il parent da broader term
155
+ OPTIONAL {
156
+ ?concept crm:P127_has_broader_term ?parent .
157
+ }
158
+
159
+ # Genero un mapping verso un URI esterno
160
+ BIND(
161
+ IRI(CONCAT("${rootIRI}", REPLACE(STR(?concept), "^.*[/#]", "")))
162
+ AS ?mappedConcept
163
+ )
164
+
165
+ }
166
+ ORDER BY ?label`
167
+
168
+ axios.post(fusekiUrl, `query=${encodeURIComponent(query)}`,{
169
+ headers: {
170
+ 'Accept': `text/turtle`,
171
+ 'Content-Type': 'application/x-www-form-urlencoded'
172
+ },
173
+ responseType: 'stream'
174
+ }).then(response => {
175
+ res.setHeader('Content-Type', response.headers['content-type']);
176
+ response.data.pipe(res);
177
+ }).catch(err => {
178
+ res.status(500).json({
179
+ success: false,
180
+ data: null,
181
+ message: `Error: ${err}`
182
+ });
183
+ });
184
+ })
185
+ //---------------------------------------------------------------
186
+
187
+ /**
188
+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
189
+
190
+ SELECT ?obj
191
+ WHERE {
192
+ ?obj rdf:type <http://www.cidoc-crm.org/cidoc-crm/E55_Type> .
193
+ }
194
+ ORDER BY ?obj
195
+ LIMIT 10
196
+ OFFSET 50
197
+ */
198
+ //---------------------------------------------------------------
199
+ router.post('/search/by-prefix', (req, res) => {
200
+ const query = Fuseki.getQuerySearchByPrefix(
201
+ req.body.prefix || "",
202
+ parseInt(req.body.limit) || 50,
203
+ parseInt(req.body.offset) || 0
204
+ );
205
+
206
+ axios.post(fusekiUrl, `query=${encodeURIComponent(query)}`, {
207
+ headers: {
208
+ 'Content-Type': 'application/x-www-form-urlencoded',
209
+ 'Accept': 'application/sparql-results+json'
210
+ }
211
+ }).then(response => {
212
+ const bindings = response.data.results.bindings;
213
+ let results = []
214
+ bindings.forEach(result => {
215
+ results.push({
216
+ instance: result.instance.value,
217
+ label: result.label.value,
218
+ });
219
+ });
220
+ res.json({
221
+ success: true,
222
+ data: results,
223
+ message: null
224
+ });
225
+ }).catch(err => {
226
+ res.status(500).json({
227
+ success: false,
228
+ data: null,
229
+ message: `Error: ${err}`
230
+ });
231
+ });
232
+
233
+ });
119
234
  //---------------------------------------------------------------
120
235
  router.get('/count/entities', (req, res) => {
121
236
  const query = `
@@ -154,7 +269,6 @@ router.get('/count/entities', (req, res) => {
154
269
  message: `Error: ${err}`
155
270
  });
156
271
  });
157
-
158
272
  });
159
273
 
160
274
  router.get('/export/:format/:entity/:id', (req, res) => {
@@ -1,7 +1,11 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
3
  const path = require('path');
4
- const ONTO_FOLDER = path.join(__dirname, '..', 'ontology'); ;
4
+ const fs = require('fs');
5
+ const ONTO_FOLDER = path.join(__dirname, '..', 'ontology');
6
+ const Converter = require('../models/converter');
7
+ const tmp = require('tmp');
8
+
5
9
 
6
10
  router.get('/schema/:format', (req, res) => {
7
11
  console.log(`Requesting SHACL schema in format: ${req.params.format}`);
@@ -32,7 +36,20 @@ router.get('/schema/:format', (req, res) => {
32
36
  });
33
37
  return;
34
38
  }
35
- res.sendFile(path.join(ONTO_FOLDER, filePath), (err) => {
39
+
40
+ let fileContent = fs.readFileSync(path.join(ONTO_FOLDER, filePath), 'utf8');
41
+ let protocol = process.env.OAC_EXPOSED_PROTOCOL || 'http';
42
+ let host = process.env.OAC_EXPOSED_HOST || '127.0.0.1';
43
+ if(host=="localhost") host="127.0.0.1";
44
+ let port = process.env.OAC_EXPOSED_PORT || '4000';
45
+ fileContent = fileContent.replace(/OAC_EXPOSED_PROTOCOL/g, protocol);
46
+ fileContent = fileContent.replace(/OAC_EXPOSED_HOST/g, host);
47
+ fileContent = fileContent.replace(/OAC_EXPOSED_PORT/g, port);
48
+
49
+ const tempFile = tmp.fileSync({ postfix: filePath });
50
+ fs.writeFileSync(tempFile.name, fileContent);
51
+
52
+ res.sendFile(tempFile.name, (err) => {
36
53
  if (err) {
37
54
  res.status(500).json({
38
55
  success: false,
@@ -93,5 +110,63 @@ router.get('/schema-temp', (req, res) => {
93
110
  });
94
111
  });
95
112
 
113
+ router.post('/convert/:from/:to', (req, res) => {
114
+ const from = req.params.from;
115
+ const to = req.params.to;
116
+ console.log(`Requesting conversion from ${from} to ${to}`);
117
+ let conversionFunction = null
118
+ if(from === 'ttl' && to === 'xml'){
119
+ conversionFunction = Converter.turtle2RdfXml
120
+ }else if(from === 'xml' && to === 'ttl'){
121
+ conversionFunction = Converter.rdfXml2Turtle
122
+ }else{
123
+ res.status(400).json({
124
+ success: false,
125
+ data: null,
126
+ message: `Unsupported conversion from ${from} to ${to}`
127
+ });
128
+ return;
129
+ }
130
+
131
+ const inputFile = tmp.fileSync({ postfix: `.${from}` });
132
+ const outputFile = tmp.fileSync({ postfix: `.${to}` });
133
+
134
+ const content = req.body.file
135
+
136
+ fs.writeFileSync(inputFile, content);
137
+
138
+ const removeFiles = function(_files){
139
+ for(let i=0; i<_files.length; i++){
140
+ try {
141
+ fs.unlinkSync(_files[i]);
142
+ console.log(`File ${_files[i]} removed`);
143
+ } catch (e) {
144
+ console.error(`Error deleting ${_files[i]}`, e);
145
+ }
146
+ }
147
+ }
148
+ let files = [inputFile];
149
+ conversionFunction(inputFile, outputFile).then(() => {
150
+ res.sendFile(outputFile, (err) => {
151
+ if (err) {
152
+ res.status(500).json({
153
+ success: false,
154
+ data: null,
155
+ message: `Error sending file: ${err}`
156
+ });
157
+ files = [inputFile]
158
+ }
159
+ files.push(outputFile)
160
+ removeFiles(files)
161
+ });
162
+ }).catch(err => {
163
+ res.status(500).json({
164
+ success: false,
165
+ data: null,
166
+ message: `Conversion error: ${err}`
167
+ });
168
+ removeFiles(files)
169
+ });
170
+ })
96
171
 
97
172
  module.exports = router
@@ -1,9 +1,46 @@
1
1
  const fs = require('fs');
2
2
  const { Parser } = require('n3');
3
3
  const { DataFactory, Store } = require('n3');
4
+ const { exec } = require('child_process');
4
5
 
5
6
  class Converter {
6
7
 
8
+ static async turtle2RdfXml(inTurtlePath, outRdfXmlPath) {
9
+ return new Promise((resolve, reject) => {
10
+ const command = `rapper -i turtle -o rdfxml "${inTurtlePath}" > "${outRdfXmlPath}"`;
11
+ exec(command, (error, stdout, stderr) => {
12
+ if (error) {
13
+ console.error(`Error during conversion: ${error.message}`);
14
+ reject(error);
15
+ return;
16
+ }
17
+ if (stderr) {
18
+ console.log(`Conversion stderr: ${stderr}`);
19
+ }
20
+ console.log(`Conversion stdout: ${stdout}`);
21
+ resolve();
22
+ });
23
+ });
24
+ }
25
+
26
+ static async rdfXml2Turtle(inRdfXmlPath, outTurtlePath) {
27
+ return new Promise((resolve, reject) => {
28
+ const command = `rapper -i rdfxml -o turtle "${inRdfXmlPath}" > "${outTurtlePath}"`;
29
+ exec(command, (error, stdout, stderr) => {
30
+ if (error) {
31
+ console.error(`Error during conversion: ${error.message}`);
32
+ reject(error);
33
+ return;
34
+ }
35
+ if (stderr) {
36
+ console.log(`Conversion stderr: ${stderr}`);
37
+ }
38
+ console.log(`Conversion stdout: ${stdout}`);
39
+ resolve();
40
+ });
41
+ });
42
+ }
43
+
7
44
  // Funzione per generare messaggi di errore multilingua
8
45
  static generateErrorMessages(propertyName, datatype, isRelation) {
9
46
  const messages = {
@@ -84,6 +84,28 @@ class Fuseki {
84
84
  }`;
85
85
  }
86
86
 
87
+ static getQuerySearchByPrefix(prefix, limit, offset){
88
+ limit = limit || 50;
89
+ offset = offset || 0;
90
+ if(limit < 1) limit = 50;
91
+ return `
92
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
93
+ SELECT DISTINCT ?instance ?label
94
+ WHERE {
95
+ # tutte le istanze con qualsiasi proprietà
96
+ ?instance ?p ?o .
97
+ # filtro per IRI che inizia con il prefisso specifico
98
+ FILTER(STRSTARTS(STR(?instance), "${prefix}"))
99
+ # filtro per IRI che termina con UUID
100
+ #FILTER(REGEX(STR(?instance), "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))
101
+ # label opzionale
102
+ OPTIONAL { ?instance rdfs:label ?label . }
103
+ }
104
+ ORDER BY ?label
105
+ LIMIT ${limit}
106
+ OFFSET ${offset}`
107
+ }
108
+
87
109
  }
88
110
 
89
111
  module.exports = Fuseki;
@@ -3,6 +3,8 @@
3
3
  @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
4
4
  @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
5
5
  @prefix crm: <http://www.cidoc-crm.org/cidoc-crm/> .
6
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
7
+ @prefix dcterms: <http://purl.org/dc/terms/> .
6
8
 
7
9
  ex:E7ActivityShape
8
10
  a sh:NodeShape ;
@@ -57,7 +59,8 @@ ex:E42IdentifierShape
57
59
  sh:path ex:P48_has_preferred_identifier ;
58
60
  sh:datatype xsd:string ;
59
61
  sh:name "ID" ;
60
- sh:defaultValue "$uuid$" ;
62
+ sh:nodeKind sh:IRI;
63
+ sh:description "http://diagnostica/indagine/$UUID$" ;
61
64
  sh:maxCount 1 ;
62
65
  sh:minCount 1 ;
63
66
  ] .
@@ -66,24 +69,14 @@ ex:E55Type01Shape
66
69
  a sh:NodeShape ;
67
70
  sh:targetClass crm:E55_Type ;
68
71
  sh:property [
69
- sh:path ex:P2hastype1 ;
70
- #sh:class ex:E55Type01 ;
71
- #sh:in ( ex:TipoIndagineA ex:TipoIndagineB ) ;
72
- sh:in ( "Tipo indagine A" "Tipo indagine B" ) ;
73
- #owl:imports
72
+ sh:class owl:Class ;
73
+ sh:path crm:P127_has_broader_term ;
74
+ owl:imports <OAC_EXPOSED_PROTOCOL://OAC_EXPOSED_HOST:OAC_EXPOSED_PORT/backend/fuseki/get-vocabolary-terms/fase-di-analisi> ;
74
75
  sh:name "Tipo" ;
75
76
  sh:maxCount 1 ;
76
77
  sh:minCount 1 ;
77
78
  ] .
78
79
 
79
- # Vocabolario E55Type01 (Tipo indagine)
80
- #ex:TipoIndagineA
81
- # a crm:E55_Type ;
82
- # rdfs:label "Tipo indagine A" .
83
- #ex:TipoIndagineB
84
- # a crm:E55_Type ;
85
- # rdfs:label "Tipo indagine B" .
86
-
87
80
  ex:I12AdoptedBeliefShape
88
81
  a sh:NodeShape ;
89
82
  sh:targetClass crm:I12_Adopted_Belief ;
@@ -99,13 +92,18 @@ ex:E55Type02Shape
99
92
  a sh:NodeShape ;
100
93
  sh:targetClass crm:E55_Type ;
101
94
  sh:property [
102
- sh:path ex:P2hastype2 ;
103
- #sh:class ex:E55Type02 ;
104
- #sh:in ( ex:TipoQuesitoDiagnostico1 ex:TipoQuesitoDiagnostico2 ex:TipoQuesitoDiagnostico3 ) ;
105
- sh:in ( "Tipo quesito diagnistico 1" "Tipo quesito diagnistico 2" "Tipo quesito diagnistico 3" ) ;
106
95
  sh:name "Tipo" ;
107
96
  sh:maxCount 1 ;
108
97
  sh:minCount 1 ;
98
+ #sh:class crm:E55_Type ;
99
+ sh:class owl:Class ;
100
+ sh:path crm:P127_has_broader_term ;
101
+ owl:imports <OAC_EXPOSED_PROTOCOL://OAC_EXPOSED_HOST:OAC_EXPOSED_PORT/backend/fuseki/get-vocabolary-terms/quesito-diagnostico> ;
102
+
103
+ #sh:class owl:Class ;
104
+ #sh:path dcterms:subject ;
105
+ #owl:imports <https://raw.githubusercontent.com/tibonto/DFG-Fachsystematik-Ontology/refs/heads/main/dfgfo.ttl> ;
106
+
109
107
  ] ;
110
108
  sh:property [
111
109
  sh:path crm:P3_has_note ;
@@ -114,25 +112,20 @@ ex:E55Type02Shape
114
112
  sh:maxCount 1 ;
115
113
  ] .
116
114
 
117
- # Vocabolario E55Type02 (Tipo quesito diagnostico)
118
- #ex:TipoQuesitoDiagnostico1
119
- # a crm:E55_Type ;
120
- # rdfs:label "Tipo quesito diagnistico 1" .
121
- #ex:TipoQuesitoDiagnostico2
122
- # a crm:E55_Type ;
123
- # rdfs:label "Tipo quesito diagnistico 2" .
124
- #ex:TipoQuesitoDiagnostico3
125
- # a crm:E55_Type ;
126
- # rdfs:label "Tipo quesito diagnistico 3" .
127
-
128
115
  ex:E29ActorShape
129
116
  a sh:NodeShape ;
130
117
  sh:targetClass crm:E29_Actor ;
131
118
  sh:property [
132
119
  sh:path ex:ente_richiedente ;
133
120
  sh:datatype xsd:string ;
134
- sh:name "Ente richiedente" ;
135
- sh:maxCount 1 ;
121
+
122
+ #sh:name "Ente richiedente" ;
123
+ #sh:maxCount 1 ;
124
+
125
+ sh:name "ID" ;
126
+ sh:nodeKind sh:IRI;
127
+ sh:description "http://diagnostica/actor/$UUID$" ;
128
+
136
129
  ] ;
137
130
  sh:property [
138
131
  sh:path ex:schedatore ;
@@ -141,22 +134,3 @@ ex:E29ActorShape
141
134
  sh:maxCount 1 ;
142
135
  sh:minCount 1 ;
143
136
  ] .
144
-
145
- #Vocabolario E55Type03a (con unico termine "Ente_richiedente") ???
146
- #Vocabolario E55Type03b (con unico termine "Ente_schedatore") ???
147
- #ex:TipoAttore1
148
- # a crm:E55_Type ;
149
- # rdfs:label "Ente richiedente" .
150
- #ex:TipoAttore2
151
- # a crm:E55_Type ;
152
- # rdfs:label "Schedatore" .
153
-
154
- #ex:E55Type03Shape
155
- # a sh:NodeShape ;
156
- # sh:targetClass crm:E55_Type ;
157
- # sh:property [
158
- # sh:path crm:P1_is_defined_by ;
159
- # sh:node ex:E41AppellationShape ;
160
- # sh:name "Attore" ;
161
- # sh:maxCount 1 ;
162
- # ] .
@@ -0,0 +1,30 @@
1
+ const chai = require('chai');
2
+ const expect = chai.expect;
3
+ const request = require('supertest');
4
+ const converter = require('../../src/models/converter');
5
+ const fs = require('fs')
6
+
7
+ describe('Converter', () => {
8
+
9
+ beforeEach(() => {
10
+
11
+ });
12
+
13
+ it('should convert a turtle file to a rdf/xml', (done) => {
14
+ let inputTurtle = __dirname + '/example-01.ttl';
15
+ let outputRdfXml = __dirname + '/example-01.xml';
16
+ converter.turtle2RdfXml(inputTurtle, outputRdfXml).then(() => {
17
+ let isOk = fs.existsSync(outputRdfXml)
18
+ expect(isOk).to.be.equal(true);
19
+ if(isOk) {
20
+ console.log('Removing temp file: ' + outputRdfXml);
21
+ fs.unlinkSync(outputRdfXml);
22
+ }
23
+ done()
24
+ }).catch(err => {
25
+ console.error(err);
26
+ done();
27
+ });
28
+ });
29
+
30
+ })
@@ -0,0 +1,46 @@
1
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
2
+ @prefix schema: <http://schema.org/> .
3
+ @prefix dcterms: <http://purl.org/dc/terms/> .
4
+ @prefix dcat: <http://www.w3.org/ns/dcat#> .
5
+ @prefix geo: <http://www.opengis.net/ont/geosparql#> .
6
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
7
+ @prefix example: <http://example.org/> .
8
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
9
+ @prefix prov: <http://www.w3.org/ns/prov#> .
10
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
11
+
12
+ example:4f2a8de3-9fc8-40a9-9237-d5964520ec54
13
+ a dcat:Dataset, example:ArchitectureModelDataset ;
14
+ dcterms:title "Einsteinturm"@de, "Einstein Tower"@en ;
15
+ dcterms:description "Modell des Einsteinturms"@de, "Model of the Einstein Tower"@en ;
16
+ dcterms:issued "2023-07-27"^^xsd:date ;
17
+ dcterms:subject <https://w3id.org/dfgfo/2024/451-01>;
18
+ dcterms:license <http://creativecommons.org/licenses/by/4.0/> ;
19
+ schema:artworkSurface example:plaster ;
20
+ schema:width 200 ;
21
+ dcterms:spatial [
22
+ a dcterms:Location ;
23
+ geo:asWKT "POLYGON((13.06382134836241 52.37900504575066,13.063796707503286 52.37896794299019,13.063798350228126 52.37875635638159,13.063926482692182 52.37875435081642,13.0639281254158 52.378964934657034,13.063905127281544 52.37900404297392,13.06382134836241 52.37900504575066))"^^geo:wktLiteral ;
24
+ dcterms:description "Building has been realized here" ;
25
+ ] ;
26
+ dcterms:spatial [
27
+ a dcterms:Location ;
28
+ geo:asWKT "POINT(8.681927539753275 50.09895428462539)"^^geo:wktLiteral ;
29
+ dcterms:description "Model is stored here" ;
30
+ ] ;
31
+ prov:qualifiedAttribution [
32
+ a prov:Attribution ;
33
+ prov:agent [
34
+ a foaf:Person ;
35
+ foaf:name "Jane Doe";
36
+ dcterms:identifier "https://orcid.org/0000-0002-1584-4316" ;
37
+ ] ;
38
+ dcat:hadRole <http://w3id.org/nfdi4ing/metadata4ing#Researcher> ;
39
+ ] .
40
+
41
+
42
+ example:darmstadt
43
+ a dcterms:Location ;
44
+ rdfs:label "Darmstadt" ;
45
+ geo:asWKT "POINT(8.657631022574474 49.87622847744955)"^^geo:wktLiteral ;
46
+ dcterms:description "Darmstadt is a city in germany" .