@igea/oac_backend 1.0.43 → 1.0.45

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.43",
3
+ "version": "1.0.45",
4
4
  "description": "Backend service for the OAC project",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -36,6 +36,7 @@
36
36
  "n3": "1.26.0",
37
37
  "nodemailer": "7.0.6",
38
38
  "pg": "8.16.3",
39
+ "rdf-validate-shacl": "0.6.5",
39
40
  "strip-bom": "5.0.0",
40
41
  "tmp": "0.2.5"
41
42
  },
@@ -4,50 +4,61 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
  const ONTO_FOLDER = path.join(__dirname, '..', 'ontology');
6
6
  const Converter = require('../models/converter');
7
+ const Validator = require('../models/validator');
7
8
  const tmp = require('tmp');
9
+ const Investigations = require('../models/investigations');
8
10
 
9
- router.get('/schema/:format', (req, res) => {
10
- console.log(`Requesting SHACL schema in format: ${req.params.format}`);
11
- let format = req.params.format || 'ttl';
11
+ let SCHEMAS = {}
12
+
13
+ const getSchema = function(format){
14
+ let fileContent = null;
12
15
  let filePath = null;
16
+ let fileType = null;
13
17
  switch(format){
14
18
  case 'ttl':
15
19
  filePath = 'config.shacl.ttl'; //'schema_v1.shacl.ttl';
16
- res.setHeader('Content-Type', 'text/turtle');
20
+ fileType = 'text/turtle';
17
21
  break;
18
22
  case 'ttl2':
19
23
  filePath = 'schema_v2.shacl.ttl';
20
- res.setHeader('Content-Type', 'text/turtle');
24
+ fileType = 'text/turtle';
21
25
  break;
22
26
  case 'jsonld':
23
27
  filePath = 'schema_v1.shacl.jsonld';
24
- res.setHeader('Content-Type', 'application/ld+json');
28
+ fileType = 'application/ld+json';
25
29
  break;
26
30
  case 'xml':
27
31
  filePath = 'schema_v1.shacl.rdf';
28
- res.setHeader('Content-Type', 'application/rdf+xml');
32
+ fileType = 'application/rdf+xml';
29
33
  break;
30
34
  default:
31
- res.status(400).json({
32
- success: false,
33
- data: null,
34
- message: `Unsupported format: ${format}`
35
- });
36
- return;
35
+ throw new Error(`Unsupported format: ${format}`);
37
36
  }
37
+ if(SCHEMAS.hasOwnProperty(format)){
38
+ fileContent = SCHEMAS[format];
39
+ }
40
+ if(!fileContent){
41
+ fileContent = fs.readFileSync(path.join(ONTO_FOLDER, filePath), 'utf8');
42
+ let protocol = process.env.OAC_EXPOSED_PROTOCOL || 'http';
43
+ let host = process.env.OAC_EXPOSED_HOST || '127.0.0.1';
44
+ if(host=="localhost") host="127.0.0.1";
45
+ let port = process.env.OAC_EXPOSED_PORT || '4000';
46
+ fileContent = fileContent.replace(/OAC_EXPOSED_PROTOCOL/g, protocol);
47
+ fileContent = fileContent.replace(/OAC_EXPOSED_HOST/g, host);
48
+ fileContent = fileContent.replace(/OAC_EXPOSED_PORT/g, port);
49
+ SCHEMAS[format] = fileContent;
50
+ }
51
+ return { content: fileContent , path: filePath, type: fileType };
52
+ }
38
53
 
39
- let fileContent = fs.readFileSync(path.join(ONTO_FOLDER, filePath), 'utf8');
40
- let protocol = process.env.OAC_EXPOSED_PROTOCOL || 'http';
41
- let host = process.env.OAC_EXPOSED_HOST || '127.0.0.1';
42
- if(host=="localhost") host="127.0.0.1";
43
- let port = process.env.OAC_EXPOSED_PORT || '4000';
44
- fileContent = fileContent.replace(/OAC_EXPOSED_PROTOCOL/g, protocol);
45
- fileContent = fileContent.replace(/OAC_EXPOSED_HOST/g, host);
46
- fileContent = fileContent.replace(/OAC_EXPOSED_PORT/g, port);
47
-
48
- const tempFile = tmp.fileSync({ postfix: filePath });
54
+ router.get('/schema/:format', (req, res) => {
55
+ console.log(`Requesting SHACL schema in format: ${req.params.format}`);
56
+ let format = req.params.format || 'ttl';
57
+ let schema = getSchema(format);
58
+ let fileContent = schema.content;
59
+ res.setHeader('Content-Type', schema.type);
60
+ const tempFile = tmp.fileSync({ postfix: schema.path });
49
61
  fs.writeFileSync(tempFile.name, fileContent);
50
-
51
62
  res.sendFile(tempFile.name, (err) => {
52
63
  if (err) {
53
64
  res.status(500).json({
@@ -59,6 +70,77 @@ router.get('/schema/:format', (req, res) => {
59
70
  });
60
71
  });
61
72
 
73
+ router.get('/counter/:name', (req, res) => {
74
+ let name = req.params.name;
75
+ Investigations.getCounter(name).then( (count) => {
76
+ res.json({
77
+ success: true,
78
+ data: count,
79
+ message: `Counter ${name} value retrieved`
80
+ });
81
+ }).catch( (err) => {
82
+ console.log(err);
83
+ res.status(500).json({
84
+ success: false,
85
+ data: null,
86
+ message: `Error retrieving counter ${name}: ${err}`
87
+ });
88
+ });
89
+ });
90
+
91
+ router.post('/validate', (req, res) => {
92
+ let turtle = req.body.turtle;
93
+ let schema = getSchema('ttl2');
94
+ console.log("validate...")
95
+ let shacl = schema.content.replace(/owl:imports/g, '#owl:imports');
96
+ Validator.validateDataSyntax(turtle, shacl).then( (result) => {
97
+ res.json({
98
+ success: true,
99
+ data: result,
100
+ message: 'Validation completed'
101
+ });
102
+ }).catch( (err) => {
103
+ console.log(err);
104
+ res.status(500).json({
105
+ success: false,
106
+ data: null,
107
+ message: `Validation error: ${err}`
108
+ });
109
+ });
110
+ });
111
+
112
+ router.post('/form/save', (req, res) => {
113
+ let dataset = req.body.turtle;
114
+ 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);
123
+ res.json({
124
+ success: false,
125
+ message: `Error: ${err}`
126
+ });
127
+ });
128
+ });
129
+
130
+ router.get('/form/:uuid', (req, res) => {
131
+ let uuid = req.params.uuid;
132
+ res.setHeader('Content-Type', 'text/turtle');
133
+ Investigations.get(uuid).then( (response) => {
134
+ res.send(response ? response.dataset : null);
135
+ }).catch( (err) => {
136
+ console.log("Error getting investigation datset: ", err);
137
+ res.json({
138
+ success: false,
139
+ message: `Error: ${err}`
140
+ });
141
+ });
142
+ });
143
+
62
144
  router.get('/schema/:type/:what', (req, res) => {
63
145
  console.log(`Requesting SHACL schema in format: ${req.params.type}`);
64
146
  let type = req.params.type || 'config';
@@ -0,0 +1,56 @@
1
+ const {db, schema } = require('./db')
2
+ const table = `${schema}.investigations`
3
+
4
+ class Investigations {
5
+
6
+ static get(uuid){
7
+ return new Promise(async (resolve, reject) => {
8
+ try{
9
+ const item = await db(table).select('*').where({ uuid }).first();
10
+ resolve(item)
11
+ }catch(e){
12
+ reject(e)
13
+ }
14
+ });
15
+ }
16
+
17
+ static getCounter(name){
18
+ return new Promise(async (resolve, reject) => {
19
+ try{
20
+ const sql = `SELECT nextval('${name}') as count`;
21
+ const result = await db.raw(sql);
22
+ const count = result.rows[0].count;
23
+ resolve(count)
24
+ }catch(e){
25
+ reject(e)
26
+ }
27
+ });
28
+ }
29
+
30
+ static save(item){
31
+ item.format = item.format || 'turtle'
32
+ return new Promise(async (resolve, reject) => {
33
+ try {
34
+ const existing = await db(table)
35
+ .where({uuid: item.uuid})
36
+ .first();
37
+ let operation = existing ? 'UPDATE' : 'INSERT';
38
+ if(existing){
39
+ await db(table)
40
+ .where({uuid: item.uuid})
41
+ .update(item);
42
+ }else{
43
+ await db(table).insert(item);
44
+ }
45
+ resolve({
46
+ success: true, operation
47
+ })
48
+ }catch(e){
49
+ reject(e)
50
+ }
51
+ });
52
+ }
53
+
54
+ }
55
+
56
+ module.exports = Investigations
@@ -0,0 +1,37 @@
1
+ const { Parser, Store } = require('n3');
2
+ const SHACLValidator = require('rdf-validate-shacl').default;
3
+
4
+
5
+ class Validator {
6
+
7
+ static turtleToDataset(turtleStr) {
8
+ const parser = new Parser({ format: 'text/turtle' });
9
+ const quads = parser.parse(turtleStr);
10
+ const store = new Store();
11
+ quads.forEach(q => store.addQuad(q));
12
+ return store;
13
+ }
14
+
15
+ static async validateDataSyntax(dataContent, shapesContent) {
16
+ const dataDS = Validator.turtleToDataset(dataContent);
17
+ const shapesDS = Validator.turtleToDataset(shapesContent);
18
+ const validator = new SHACLValidator(shapesDS);
19
+ const report = await validator.validate(dataDS);
20
+ const details = (report.results || []).map(r => ({
21
+ focusNode: r.focusNode?.value,
22
+ message: (r.message || []).map(m => m.value || m),
23
+ path: r.path?.value,
24
+ severity: r.severity?.value,
25
+ sourceShape: r.sourceShape?.value,
26
+ sourceConstraintComponent: r.sourceConstraintComponent?.value,
27
+ value: r.value?.value
28
+ }));
29
+ return {
30
+ conforms: report.conforms,
31
+ details
32
+ }
33
+ }
34
+
35
+ }
36
+
37
+ module.exports = Validator ;
@@ -117,14 +117,14 @@ ex:E29ActorShape
117
117
  sh:targetClass crm:E29_Actor ;
118
118
  sh:property [
119
119
  sh:path ex:ente_richiedente ;
120
- sh:datatype xsd:string ;
120
+ sh:datatype xsd:string ;
121
121
 
122
122
  #sh:name "Ente richiedente" ;
123
123
  #sh:maxCount 1 ;
124
124
 
125
125
  sh:name "ID" ;
126
126
  sh:nodeKind sh:IRI;
127
- sh:description "http://diagnostica/actor/$UUID$" ;
127
+ sh:description "http://diagnostica/actor/$SEQ3$" ;
128
128
 
129
129
  ] ;
130
130
  sh:property [
@@ -0,0 +1,76 @@
1
+ const chai = require('chai');
2
+ const expect = chai.expect;
3
+ const request = require('supertest');
4
+ const Validator = require('../../src/models/validator');
5
+ const fs = require('fs')
6
+
7
+ describe('Validator', () => {
8
+
9
+ const shaclTurtle = `
10
+ @prefix ex: <http://example.com/ns#> .
11
+ @prefix sh: <http://www.w3.org/ns/shacl#> .
12
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
13
+
14
+ ex:PersonShape a sh:NodeShape ;
15
+ sh:targetClass ex:Person ;
16
+ sh:property [
17
+ sh:path ex:age ;
18
+ sh:datatype xsd:integer ; # Richiede un intero
19
+ ] .
20
+ `;
21
+
22
+ beforeEach(() => {
23
+
24
+ });
25
+
26
+ it('should convert a turtle into a Dataset with 3 triples', () => {
27
+ const inputTurtle = `
28
+ @prefix ex: <http://example.org/> .
29
+ ex:subject1 ex:predicate1 ex:object1 .
30
+ ex:subject2 ex:predicate2 ex:object2 .
31
+ ex:subject3 ex:predicate3 ex:object3 .
32
+ `;
33
+
34
+ const ds = Validator.turtleToDataset(inputTurtle);
35
+
36
+ // Controlla che siano presenti 3 triple
37
+ expect(ds.size).to.equal(3);
38
+
39
+ // Controllo opzionale: verifica che i soggetti siano corretti
40
+ const subjects = Array.from(ds).map(q => q.subject.value);
41
+ expect(subjects).to.include.members([
42
+ 'http://example.org/subject1',
43
+ 'http://example.org/subject2',
44
+ 'http://example.org/subject3'
45
+ ]);
46
+ });
47
+
48
+ it('should fail for turtle with wrong age type', async () => {
49
+ const wrongTurtle = `
50
+ @prefix ex: <http://example.com/ns#> .
51
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
52
+ ex:Alice a ex:Person ;
53
+ ex:age "dodici" . # Valore letterale non valido per xsd:integer
54
+ `;
55
+ const result = await Validator.validateDataSyntax(wrongTurtle, shaclTurtle);
56
+ expect(result.conforms).to.equal(false);
57
+ expect(result.details.length).to.equal(1);
58
+ expect(result.details[0].sourceConstraintComponent).to.equal('http://www.w3.org/ns/shacl#DatatypeConstraintComponent');
59
+ expect(result.details[0].value).to.equal('dodici');
60
+
61
+ });
62
+
63
+ it('should pass for turtle with valid age type', async () => {
64
+ const wrongTurtle = `
65
+ @prefix ex: <http://example.com/ns#> .
66
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
67
+ ex:Alice a ex:Person ;
68
+ ex:age 12 . # Valore valido per xsd:integer
69
+ `;
70
+ const result = await Validator.validateDataSyntax(wrongTurtle, shaclTurtle);
71
+ expect(result.conforms).to.equal(true);
72
+ expect(result.details.length).to.equal(0);
73
+
74
+ });
75
+
76
+ })