diff --git a/package-lock.json b/package-lock.json index 1eb4aa065..69e8e0d75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "origintrail_node", - "version": "8.0.1+hotfix.3", + "version": "8.0.1+hotfix.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "origintrail_node", - "version": "8.0.1+hotfix.3", + "version": "8.0.1+hotfix.4", "license": "ISC", "dependencies": { "@comunica/query-sparql": "^2.4.3", diff --git a/package.json b/package.json index b73005093..9d745b253 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "8.0.1+hotfix.3", + "version": "8.0.1+hotfix.4", "description": "OTNode V8", "main": "index.js", "type": "module", diff --git a/src/constants/constants.js b/src/constants/constants.js index d0a686bb8..9bb7f59f8 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -1014,6 +1014,11 @@ export const HTTP_API_ROUTES = { path: '/ask', options: {}, }, + 'local-store': { + method: 'post', + path: '/local-store', + options: {}, + }, }, }; diff --git a/src/controllers/http-api/v1/local-store-http-api-controller-v1.js b/src/controllers/http-api/v1/local-store-http-api-controller-v1.js new file mode 100644 index 000000000..4b3f2e518 --- /dev/null +++ b/src/controllers/http-api/v1/local-store-http-api-controller-v1.js @@ -0,0 +1,157 @@ +import { kcTools } from 'assertion-tools'; +import BaseController from '../base-http-api-controller.js'; +import { + PRIVATE_HASH_SUBJECT_PREFIX, + TRIPLE_STORE_REPOSITORIES, +} from '../../../constants/constants.js'; + +class LocalStoreController extends BaseController { + constructor(ctx) { + super(ctx); + this.validationService = ctx.validationService; + this.ualService = ctx.ualService; + this.tripleStoreService = ctx.tripleStoreService; + this.cryptoService = ctx.cryptoService; + } + + async handleRequest(req, res) { + const { dataset, blockchain, datasetRoot, UAL } = req.body; + let contract; + let knowledgeCollectionId; + try { + ({ contract, knowledgeCollectionId } = this.ualService.resolveUAL(UAL)); + const { publicKnowledgeAssetsUALs, privateKnowledgeAssetsUALs } = this.getKAUALs( + dataset, + UAL, + ); + + const alreadyInserted = await this.tripleStoreService.ask(` + ASK { + FILTER ( + ${[...publicKnowledgeAssetsUALs, ...privateKnowledgeAssetsUALs] + .map((ual) => `EXISTS { GRAPH <${ual}> { ?s ?p ?o } }`) + .join(' && ')} + ) + } + `); + + if (alreadyInserted) { + return this.returnResponse(res, 200, { + status: true, + }); + } + } catch (error) { + return this.returnResponse(res, 500, { + status: false, + error, + }); + } + + try { + const validations = [ + this.validationService.validateDatasetRoot(dataset.public, datasetRoot), + this.validationService.validateDatasetRootOnBlockchain( + datasetRoot, + blockchain, + contract, + knowledgeCollectionId, + ), + ]; + + if (dataset?.private?.length) { + validations.push( + this.validationService.validatePrivateMerkleRoot( + dataset.public, + dataset.private, + ), + ); + } + + await Promise.all(validations); + } catch (error) { + return this.returnResponse(res, 200, { + status: false, + error, + }); + } + try { + await this.tripleStoreService.insertKnowledgeCollection( + TRIPLE_STORE_REPOSITORIES.DKG, + UAL, + dataset, + ); + + return this.returnResponse(res, 200, { + status: true, + }); + } catch (error) { + return this.returnResponse(res, 200, { + status: false, + error, + }); + } + } + + getKAUALs(dataset, UAL) { + const privateHashTriples = []; + const filteredPublic = []; + let privateKnowledgeAssetsUALs = []; + // Check if already inserted + dataset.public.forEach((triple) => { + if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) { + privateHashTriples.push(triple); + } else { + filteredPublic.push(triple); + } + }); + + const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject( + filteredPublic, + true, + ); + publicKnowledgeAssetsTriplesGrouped.push( + ...kcTools.groupNquadsBySubject(privateHashTriples, true), + ); + + let publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map( + (_, index) => `${UAL}/${index + 1}`, + ); + + if (dataset.private?.length) { + const privateKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject( + dataset.private, + true, + ); + + const publicSubjectMap = publicKnowledgeAssetsTriplesGrouped.reduce( + (map, group, index) => { + const [publicSubject] = group[0].split(' '); + map.set(publicSubject, index); + return map; + }, + new Map(), + ); + + for (const privateTriple of privateKnowledgeAssetsTriplesGrouped) { + const [privateSubject] = privateTriple[0].split(' '); + if (publicSubjectMap.has(privateSubject)) { + const ualIndex = publicSubjectMap.get(privateSubject); + privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]); + } else { + const privateSubjectHashed = `<${PRIVATE_HASH_SUBJECT_PREFIX}${this.cryptoService.sha256( + privateSubject.slice(1, -1), + )}>`; + if (publicSubjectMap.has(privateSubjectHashed)) { + const ualIndex = publicSubjectMap.get(privateSubjectHashed); + privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]); + } + } + } + } + publicKnowledgeAssetsUALs = publicKnowledgeAssetsUALs.map((ual) => `${ual}/public`); + privateKnowledgeAssetsUALs = privateKnowledgeAssetsUALs.map((ual) => `${ual}/private`); + return { publicKnowledgeAssetsUALs, privateKnowledgeAssetsUALs }; + } +} + +export default LocalStoreController; diff --git a/src/controllers/http-api/v1/request-schema/local-store-schema-v1.js b/src/controllers/http-api/v1/request-schema/local-store-schema-v1.js new file mode 100644 index 000000000..a66c75e0b --- /dev/null +++ b/src/controllers/http-api/v1/request-schema/local-store-schema-v1.js @@ -0,0 +1,37 @@ +export default (argumentsObject) => ({ + type: 'object', + required: ['datasetRoot', 'dataset', 'blockchain'], + properties: { + datasetRoot: { + type: 'string', + minLength: 66, + maxLength: 66, + }, + dataset: { + type: 'object', + properties: { + public: { + type: 'array', + items: { + type: 'string', + }, + minItems: 1, + }, + private: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: ['public'], + additionalProperties: false, + }, + blockchain: { + enum: argumentsObject.blockchainImplementationNames, + }, + UAL: { + type: 'string', + }, + }, +}); diff --git a/src/modules/triple-store/triple-store-module-manager.js b/src/modules/triple-store/triple-store-module-manager.js index cde1972f0..69c5371a6 100644 --- a/src/modules/triple-store/triple-store-module-manager.js +++ b/src/modules/triple-store/triple-store-module-manager.js @@ -282,6 +282,12 @@ class TripleStoreModuleManager extends BaseModuleManager { } } + async ask(implementationName, repository, query) { + if (this.getImplementation(implementationName)) { + return this.getImplementation(implementationName).module.ask(repository, query); + } + } + getName() { return 'tripleStore'; } diff --git a/src/service/triple-store-service.js b/src/service/triple-store-service.js index 41c1226c7..75dba6e86 100644 --- a/src/service/triple-store-service.js +++ b/src/service/triple-store-service.js @@ -580,6 +580,15 @@ class TripleStoreService { ); } + async ask(query, repository = TRIPLE_STORE_REPOSITORY.DKG) { + return this.tripleStoreModuleManager.ask( + this.repositoryImplementations[repository] ?? + this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG], + repository, + query, + ); + } + async queryVoid(repository, query, namedGraphs = null, labels = null) { return this.tripleStoreModuleManager.queryVoid( this.repositoryImplementations[repository],