@igea/oac_backend 1.0.47 → 1.0.48

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.47",
3
+ "version": "1.0.48",
4
4
  "description": "Backend service for the OAC project",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3,16 +3,10 @@ const multer = require('multer');
3
3
  const router = express.Router();
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
- const config = require('../config')
7
- const configFuseki = config.fuseki || {
8
- "protocol": "http",
9
- "host": "127.0.0.1",
10
- "port": "3030",
11
- "dataset": "oac"
12
- }
13
- const fusekiUrlDataset = `${configFuseki.protocol}://${configFuseki.host}:${configFuseki.port}/${configFuseki.dataset}`;
14
- const fusekiUrl = `${fusekiUrlDataset}/sparql`;
15
- const fusekiUrlUpdate = `${fusekiUrlDataset}/update`;
6
+ const {
7
+ fusekiUrl,
8
+ fusekiUrlUpdate
9
+ } = require('../models/fusekiConfig');
16
10
  const axios = require('axios');
17
11
  const Fuseki = require('../models/fuseki');
18
12
  const { Parser, transformMode } = require('../models/vocabolaries/parser');
@@ -7,6 +7,12 @@ const Converter = require('../models/converter');
7
7
  const Validator = require('../models/validator');
8
8
  const tmp = require('tmp');
9
9
  const Investigations = require('../models/investigations');
10
+ const {
11
+ fusekiUrlDataset,
12
+ fusekiUrl,
13
+ fusekiUrlUpdate
14
+ } = require('../models/fusekiConfig');
15
+ const axios = require('axios');
10
16
 
11
17
  let SCHEMAS = {}
12
18
 
@@ -112,19 +118,43 @@ router.post('/validate', (req, res) => {
112
118
  router.post('/form/save', (req, res) => {
113
119
  let dataset = req.body.turtle;
114
120
  let uuid = req.body.uuid;
115
- Investigations.save({
116
- uuid, dataset, format: 'turtle'
117
- }).then( () => {
118
- res.json({
119
- success: true
120
- });
121
- }).catch( (err) => {
122
- console.log("Error saving investigation: ", err);
121
+ try{
122
+ let updateQuery = Converter.turtle2Sparql(dataset);
123
+ Investigations.save({
124
+ uuid, dataset, format: 'turtle'
125
+ }).then( () => {
126
+ axios.post(fusekiUrlUpdate, updateQuery, {
127
+ headers: {
128
+ 'Content-Type': 'application/sparql-update',
129
+ 'Accept': 'application/sparql-results+json'
130
+ }
131
+ }).then(response => {
132
+ console.log(response.data);
133
+ res.status(200).json({
134
+ success: true
135
+ });
136
+ }).catch(error => {
137
+ //TODO: rollback investigation save
138
+ let message = (error.response?.status + error.response?.data) || error.message
139
+ res.status(500).json({
140
+ message: 'Error from SPARQL end-point: ' + message,
141
+ success: false
142
+ });
143
+ });
144
+ }).catch( (err) => {
145
+ console.log("Error saving investigation: ", err);
146
+ res.json({
147
+ success: false,
148
+ message: `Error: ${err}`
149
+ });
150
+ });
151
+ }catch(e){
123
152
  res.json({
124
153
  success: false,
125
- message: `Error: ${err}`
154
+ message: `Error: ${e.message}`
126
155
  });
127
- });
156
+ return;
157
+ }
128
158
  });
129
159
 
130
160
  router.get('/form/:uuid', (req, res) => {
@@ -17,6 +17,83 @@ class Converter {
17
17
  })
18
18
  }
19
19
 
20
+ static termToSparql(term) {
21
+ if (!term) return '';
22
+ const t = term.termType || term.type; // some versions use .type
23
+ const value = term.value;
24
+ if (t === 'NamedNode' || t === 'IRI') {
25
+ return `<${value}>`;
26
+ }
27
+ if (t === 'BlankNode' || t === 'Blank') {
28
+ return `_:${value}`;
29
+ }
30
+ if (t === 'Literal' || t === 'literal') {
31
+ // escape per basic N-Triples rules
32
+ const esc = value
33
+ .replace(/\\/g, '\\\\')
34
+ .replace(/"/g, '\\"')
35
+ .replace(/\r/g, '\\r')
36
+ .replace(/\n/g, '\\n');
37
+ const lang = term.language;
38
+ const dt = term.datatype && term.datatype.value;
39
+ if (lang) return `"${esc}"@${lang}`;
40
+ if (dt && dt !== 'http://www.w3.org/2001/XMLSchema#string')
41
+ return `"${esc}"^^<${dt}>`;
42
+ return `"${esc}"`;
43
+ }
44
+ return `${value}`;
45
+ }
46
+
47
+ static turtle2Sparql(turtle, opts={}){
48
+ const graph = opts.graph || null; // if null -> default graph
49
+ const parser = new Parser();
50
+ const quads = parser.parse(turtle);
51
+ const termToSparql = Converter.termToSparql;
52
+ // group objects by subject+predicate
53
+ const groups = new Map(); // key -> { subj, pred, objects: Set() }
54
+ for (const q of quads) {
55
+ const s = q.subject;
56
+ const p = q.predicate;
57
+ const o = q.object;
58
+ const key = `${termToSparql(s)} ${termToSparql(p)}`;
59
+ if (!groups.has(key)) groups.set(key, { subj: s, pred: p, objects: new Set() });
60
+ groups.get(key).objects.add(termToSparql(o));
61
+ }
62
+
63
+ // build SPARQL update parts
64
+ const parts = [];
65
+ for (const [, { subj, pred, objects }] of groups) {
66
+ const sStr = termToSparql(subj);
67
+ const pStr = termToSparql(pred);
68
+
69
+ // DELETE WHERE: remove any existing object for the subject/predicate
70
+ // we use a variable ?o to delete any existing triples with same s,p
71
+ let deleteBlock;
72
+ if (graph) {
73
+ deleteBlock = `DELETE WHERE { GRAPH <${graph}> { ${sStr} ${pStr} ?o } };`;
74
+ } else {
75
+ deleteBlock = `DELETE WHERE { ${sStr} ${pStr} ?o } ;`;
76
+ }
77
+
78
+ // INSERT DATA: insert the objects we parsed. If multiple objects, join with comma.
79
+ const objs = Array.from(objects);
80
+ const objectsList = objs.join(' ,\n '); // pretty print
81
+ let insertBlock;
82
+ if (graph) {
83
+ insertBlock = `INSERT DATA { GRAPH <${graph}> { ${sStr} ${pStr} ${objectsList} . } };`;
84
+ } else {
85
+ insertBlock = `INSERT DATA { ${sStr} ${pStr} ${objectsList} . } ;`;
86
+ }
87
+
88
+ // append as one atomic unit (delete then insert)
89
+ parts.push(`${deleteBlock}\n${insertBlock}`);
90
+ }
91
+
92
+ // join with double newline for readability
93
+ return parts.join('\n\n');
94
+
95
+ }
96
+
20
97
  static async turtle2RdfXml(inTurtlePath, outRdfXmlPath) {
21
98
  return new Promise((resolve, reject) => {
22
99
  const command = `rapper -i turtle -o rdfxml "${inTurtlePath}" > "${outRdfXmlPath}"`;
@@ -0,0 +1,16 @@
1
+ const config = require('../config')
2
+ const configFuseki = config.fuseki || {
3
+ "protocol": "http",
4
+ "host": "127.0.0.1",
5
+ "port": "3030",
6
+ "dataset": "oac"
7
+ }
8
+ const fusekiUrlDataset = `${configFuseki.protocol}://${configFuseki.host}:${configFuseki.port}/${configFuseki.dataset}`;
9
+ const fusekiUrl = `${fusekiUrlDataset}/sparql`;
10
+ const fusekiUrlUpdate = `${fusekiUrlDataset}/update`;
11
+
12
+ module.exports = {
13
+ fusekiUrlDataset,
14
+ fusekiUrl,
15
+ fusekiUrlUpdate
16
+ }
@@ -27,4 +27,13 @@ describe('Converter', () => {
27
27
  });
28
28
  });
29
29
 
30
+ it('should convert a turtle string to sparql UPSERT', (done) => {
31
+ let inputTurtle = fs.readFileSync(__dirname + '/example-investigation-01.ttl', 'utf8');
32
+ let sparql = converter.turtle2Sparql(inputTurtle)
33
+ expect(sparql).to.be.a('string');
34
+ expect(sparql.length).to.be.greaterThan(0);
35
+ console.log(sparql);
36
+ done();
37
+ });
38
+
30
39
  })
@@ -0,0 +1,32 @@
1
+ @prefix sh: <http://www.w3.org/ns/shacl#>.
2
+ @prefix ex: <http://example.org/shapes/>.
3
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
4
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
5
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
6
+ @prefix owl: <http://www.w3.org/2002/07/owl#>.
7
+ @prefix crm: <http://www.cidoc-crm.org/cidoc-crm/>.
8
+ @prefix basecpm: <http://ontome.net/ns/cpm/>.
9
+ @prefix base: <http://www.ics.forth.gr/isl/CRMinf/>.
10
+ @prefix cpm: <http://ontome.net/ns/cpm/>.
11
+ @prefix crmsci: <http://www.cidoc-crm.org/extensions/crmsci/>.
12
+ @prefix pref: <http://diagnostica/>.
13
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#>.
14
+
15
+ pref:3cc24c8c-ce06-40ef-bdce-9da2c4c5394b ex:P48haspreferredidentifier01 <http://indagine/3>;
16
+ a crm:E42_Identifier.
17
+ pref:1adba9d5-c83b-41a8-b97f-c6e5b168658a a crm:E7_Activity;
18
+ crm:P48_has_preferred_identifier pref:3cc24c8c-ce06-40ef-bdce-9da2c4c5394b;
19
+ crm:P17_was_motivated_by pref:f30ead0e-edcc-4f6f-868e-8bc6f9d13813;
20
+ crm:P14_carried_out_by pref:57701c6c-ece4-472b-bc24-b75c1c254568;
21
+ <http://purl.org/dc/terms/conformsTo> ex:E7ActivityShape.
22
+ pref:40a75ae6-daa4-4ae1-bd41-2e07175f6f75 a crm:E55_Type;
23
+ ex:P2hastype02 <http://diagnostica/vocabularies/quesito-diagnostico/tecnologia-di-produzione>.
24
+ pref:f30ead0e-edcc-4f6f-868e-8bc6f9d13813 a base:I12_Adopted_Belief;
25
+ crm:P2_has_type pref:40a75ae6-daa4-4ae1-bd41-2e07175f6f75.
26
+ pref:95e957ad-55b5-45d3-bf62-6a292959c4f9 a crm:E41_Appellation;
27
+ ex:ente_richiedente "Università";
28
+ ex:schedatore "Christian".
29
+ pref:956f0c5a-6fc5-4a79-b399-6c6288fb000f a crm:E55_Type;
30
+ crm:P1_is_defined_by pref:95e957ad-55b5-45d3-bf62-6a292959c4f9.
31
+ pref:57701c6c-ece4-472b-bc24-b75c1c254568 a crm:E29_Actor;
32
+ crm:P2_has_type pref:956f0c5a-6fc5-4a79-b399-6c6288fb000f.