From acfe6fec96771a84eec2f90cb655a4538c6da684 Mon Sep 17 00:00:00 2001 From: simvalery Date: Fri, 29 Sep 2023 19:41:53 +0400 Subject: [PATCH] Merging develop into main for 2.17.0 release (#2713) * Add GCP Secret Manager * Updated ICP Timestamps * gulp Signed-off-by: simvalery * Revert "Add GCP Secret Manager" This reverts commit c9762344f36fe2053ff35d75f60616b387583c36. * Revert "Add GCP Secret Manager" This reverts commit ac02124dbae014db88c6b9499c0b9b12c8fefd05. * Front end of meeco integration Signed-off-by: Felipe Neuhauss * Rebase with develop branch Signed-off-by: Felipe Neuhauss * Meeco provider conditional configuration Signed-off-by: Felipe Neuhauss * Meeco integration flows and docs Signed-off-by: Felipe Neuhauss * Meeco credntials validation - checking revoking Signed-off-by: Felipe Neuhauss * Rebase with develop branch Signed-off-by: Felipe Neuhauss * Fix verificiation of the Meeco auth provider config Signed-off-by: Felipe Neuhauss * GITBOOK-402: Subpages added regarding "Deploying Guardian Using default Environment" * fix 2509 Signed-off-by: simvalery * Documentation and Readme Signed-off-by: otherNet * fix lint Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * GITBOOK-398: meeco-auth-integration * GITBOOK-404: Updated previewnet topic ID * fix aws test secret Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * GITBOOK-403: add explicit version on cloud deployment and delete old cloud infra roadmap page * Console log removed Signed-off-by: Felipe Neuhauss * Console log removed Signed-off-by: Felipe Neuhauss * Preused Documentation on README Signed-off-by: otherNet * Syntax check Signed-off-by: otherNet * fix lint Signed-off-by: simvalery * GITBOOK-405: page added under "Installation" for Upgrading/Backup tools and new sections for community/feedback * GITBOOK-405: page added under "Installation" for Upgrading/Backup tools and new sections for community/feedback * GITBOOK-408: Format fixes * demo build Signed-off-by: simvalery * fix production build Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * docker build Signed-off-by: simvalery * docker build Signed-off-by: simvalery * update * fix scroll * GITBOOK-406: Adding GCP Section * update * GITBOOK-375: Fixing UX issue and Environment Topic * update Signed-off-by: Stepan Kiryakov * GITBOOK-412: "User Profile setup" final page for UX in selecting standards * update Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * add aliases Signed-off-by: Stepan Kiryakov * fix update policy hash Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * add document comparator Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * GITBOOK-410: added docs for Improvements Performance/Cloud infrastructure/updated user journey/demo experience * Fixed local IPFS node configuration and updated to v0.22.0; Signed-off-by: Giuseppe Bertone * Fix icon alignment in schema-form component Signed-off-by: Iryna Telesheva * update Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * GITBOOK-413: changes on improvements on the UX * GITBOOK-414: updated corrections in icons * 2531 Signed-off-by: simvalery * 2530 Signed-off-by: simvalery * restore user accounts for restored SR Signed-off-by: simvalery * fix restore users Signed-off-by: simvalery * fix malformed error Signed-off-by: simvalery * added new cdm policy [skip ci] Signed-off-by: Artem Buslaev * fix wrong redirects Signed-off-by: Artem Buslaev * fix long names in enums Signed-off-by: Artem Buslaev * fix cdm policy [skip ci] Signed-off-by: Artem Buslaev * up version to prerelease Signed-off-by: simvalery * [skip ci] Add swagger.yaml * update Signed-off-by: Stepan Kiryakov * fix initialization topicId Signed-off-by: simvalery * fix snyk Signed-off-by: simvalery * fix snyk Signed-off-by: simvalery * fix snyk Signed-off-by: simvalery * fix snyk Signed-off-by: simvalery * fixes package.json Signed-off-by: simvalery * fix demo publish path Signed-off-by: simvalery * GITBOOK-415: Removed spacing for Community standards pages * update Signed-off-by: Stepan Kiryakov * fix attributes events Signed-off-by: simvalery * fix geojson type Signed-off-by: Artem Buslaev * fix logs attrs Signed-off-by: simvalery Signed-off-by: simvalery * fix geojson type Signed-off-by: simvalery Signed-off-by: simvalery * fix navigation Signed-off-by: simvalery Signed-off-by: simvalery * fix error stack trace Signed-off-by: simvalery Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * GITBOOK-416: AMS III D guide added * Create readme.md * Add files via upload * Update readme.md of AMS III D * 2601 Signed-off-by: simvalery Signed-off-by: simvalery * fix schema import Signed-off-by: simvalery Signed-off-by: simvalery * bump version Signed-off-by: simvalery Signed-off-by: simvalery * [skip ci] Add swagger.yaml * remove extra policy Signed-off-by: simvalery Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix spell Signed-off-by: simvalery Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * separate npm and yarn builds Signed-off-by: simvalery Signed-off-by: simvalery * about page Signed-off-by: simvalery Signed-off-by: simvalery * [skip ci] Add swagger.yaml * update Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * fix README.md Signed-off-by: simvalery Signed-off-by: simvalery * fix README.md Signed-off-by: simvalery Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * fix compare documents Signed-off-by: Stepan Kiryakov * update GHGP policy [skip ci] Signed-off-by: Artem Buslaev * [2400] add lock files to containers Signed-off-by: simvalery Signed-off-by: simvalery * replace npm with yarn Signed-off-by: simvalery Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * fix wizard circular dependency Signed-off-by: Artem Buslaev * update Signed-off-by: Stepan Kiryakov * pass assignedTo props Signed-off-by: Artem Buslaev * update ghgp policy [skip ci] Signed-off-by: Artem Buslaev * fix error response Signed-off-by: simvalery * update Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * update Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * upd redd policies [skip ci] Signed-off-by: Artem Buslaev * fix Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * update Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix task wrong schema redirect Signed-off-by: Artem Buslaev * added delete schema validator Signed-off-by: Artem Buslaev * [skip ci] Add swagger.yaml * fix Signed-off-by: Artem Buslaev * limit page log size Signed-off-by: simvalery * fix limit Signed-off-by: simvalery * fix Signed-off-by: Stepan Kiryakov * fix export tool Signed-off-by: Stepan Kiryakov * fix import error messages Signed-off-by: Stepan Kiryakov * fix logs saving Signed-off-by: simvalery * fix lint Signed-off-by: Stepan Kiryakov * fix port Signed-off-by: simvalery * fix remove limit Signed-off-by: simvalery * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * fix block validation Signed-off-by: Stepan Kiryakov * fix error message Signed-off-by: Stepan Kiryakov * fix watch tasks Signed-off-by: simvalery * fix import artifacts Signed-off-by: Stepan Kiryakov * bump version Signed-off-by: simvalery * fix readonly schemas Signed-off-by: Stepan Kiryakov * fix system schemas Signed-off-by: Stepan Kiryakov * fix progress list overflow Signed-off-by: simvalery * add tags Signed-off-by: Stepan Kiryakov * [skip ci] Add swagger.yaml * fix Signed-off-by: Stepan Kiryakov * fix Signed-off-by: Stepan Kiryakov * Update timestamp of ACM0003 * Update readme.md of AMS-IIID * Update readme.md AMS-III.AR * Update readMe.md AMS.IIG * Update README.md GoldStandard AR * Update README.md Verra ARR timestamp * Update readMe.md VM0042 timestamp * Update README.md VM0017 Timestamp * Update readme.md Verra timestamps * fix tools config * fix import tags * fix compare dialog * update swagger Signed-off-by: Stepan Kiryakov * fix lint Signed-off-by: Stepan Kiryakov * upd verra redd 1 [skip ci] Signed-off-by: Artem Buslaev * bump version Signed-off-by: simvalery * increase hedera test timeout Signed-off-by: simvalery --------- Signed-off-by: simvalery Signed-off-by: Felipe Neuhauss Signed-off-by: otherNet Signed-off-by: Stepan Kiryakov Signed-off-by: Giuseppe Bertone Signed-off-by: Iryna Telesheva Signed-off-by: Artem Buslaev Co-authored-by: unknown Co-authored-by: prernaadev01 Co-authored-by: Felipe Neuhauss Co-authored-by: Serg Metelin Co-authored-by: Stepan Kiryakov Co-authored-by: otherNet Co-authored-by: envision-ci-agent Co-authored-by: Vanessa Lopes Co-authored-by: muhammad.uzair Co-authored-by: Giuseppe Bertone Co-authored-by: Iryna Telesheva Co-authored-by: Artem Buslaev --- .../CDM/CDM AMS-II.G/readMe.md | 6 +- .../CDM/CDM AMS-III.AR/readme.md | 4 +- .../CDM/CDM AMS-III.D/readme.md | 6 +- .../CDM/CDM AR-ACM0003/readme.md | 2 +- .../GoldStandard/GoldStandard AR/README.md | 2 +- .../Policies/Verra VM0007 (1.0.0).policy | Bin 38503 -> 38583 bytes .../Verra/Verra Redd/VM0007/readme.md | 6 +- .../Verra/Verra Redd/VM0017/README.md | 2 +- .../Verra/Verra Redd/VM0042/readMe.md | 2 +- .../Verra/Verra Redd/VerraARR/README.md | 2 +- analytics-service/package.json | 7 +- api-gateway/package.json | 7 +- api-gateway/src/api/service/analytics.ts | 381 +++++++++++++- api-gateway/src/api/service/schema.ts | 480 +++++++++++++----- .../validation/fields-validation.ts | 62 +-- .../src/middlewares/validation/index.ts | 26 +- .../validation/schemas/analytics.ts | 205 ++++++++ .../middlewares/validation/schemas/index.ts | 3 +- .../middlewares/validation/schemas/schemas.ts | 14 + api-tests/package.json | 5 +- auth-service/package.json | 7 +- common/package.json | 5 +- common/src/entity/tool.ts | 6 + .../message/message-body.interface.ts | 4 + .../hedera-modules/message/tool-message.ts | 8 + frontend/package.json | 3 +- .../notification/notification.component.scss | 4 +- .../async-progress.component.css | 9 +- .../async-progress.component.html | 4 +- .../async-progress.component.ts | 57 ++- .../compare-modules-dialog.component.ts | 3 + .../compare-policy-dialog.component.ts | 7 +- .../policy-configuration.component.ts | 16 +- .../policy-settings.component.scss | 2 +- .../policy-models/module/block.model.ts | 7 +- .../policy-models/policy/policy.model.ts | 5 +- .../policy-models/tool/block.model.ts | 5 +- .../tools-list/tools-list.component.ts | 41 +- frontend/src/app/services/tools.service.ts | 4 + .../admin/about-view/about-view.component.css | 2 +- .../app/views/schemas/schemas.component.html | 1 - guardian-service/package.json | 7 +- .../api/helpers/tool-import-export-helper.ts | 4 +- guardian-service/src/api/tag.service.ts | 52 +- guardian-service/src/api/tool.service.ts | 35 +- interfaces/package.json | 3 +- interfaces/src/type/topic.type.ts | 1 + logger-service/package.json | 7 +- mrv-sender/package.json | 5 +- notification-service/package.json | 7 +- package.json | 3 +- policy-service/package.json | 7 +- swagger-analytics.yaml | 2 +- swagger.yaml | 2 +- topic-viewer/package.json | 3 +- tree-viewer/package.json | 3 +- worker-service/package.json | 7 +- .../network-tests/hedera-sdk-helper.test.js | 2 +- yarn.lock | 40 +- 59 files changed, 1284 insertions(+), 328 deletions(-) create mode 100644 api-gateway/src/middlewares/validation/schemas/analytics.ts diff --git a/Methodology Library/CDM/CDM AMS-II.G/readMe.md b/Methodology Library/CDM/CDM AMS-II.G/readMe.md index 558179b228..d99753b77b 100644 --- a/Methodology Library/CDM/CDM AMS-II.G/readMe.md +++ b/Methodology Library/CDM/CDM AMS-II.G/readMe.md @@ -36,7 +36,7 @@ AMS-II.G provides a standardized and measurable framework for clean cooking proj ## Demo Video -Coming Soon +[Youtube](https://www.youtube.com/watch?v=jfl72_fL6iU) ## Policy Workflow @@ -46,7 +46,7 @@ Coming Soon This policy is published to Hedera network and can either be imported via Github (.policy file) or IPSF timestamp. -Policy: 1690817087.866705082 +Policy: 1695225791.622644989 Tool 30: 1690820465.670044734 Tool 33: 1690820484.707441003 @@ -74,7 +74,7 @@ Certified Emission Reduction (CER) credits, each equivalent to one tonne of CO2. ### Step By Step -1. The policy can be imported using IPFS timestamp 1690462366.484472937 +1. The policy can be imported using IPFS timestamp 1695225791.622644989 image diff --git a/Methodology Library/CDM/CDM AMS-III.AR/readme.md b/Methodology Library/CDM/CDM AMS-III.AR/readme.md index 3171cfed15..da19fe6b7b 100644 --- a/Methodology Library/CDM/CDM AMS-III.AR/readme.md +++ b/Methodology Library/CDM/CDM AMS-III.AR/readme.md @@ -51,7 +51,7 @@ reductions. This policy is published to the Hedera network and can either be imported via Github(.policy file) or IPFS timestamp. Timestamp: -Policy: 1690817347.617246606 +Policy: 1695233862.252855821 Tool 33: 1690820529.197570717 ### Available Roles @@ -93,7 +93,7 @@ Certified Emission Reduction (CER) credits, each equivalent to one tonne of CO2. ### Step By Step -1. The policy can be imported using IPFS timestamp 1688046287.912072347 +1. The policy can be imported using IPFS timestamp 1695233862.252855821 image diff --git a/Methodology Library/CDM/CDM AMS-III.D/readme.md b/Methodology Library/CDM/CDM AMS-III.D/readme.md index 39a7cd2d46..ce2c3e1ff5 100644 --- a/Methodology Library/CDM/CDM AMS-III.D/readme.md +++ b/Methodology Library/CDM/CDM AMS-III.D/readme.md @@ -34,7 +34,7 @@ In the context of modern emission reduction projects, the necessity for transpar ## Demo Video -Coming Soon +[Youtube](https://www.youtube.com/watch?v=jWpCgWl-92E&t=7s) ## Policy Workflow @@ -45,7 +45,7 @@ Coming Soon This policy is published to Hedera network and can either be imported via Github (.policy file) or IPSF timestamp. -Policy: 1693223777.317635003 +Policy: 1695235455.560040612 ### Available Roles @@ -78,7 +78,7 @@ Certified Emission Reduction (CER) credits, each equivalent to one tonne of CO2. ### Step By Step -1. The policy can be imported using IPFS timestamp 1693223777.317635003 +1. The policy can be imported using IPFS timestamp 1695235455.560040612 image diff --git a/Methodology Library/CDM/CDM AR-ACM0003/readme.md b/Methodology Library/CDM/CDM AR-ACM0003/readme.md index d991677994..9c9e68fbde 100644 --- a/Methodology Library/CDM/CDM AR-ACM0003/readme.md +++ b/Methodology Library/CDM/CDM AR-ACM0003/readme.md @@ -19,5 +19,5 @@ For Demo purpose, we have uploaded CDM Policy into IPFS and created Timestamps, | Version | IPFS Timestamp | Policy File Link | Version Differences | |---|---|---|---:| -| CDM AR-ACM0003 | 1690817784.010153219 | [Link](https://github.com/hashgraph/guardian/blob/main/Methodology%20Library/CDM/CDM.policy) | CDM AR-ACM0003 Methodology | +| CDM AR-ACM0003 | 1695237303.012398003 | [Link](https://github.com/hashgraph/guardian/blob/main/Methodology%20Library/CDM/CDM.policy) | CDM AR-ACM0003 Methodology | diff --git a/Methodology Library/GoldStandard/GoldStandard AR/README.md b/Methodology Library/GoldStandard/GoldStandard AR/README.md index e53ac8da6e..cd264ed185 100644 --- a/Methodology Library/GoldStandard/GoldStandard AR/README.md +++ b/Methodology Library/GoldStandard/GoldStandard AR/README.md @@ -20,4 +20,4 @@ The Gold Standard AR methodology promotes sustainable land use practices that co | Version | IPFS Timestamp | Policy File Link | Version Differences | |---|---|---|---:| -| GoldStandard AR | 1690818470.019357927 | [Link](https://github.com/hashgraph/guardian/blob/main/Methodology%20Library/GoldStandard/GoldStandard%20AR/Gold%20Standard%20AR.policy) | GoldStandard AR | +| GoldStandard AR | 1695251826.459491276 | [Link](https://github.com/hashgraph/guardian/blob/main/Methodology%20Library/GoldStandard/GoldStandard%20AR/Gold%20Standard%20AR.policy) | GoldStandard AR | diff --git a/Methodology Library/Verra/Verra Redd/VM0007/Policies/Verra VM0007 (1.0.0).policy b/Methodology Library/Verra/Verra Redd/VM0007/Policies/Verra VM0007 (1.0.0).policy index 2f418e83e930941037e93cb59bcc12d8cd2c2e96..8ee9441cf31951f978dd1788f8078f1e3ad422ac 100644 GIT binary patch delta 37385 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ξ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 Sra)~plH3#a4J@jzCV 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 delta 37173 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", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", "@nestjs/jwt": "^10.0.3", @@ -76,6 +76,5 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/api-gateway/package.json b/api-gateway/package.json index 404b26a339..3d7584ffb5 100644 --- a/api-gateway/package.json +++ b/api-gateway/package.json @@ -8,8 +8,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", "@nestjs/jwt": "^10.0.3", @@ -77,6 +77,5 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/api-gateway/src/api/service/analytics.ts b/api-gateway/src/api/service/analytics.ts index 87378bc13d..a8c48610bb 100644 --- a/api-gateway/src/api/service/analytics.ts +++ b/api-gateway/src/api/service/analytics.ts @@ -1,13 +1,71 @@ import { Guardians } from '@helpers/guardians'; import { Body, Controller, HttpCode, HttpException, HttpStatus, Post, Req } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { + ApiInternalServerErrorResponse, + ApiUnauthorizedResponse, + ApiForbiddenResponse, + ApiBody, + ApiOkResponse, + ApiOperation, + ApiSecurity, + ApiTags +} from '@nestjs/swagger'; import { checkPermission } from '@auth/authorization-helper'; import { UserRole } from '@guardian/interfaces'; +import { + FilterDocumentsDTO, + FilterModulesDTO, + FilterPoliciesDTO, + FilterSchemasDTO, + FilterSearchPoliciesDTO, + InternalServerErrorDTO, + CompareDocumentsDTO, + CompareModulesDTO, + ComparePoliciesDTO, + CompareSchemasDTO, + SearchPoliciesDTO +} from '@middlewares/validation/schemas'; + +const ONLY_SR = ' Only users with the Standard Registry role are allowed to make the request.' @Controller('analytics') @ApiTags('analytics') export class AnalyticsApi { + /** + * Search policies + */ @Post('/search/policies') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Search policies.', + description: 'Search policies.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterSearchPoliciesDTO, + examples: { + Filter: { + value: { + policyId: '000000000000000000000000' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SearchPoliciesDTO, + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async searchPolicies(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -30,7 +88,55 @@ export class AnalyticsApi { } } + /** + * Compare policies + */ @Post('/compare/policies') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare policies.', + description: 'Compare policies.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterPoliciesDTO, + examples: { + Filter1: { + value: { + policyId1: '000000000000000000000001', + policyId2: '000000000000000000000002', + eventsLvl: '0', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + }, + Filter2: { + value: { + policyIds: ['000000000000000000000001', '000000000000000000000002'], + eventsLvl: '0', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: ComparePoliciesDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async comparePolicies(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -72,7 +178,45 @@ export class AnalyticsApi { } } + /** + * Compare modules + */ @Post('/compare/modules') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare modules.', + description: 'Compare modules.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterModulesDTO, + examples: { + Filter: { + value: { + moduleId1: '000000000000000000000001', + moduleId2: '000000000000000000000002', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: CompareModulesDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareModules(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -110,6 +254,39 @@ export class AnalyticsApi { * Compare schemas */ @Post('/compare/schemas') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare schemas.', + description: 'Compare schemas.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterSchemasDTO, + examples: { + Filter: { + value: { + schemaId1: '000000000000000000000001', + schemaId2: '000000000000000000000002', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: CompareSchemasDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareSchemas(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -135,6 +312,43 @@ export class AnalyticsApi { * Compare documents */ @Post('/compare/documents') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare documents.', + description: 'Compare documents.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterDocumentsDTO, + examples: { + Filter1: { + value: { + documentId1: '000000000000000000000001', + documentId2: '000000000000000000000002' + } + }, + Filter2: { + value: { + documentIds: ['000000000000000000000001', '000000000000000000000002'], + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: CompareDocumentsDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareDocuments(@Body() body, @Req() req): Promise { const guardians = new Guardians(); @@ -175,11 +389,54 @@ export class AnalyticsApi { } /** - * Compare policies export - * @param body - * @param req + * Compare policies (CSV) */ @Post('/compare/policies/export') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare policies.', + description: 'Compare policies.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterPoliciesDTO, + examples: { + Filter1: { + value: { + policyId1: '000000000000000000000001', + policyId2: '000000000000000000000002', + eventsLvl: '0', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + }, + Filter2: { + value: { + policyIds: ['000000000000000000000001', '000000000000000000000002'], + eventsLvl: '0', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: String + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async comparePoliciesExport(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -220,7 +477,45 @@ export class AnalyticsApi { } } + /** + * Compare modules (CSV) + */ @Post('/compare/modules/export') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare modules.', + description: 'Compare modules.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterModulesDTO, + examples: { + Filter: { + value: { + moduleId1: '000000000000000000000001', + moduleId2: '000000000000000000000002', + propLvl: '0', + childrenLvl: '0', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: String + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareModulesExport(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -256,11 +551,42 @@ export class AnalyticsApi { } /** - * compareSchemasExport - * @param body - * @param req + * Compare schemas (CSV) */ @Post('/compare/schemas/export') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare schemas.', + description: 'Compare schemas.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterSchemasDTO, + examples: { + Filter: { + value: { + schemaId1: '000000000000000000000001', + schemaId2: '000000000000000000000002', + idLvl: '0' + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: String + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareSchemasExport(@Body() body, @Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -284,11 +610,46 @@ export class AnalyticsApi { } /** - * Compare documents export - * @param body - * @param req + * Compare documents (CSV) */ @Post('/compare/documents/export') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Compare documents.', + description: 'Compare documents.' + ONLY_SR, + }) + @ApiBody({ + description: 'Filters.', + required: true, + type: FilterDocumentsDTO, + examples: { + Filter1: { + value: { + documentId1: '000000000000000000000001', + documentId2: '000000000000000000000002' + } + }, + Filter2: { + value: { + documentIds: ['000000000000000000000001', '000000000000000000000002'], + } + } + } + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: String + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async compareDocumentsExport(@Body() body, @Req() req): Promise { const guardians = new Guardians(); diff --git a/api-gateway/src/api/service/schema.ts b/api-gateway/src/api/service/schema.ts index 1fc8b0d098..8fcac4ba97 100644 --- a/api-gateway/src/api/service/schema.ts +++ b/api-gateway/src/api/service/schema.ts @@ -17,8 +17,8 @@ import { ApiOkResponse, ApiOperation, ApiSecurity, + ApiBody, ApiTags, - getSchemaPath, ApiExtraModels } from '@nestjs/swagger'; import { @@ -42,7 +42,15 @@ import { ServiceError } from '@helpers/service-requests-base'; import { SchemaUtils } from '@helpers/schema-utils'; import { ApiImplicitQuery } from '@nestjs/swagger/dist/decorators/api-implicit-query.decorator'; import { ApiImplicitParam } from '@nestjs/swagger/dist/decorators/api-implicit-param.decorator'; -import { InternalServerErrorDTO, TaskDTO, SystemSchemaDTO, SchemaDTO, ExportSchemaDTO } from '@middlewares/validation/schemas'; +import { + InternalServerErrorDTO, + TaskDTO, + SystemSchemaDTO, + SchemaDTO, + ExportSchemaDTO, + MessageSchemaDTO, + VersionSchemaDTO +} from '@middlewares/validation/schemas'; const ONLY_SR = ' Only users with the Standard Registry role are allowed to make the request.' @@ -189,9 +197,7 @@ export class SingleSchemaApi { @ApiOkResponse({ description: 'Successful operation.', isArray: true, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -201,9 +207,7 @@ export class SingleSchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) async getSchemaParents(@Req() req): Promise { await checkPermission(UserRole.STANDARD_REGISTRY, UserRole.AUDITOR, UserRole.USER)(req.user); @@ -270,21 +274,21 @@ export class SchemaApi { type: String, description: 'Policy id', required: false, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiImplicitQuery({ name: 'moduleId', type: String, description: 'Module id', required: false, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiImplicitQuery({ name: 'toolId', type: String, description: 'Tool id', required: false, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiImplicitQuery({ name: 'topicId', @@ -304,9 +308,7 @@ export class SchemaApi { description: 'Total items in the collection.' } }, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -316,9 +318,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async getSchemasPage(@Req() req, @Response() res): Promise { @@ -384,9 +384,7 @@ export class SchemaApi { description: 'Total items in the collection.' } }, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -396,9 +394,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async getSchemasPageByTopicId(@Req() req, @Response() res): Promise { @@ -434,9 +430,7 @@ export class SchemaApi { }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -446,9 +440,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async getSchemaByType(@Req() req, @Response() res): Promise { @@ -493,9 +485,7 @@ export class SchemaApi { @ApiOkResponse({ description: 'Successful operation.', isArray: true, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -505,9 +495,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async getAll(@Req() req, @Response() res): Promise { @@ -542,12 +530,15 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'Object that contains a valid schema.', + required: true, + type: SchemaDTO + }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -557,9 +548,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.CREATED) async createNewSchema(@Req() req, @Response() res): Promise { @@ -597,11 +586,14 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'Object that contains a valid schema.', + required: true, + type: SchemaDTO + }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(TaskDTO) - }, + type: TaskDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -611,9 +603,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.ACCEPTED) async createNewSchemaAsync(@Req() req, @Response() res): Promise { @@ -648,12 +638,15 @@ export class SchemaApi { summary: 'Updates the schema.', description: 'Updates the schema.' + ONLY_SR, }) + @ApiBody({ + description: 'Object that contains a valid schema.', + required: true, + type: SchemaDTO + }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -663,9 +656,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async setSchema(@Req() req, @Response() res): Promise { @@ -708,14 +699,12 @@ export class SchemaApi { type: String, description: 'Schema ID', required: true, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -725,9 +714,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async deleteSchema(@Req() req, @Response() res): Promise { @@ -776,7 +763,19 @@ export class SchemaApi { type: String, description: 'Schema ID', required: true, - example: '000000000000000000000000' + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: VersionSchemaDTO, + examples: { + Version: { + value: { + version: '1.0.0' + } + } + } }) @ApiOkResponse({ description: 'Successful operation.', @@ -789,9 +788,7 @@ export class SchemaApi { description: 'Total items in the collection.' } }, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -801,9 +798,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async publishSchema(@Req() req, @Response() res): Promise { @@ -812,8 +807,8 @@ export class SchemaApi { const guardians = new Guardians(); const schemaId = req.params.schemaId; const { version } = req.body; - let schema; - let allVersion; + let schema: ISchema; + let allVersion: ISchema[]; try { schema = await guardians.getSchemaById(schemaId); } catch (error) { @@ -867,13 +862,23 @@ export class SchemaApi { type: String, description: 'Schema ID', required: true, - example: '000000000000000000000000' + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: VersionSchemaDTO, + examples: { + Version: { + value: { + version: '1.0.0' + } + } + } }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(TaskDTO) - }, + type: TaskDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -883,9 +888,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.ACCEPTED) async publishSchemaAsync(@Req() req, @Response() res): Promise { @@ -932,6 +935,18 @@ export class SchemaApi { summary: 'Previews the schema from IPFS without loading it into the local DB.', description: 'Previews the schema from IPFS without loading it into the local DB.' + ONLY_SR, }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: MessageSchemaDTO, + examples: { + Message: { + value: { + messageId: '0000000000.000000000' + } + } + } + }) @ApiOkResponse({ description: 'Successful operation.', schema: { @@ -946,9 +961,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async importFromMessagePreview(@Req() req, @Response() res): Promise { @@ -973,11 +986,21 @@ export class SchemaApi { summary: 'Previews the schema from IPFS without loading it into the local DB.', description: 'Previews the schema from IPFS without loading it into the local DB.' + ONLY_SR, }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: MessageSchemaDTO, + examples: { + Message: { + value: { + messageId: '0000000000.000000000' + } + } + } + }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(TaskDTO) - }, + type: TaskDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -987,9 +1010,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.ACCEPTED) async importFromMessagePreviewAsync(@Req() req, @Response() res): Promise { @@ -1021,6 +1042,10 @@ export class SchemaApi { summary: 'Previews the schema from a zip file.', description: 'Previews the schema from a zip file.' + ONLY_SR, }) + @ApiBody({ + description: 'A zip file containing schema to be imported.', + required: true + }) @ApiOkResponse({ description: 'Successful operation.', schema: { @@ -1035,9 +1060,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async importFromFilePreview(@Req() req, @Response() res): Promise { @@ -1073,6 +1096,18 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: MessageSchemaDTO, + examples: { + Message: { + value: { + messageId: '0000000000.000000000' + } + } + } + }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, @@ -1084,9 +1119,7 @@ export class SchemaApi { description: 'Total items in the collection.' } }, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -1096,9 +1129,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.CREATED) async importFromMessage(@Req() req, @Response() res): Promise { @@ -1140,11 +1171,21 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'Object that contains version.', + required: true, + type: MessageSchemaDTO, + examples: { + Message: { + value: { + messageId: '0000000000.000000000' + } + } + } + }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(TaskDTO) - }, + type: TaskDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -1154,9 +1195,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.ACCEPTED) async importFromMessageAsync(@Req() req, @Response() res): Promise { @@ -1196,6 +1235,10 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'A zip file containing schema to be imported.', + required: true + }) @ApiOkResponse({ description: 'Successful operation.', isArray: true, @@ -1207,9 +1250,7 @@ export class SchemaApi { description: 'Total items in the collection.' } }, - schema: { - $ref: getSchemaPath(SchemaDTO) - }, + type: SchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -1219,9 +1260,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.CREATED) async importToTopicFromFile(@Req() req, @Response() res): Promise { @@ -1264,11 +1303,13 @@ export class SchemaApi { required: true, example: '0.0.1' }) + @ApiBody({ + description: 'A zip file containing schema to be imported.', + required: true + }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(TaskDTO) - }, + type: TaskDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -1278,9 +1319,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.ACCEPTED) async importToTopicFromFileAsync(@Req() req, @Response() res): Promise { @@ -1316,13 +1355,11 @@ export class SchemaApi { type: String, description: 'Schema ID', required: true, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiOkResponse({ description: 'Successful operation.', - schema: { - $ref: getSchemaPath(ExportSchemaDTO) - }, + type: ExportSchemaDTO }) @ApiUnauthorizedResponse({ description: 'Unauthorized.', @@ -1332,9 +1369,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async exportMessage(@Req() req, @Response() res): Promise { @@ -1372,7 +1407,7 @@ export class SchemaApi { type: String, description: 'Schema ID', required: true, - example: '000000000000000000000000' + example: '000000000000000000000001' }) @ApiOkResponse({ description: 'Successful operation. Response zip file.' @@ -1385,9 +1420,7 @@ export class SchemaApi { }) @ApiInternalServerErrorResponse({ description: 'Internal server error.', - schema: { - $ref: getSchemaPath(InternalServerErrorDTO) - } + type: InternalServerErrorDTO }) @HttpCode(HttpStatus.OK) async exportToFile(@Req() req, @Response() res): Promise { @@ -1420,7 +1453,36 @@ export class SchemaApi { } } + /** + * Create system schema + */ @Post('/system/:username') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Creates a new system schema.', + description: 'Creates a new system schema.' + ONLY_SR + }) + @ApiImplicitParam({ + name: 'username', + type: String, + description: 'username', + required: true, + example: 'username' + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: SchemaDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.CREATED) async postSystemSchema(@Body() body: SystemSchemaDTO, @Req() req, @Response() res): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -1453,7 +1515,59 @@ export class SchemaApi { } } + /** + * Get system schemas page + */ @Get('/system/:username') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Return a list of all system schemas.', + description: 'Returns all system schemas.' + ONLY_SR + }) + @ApiImplicitParam({ + name: 'username', + type: String, + description: 'username', + required: true, + example: 'username' + }) + @ApiImplicitQuery({ + name: 'pageIndex', + type: Number, + description: 'The number of pages to skip before starting to collect the result set', + required: false, + example: 0 + }) + @ApiImplicitQuery({ + name: 'pageSize', + type: Number, + description: 'The numbers of items to return', + required: false, + example: 20 + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: { + 'x-total-count': { + schema: { + 'type': 'integer' + }, + description: 'Total items in the collection.' + } + }, + type: SchemaDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async getSystemSchema(@Req() req, @Response() res): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -1476,7 +1590,35 @@ export class SchemaApi { } } + /** + * Delete system schema + */ @Delete('/system/:schemaId') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Deletes the system schema with the provided schema ID.', + description: 'Deletes the system schema with the provided schema ID.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'schemaId', + type: String, + description: 'Schema ID', + required: true, + example: '000000000000000000000001' + }) + @ApiOkResponse({ + description: 'Successful operation.', + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.NO_CONTENT) async deleteSystemSchema(@Req() req, @Response() res): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -1503,7 +1645,42 @@ export class SchemaApi { } } + /** + * Update system schema + */ @Put('/system/:schemaId') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Updates the system schema.', + description: 'Updates the system schema.' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'schemaId', + type: String, + description: 'Schema ID', + required: true, + example: '000000000000000000000001' + }) + @ApiBody({ + description: 'Object that contains a valid schema.', + required: true, + type: SchemaDTO + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + type: SchemaDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async setSystemSchema(@Req() req, @Response() res): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -1531,7 +1708,35 @@ export class SchemaApi { } } + /** + * Makes the selected scheme active. + */ @Put('/system/:schemaId/active') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Makes the selected scheme active. Other schemes of the same type become inactive', + description: 'Makes the selected scheme active. Other schemes of the same type become inactive' + ONLY_SR, + }) + @ApiImplicitParam({ + name: 'schemaId', + type: String, + description: 'Schema ID', + required: true, + example: '000000000000000000000001' + }) + @ApiOkResponse({ + description: 'Successful operation.', + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async activeSystemSchema(@Req() req: any): Promise { await checkPermission(UserRole.STANDARD_REGISTRY)(req.user); @@ -1556,7 +1761,36 @@ export class SchemaApi { } } + /** + * Finds the schema by entity. + */ @Get('/system/entity/:schemaEntity') + @ApiSecurity('bearerAuth') + @ApiOperation({ + summary: 'Finds the schema using the schema type.', + description: 'Finds the schema using the schema type.', + }) + @ApiImplicitParam({ + name: 'schemaEntity', + enum: ['STANDARD_REGISTRY', 'USER', 'POLICY', 'MINT_TOKEN', 'WIPE_TOKEN', 'MINT_NFTOKEN'], + description: 'Entity name', + required: true, + example: 'STANDARD_REGISTRY' + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: TaskDTO + }) + @ApiUnauthorizedResponse({ + description: 'Unauthorized.', + }) + @ApiForbiddenResponse({ + description: 'Forbidden.', + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) @HttpCode(HttpStatus.OK) async getSchemaEntity(@Req() req, @Response() res): Promise { await checkPermission(UserRole.STANDARD_REGISTRY, UserRole.USER, UserRole.AUDITOR)(req.user) diff --git a/api-gateway/src/middlewares/validation/fields-validation.ts b/api-gateway/src/middlewares/validation/fields-validation.ts index 4343e548a8..b4f083d3c1 100644 --- a/api-gateway/src/middlewares/validation/fields-validation.ts +++ b/api-gateway/src/middlewares/validation/fields-validation.ts @@ -2,37 +2,37 @@ import * as yup from 'yup'; import { UserRole, SchemaEntity } from '@guardian/interfaces'; const fieldsValidation = { - contractId: yup.string().required('The contractId field is required'), - description: yup.string().required('The description field is required'), - baseTokenId: yup.string().required('The baseTokenId field is required'), - oppositeTokenId: yup.string().nullable().typeError('The oppositeTokenId field must be a string'), - baseTokenCount: yup.string().nullable().typeError('The baseTokenCount field must be a string'), - oppositeTokenCount: yup.string().nullable().typeError('The oppositeTokenId field must be a string'), - baseTokenSerials: yup.array().typeError('The baseTokenSerials field must be an array').of(yup.number()) - .min(1, 'The baseTokenSerials field must contains at least 1 item') - .required('The baseTokenSerials field is required'), - oppositeTokenSerials: yup.array().of(yup.number()) - .nullable().when(['baseTokenId', 'oppositeTokenId'], { - is: false, - then: () => { - return yup.array().of(yup.number()).required('The field oppositeTokenSerials must be provided') - } - }), - requestId: yup.string().required('The requestId field is required'), - name: yup.string().min(1, 'The name field can not be empty').required('The name field is required'), - username: yup.string().min(1, 'The username field can not be empty').required('The username field is required'), - entity: yup.mixed() - .oneOf([SchemaEntity.STANDARD_REGISTRY, SchemaEntity.USER]).required('The entity field is required'), - password: yup.string().min(1, 'The password field can not be empty').required('The password field is required'), - messageId: yup.string().required('message ID in body is required'), - password_confirmation: yup.string().min(1, 'The password_confirmation field can not be empty') - .required('The password_confirmation field is required') - .oneOf([yup.ref('password'), null], 'Passwords must match'), - role: yup.mixed().oneOf([...Object.values(UserRole), 'ROOT_AUTHORITY']) - .required('The role field is required'), - ipfsStorageApiKey: yup.string().required('ipfsStorageApiKey field is required'), - operatorId: yup.string().required('operatorId field is required'), - operatorKey: yup.string().required('operatorKey field is required') + contractId: yup.string().required('The contractId field is required'), + description: yup.string().required('The description field is required'), + baseTokenId: yup.string().required('The baseTokenId field is required'), + oppositeTokenId: yup.string().nullable().typeError('The oppositeTokenId field must be a string'), + baseTokenCount: yup.string().nullable().typeError('The baseTokenCount field must be a string'), + oppositeTokenCount: yup.string().nullable().typeError('The oppositeTokenId field must be a string'), + baseTokenSerials: yup.array().typeError('The baseTokenSerials field must be an array').of(yup.number()) + .min(1, 'The baseTokenSerials field must contains at least 1 item') + .required('The baseTokenSerials field is required'), + oppositeTokenSerials: yup.array().of(yup.number()) + .nullable().when(['baseTokenId', 'oppositeTokenId'], { + is: false, + then: () => { + return yup.array().of(yup.number()).required('The field oppositeTokenSerials must be provided') + } + }), + requestId: yup.string().required('The requestId field is required'), + name: yup.string().min(1, 'The name field can not be empty').required('The name field is required'), + username: yup.string().min(1, 'The username field can not be empty').required('The username field is required'), + entity: yup.mixed() + .oneOf([SchemaEntity.STANDARD_REGISTRY, SchemaEntity.USER]).required('The entity field is required'), + password: yup.string().min(1, 'The password field can not be empty').required('The password field is required'), + messageId: yup.string().required('message ID in body is required'), + password_confirmation: yup.string().min(1, 'The password_confirmation field can not be empty') + .required('The password_confirmation field is required') + .oneOf([yup.ref('password'), null], 'Passwords must match'), + role: yup.mixed().oneOf([...Object.values(UserRole), 'ROOT_AUTHORITY']) + .required('The role field is required'), + ipfsStorageApiKey: yup.string().required('ipfsStorageApiKey field is required'), + operatorId: yup.string().required('operatorId field is required'), + operatorKey: yup.string().required('operatorKey field is required') }; export default fieldsValidation; diff --git a/api-gateway/src/middlewares/validation/index.ts b/api-gateway/src/middlewares/validation/index.ts index a7a15d8c59..d196aa60b4 100644 --- a/api-gateway/src/middlewares/validation/index.ts +++ b/api-gateway/src/middlewares/validation/index.ts @@ -9,7 +9,7 @@ type ValidationError = string & { errors: string[] } * @param err */ const getValidationErrors = (err: ValidationError): string[] => { - return err?.errors || [err] + return err?.errors || [err] } /** @@ -18,7 +18,7 @@ const getValidationErrors = (err: ValidationError): string[] => { * @param type */ export const prepareValidationResponse = (err, type: string = 'ValidationError') => { - return { type, message: getValidationErrors(err) }; + return { type, message: getValidationErrors(err) }; } /** @@ -26,16 +26,16 @@ export const prepareValidationResponse = (err, type: string = 'ValidationError') * @param schema */ const validate = (schema) => async (req, res, next) => { - try { - await schema.validate({ - body: req.body, - query: req.query, - params: req.params, - }, { abortEarly: false }); - return next(); - } catch (err) { - return res.status(422).json(prepareValidationResponse(err, err.name)); - } + try { + await schema.validate({ + body: req.body, + query: req.query, + params: req.params, + }, { abortEarly: false }); + return next(); + } catch (err) { + return res.status(422).json(prepareValidationResponse(err, err.name)); + } }; -export default validate +export default validate \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/analytics.ts b/api-gateway/src/middlewares/validation/schemas/analytics.ts new file mode 100644 index 0000000000..00660a4ec1 --- /dev/null +++ b/api-gateway/src/middlewares/validation/schemas/analytics.ts @@ -0,0 +1,205 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsObject, IsString, IsNumber } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class SearchPoliciesDTO { + @ApiProperty() + @IsObject() + target: any; + + @ApiProperty() + @IsObject() + result: any; +} + +export class ComparePoliciesDTO { + @ApiProperty() + @IsObject() + blocks: any; + + @ApiProperty() + @IsObject() + groups: any; + + @ApiProperty() + @IsObject() + left: any; + + @ApiProperty() + @IsObject() + right: any; + + @ApiProperty() + @IsObject() + roles: any; + + @ApiProperty() + @IsObject() + tokens: any; + + @ApiProperty() + @IsObject() + topics: any; + + @ApiProperty() + @IsObject() + total: any; +} + +export class CompareModulesDTO { + @ApiProperty() + @IsObject() + blocks: any; + + @ApiProperty() + @IsObject() + left: any; + + @ApiProperty() + @IsObject() + right: any; + + @ApiProperty() + @IsObject() + inputEvents: any; + + @ApiProperty() + @IsObject() + outputEvents: any; + + @ApiProperty() + @IsObject() + variables: any; + + @ApiProperty() + @IsObject() + total: any; +} + +export class CompareSchemasDTO { + @ApiProperty() + @IsObject() + fields: any; + + @ApiProperty() + @IsObject() + left: any; + + @ApiProperty() + @IsObject() + right: any; + + @ApiProperty() + @IsObject() + total: any; +} + +export class CompareDocumentsDTO { + @ApiProperty() + @IsObject() + documents: any; + + @ApiProperty() + @IsObject() + left: any; + + @ApiProperty() + @IsObject() + right: any; + + @ApiProperty() + @IsObject() + total: any; +} + +export class FilterSearchPoliciesDTO { + @ApiProperty() + @IsString() + policyId: string; +} + +export class FilterPoliciesDTO { + @ApiProperty() + @IsString() + policyId1: string; + + @ApiProperty() + @IsString() + policyId2: string; + + @ApiProperty({ type: () => String }) + @IsArray() + @Type(() => String) + policyIds: string[]; + + @ApiProperty() + @IsNumber() + eventsLvl: number; + + @ApiProperty() + @IsNumber() + propLvl: number; + + @ApiProperty() + @IsNumber() + childrenLvl: number; + + @ApiProperty() + @IsNumber() + idLvl: number; +} + +export class FilterModulesDTO { + @ApiProperty() + @IsString() + moduleId1: string; + + @ApiProperty() + @IsString() + moduleId2: string; + + @ApiProperty() + @IsNumber() + eventsLvl: number; + + @ApiProperty() + @IsNumber() + propLvl: number; + + @ApiProperty() + @IsNumber() + childrenLvl: number; + + @ApiProperty() + @IsNumber() + idLvl: number; +} + +export class FilterSchemasDTO { + @ApiProperty() + @IsString() + schemaId1: string; + + @ApiProperty() + @IsString() + schemaId2: string; + + @ApiProperty() + @IsNumber() + idLvl: number; +} + +export class FilterDocumentsDTO { + @ApiProperty() + @IsString() + documentId1: string; + + @ApiProperty() + @IsString() + documentId2: string; + + @ApiProperty({ type: () => String }) + @IsArray() + @Type(() => String) + documentIds: string[]; +} \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/index.ts b/api-gateway/src/middlewares/validation/schemas/index.ts index e2b86f0322..277a67f973 100644 --- a/api-gateway/src/middlewares/validation/schemas/index.ts +++ b/api-gateway/src/middlewares/validation/schemas/index.ts @@ -7,4 +7,5 @@ export * from './schemas' export * from './settings' export * from './suggestions' export * from './task' -export * from './tool' \ No newline at end of file +export * from './tool' +export * from './analytics' \ No newline at end of file diff --git a/api-gateway/src/middlewares/validation/schemas/schemas.ts b/api-gateway/src/middlewares/validation/schemas/schemas.ts index b2b5cb5684..352078e9fb 100644 --- a/api-gateway/src/middlewares/validation/schemas/schemas.ts +++ b/api-gateway/src/middlewares/validation/schemas/schemas.ts @@ -121,4 +121,18 @@ export class ExportSchemaDTO { @ApiProperty() @IsString() messageId: string; +} + +export class VersionSchemaDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + version: string; +} + +export class MessageSchemaDTO { + @ApiProperty() + @IsString() + @IsNotEmpty() + messageId: string; } \ No newline at end of file diff --git a/api-tests/package.json b/api-tests/package.json index d358d638dc..85351337e3 100644 --- a/api-tests/package.json +++ b/api-tests/package.json @@ -1,6 +1,6 @@ { "name": "api-tests", - "version": "2.17.0-prerelease", + "version": "2.17.0", "description": "API Tests", "main": "index.js", "scripts": { @@ -25,6 +25,5 @@ "gulp-rename": "^2.0.0", "gulp-sourcemaps": "^3.0.0", "gulp-typescript": "^6.0.0-alpha.1" - }, - "stableVersion": "2.16.0" + } } diff --git a/auth-service/package.json b/auth-service/package.json index 195ec55489..4e0bedf8b3 100644 --- a/auth-service/package.json +++ b/auth-service/package.json @@ -6,8 +6,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@meeco/cryppo": "^2.0.2", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", @@ -69,6 +69,5 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/ui-service.xml" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/common/package.json b/common/package.json index 8f7e51b2dc..28b74b9aac 100644 --- a/common/package.json +++ b/common/package.json @@ -5,7 +5,7 @@ "@azure/identity": "^3.2.2", "@azure/keyvault-secrets": "^4.7.0", "@google-cloud/secret-manager": "^4.2.2", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/interfaces": "^2.17.0", "@hashgraph/sdk": "2.24.2", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "^2.0.2", @@ -77,6 +77,5 @@ "test:local": "mocha tests/**/*.test.js --exit", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/common/src/entity/tool.ts b/common/src/entity/tool.ts index 6ffe025fe2..22920ae00b 100644 --- a/common/src/entity/tool.ts +++ b/common/src/entity/tool.ts @@ -81,6 +81,12 @@ export class PolicyTool extends BaseEntity { @Property({ nullable: true }) codeVersion?: string; + /** + * Tool topic id + */ + @Property({ nullable: true }) + tagsTopicId?: string; + /** * Set policy defaults */ diff --git a/common/src/hedera-modules/message/message-body.interface.ts b/common/src/hedera-modules/message/message-body.interface.ts index c388062019..925c8d0c2a 100644 --- a/common/src/hedera-modules/message/message-body.interface.ts +++ b/common/src/hedera-modules/message/message-body.interface.ts @@ -497,4 +497,8 @@ export interface ToolMessageBody extends MessageBody { * URI */ uri: string; + /** + * Tags topic ID + */ + tagsTopicId: string; } \ No newline at end of file diff --git a/common/src/hedera-modules/message/tool-message.ts b/common/src/hedera-modules/message/tool-message.ts index b6dfe4792e..e28537172e 100644 --- a/common/src/hedera-modules/message/tool-message.ts +++ b/common/src/hedera-modules/message/tool-message.ts @@ -39,6 +39,10 @@ export class ToolMessage extends Message { * @private */ public toolTopicId: string; + /** + * Tags topic ID + */ + public tagsTopicId: string; constructor(type: MessageType.Tool, action: MessageAction) { super(action, type); @@ -59,6 +63,7 @@ export class ToolMessage extends Message { this.hash = model.hash; this.document = zip; this.toolTopicId = model.topicId; + this.tagsTopicId = model.tagsTopicId; } /** @@ -84,6 +89,7 @@ export class ToolMessage extends Message { owner: this.owner, hash: this.hash, topicId: this.toolTopicId?.toString(), + tagsTopicId: this.tagsTopicId, cid: this.getDocumentUrl(UrlType.cid), uri: this.getDocumentUrl(UrlType.url) } @@ -146,6 +152,7 @@ export class ToolMessage extends Message { message.owner = json.owner; message.hash = json.hash; message.toolTopicId = json.topicId; + message.tagsTopicId = json.tagsTopicId; if (json.cid) { const urls = [{ @@ -194,6 +201,7 @@ export class ToolMessage extends Message { result.hash = this.hash; result.document = this.document; result.toolTopicId = this.toolTopicId; + result.tagsTopicId = this.tagsTopicId; return result; } diff --git a/frontend/package.json b/frontend/package.json index 95f559625d..3f4722bb7d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -68,6 +68,5 @@ "test": "ng test", "watch": "ng build --watch --configuration development --output-path ../www-data" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/frontend/src/app/components/notification/notification.component.scss b/frontend/src/app/components/notification/notification.component.scss index b0f3111e5f..e12a4b5ae7 100644 --- a/frontend/src/app/components/notification/notification.component.scss +++ b/frontend/src/app/components/notification/notification.component.scss @@ -24,7 +24,7 @@ .notification-menu-header { display: flex; justify-content: space-between; - align-items: end; + align-items: flex-end; padding: 0 5px; &__title { @@ -134,7 +134,7 @@ .progress-bar { display: flex; gap: 10px; - align-items: end; + align-items: flex-end; grid-area: progress; margin-bottom: 3px; diff --git a/frontend/src/app/modules/common/async-progress/async-progress.component.css b/frontend/src/app/modules/common/async-progress/async-progress.component.css index e565425607..7a70f10265 100644 --- a/frontend/src/app/modules/common/async-progress/async-progress.component.css +++ b/frontend/src/app/modules/common/async-progress/async-progress.component.css @@ -27,6 +27,13 @@ height: 80%; } +.loading-progress-info { + height: 100%; + overflow-y: auto; + padding-right: 10px; + padding-top: 10px; +} + .task-not-found { display: flex; gap: 10px; @@ -41,4 +48,4 @@ .task-not-found__label { font-size: 60px; -} \ No newline at end of file +} diff --git a/frontend/src/app/modules/common/async-progress/async-progress.component.html b/frontend/src/app/modules/common/async-progress/async-progress.component.html index 3ebcf0ee58..87c1bfd3da 100644 --- a/frontend/src/app/modules/common/async-progress/async-progress.component.html +++ b/frontend/src/app/modules/common/async-progress/async-progress.component.html @@ -3,7 +3,7 @@
-
+
INFO: {{ @@ -18,4 +18,4 @@
searchTask not found
-
\ No newline at end of file +
diff --git a/frontend/src/app/modules/common/async-progress/async-progress.component.ts b/frontend/src/app/modules/common/async-progress/async-progress.component.ts index 79d5ba8b21..142a4bd9a9 100644 --- a/frontend/src/app/modules/common/async-progress/async-progress.component.ts +++ b/frontend/src/app/modules/common/async-progress/async-progress.component.ts @@ -144,7 +144,7 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { queryParams[key] = value; } } - this.router.navigate(path, {queryParams}); + this.router.navigate(path, { queryParams }); } handleResult(result: any) { @@ -178,6 +178,14 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { replaceUrl: true, }); break; + case TaskAction.CREATE_TOOL: + this.router.navigate(['policy-configuration'], { + queryParams: { + toolId: result, + }, + replaceUrl: true, + }); + break; case TaskAction.IMPORT_POLICY_FILE: case TaskAction.IMPORT_POLICY_MESSAGE: this.router.navigate(['policy-configuration'], { @@ -236,8 +244,43 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { }); } break; + case TaskAction.PUBLISH_TOOL: + if (result) { + const { isValid, errors, tool } = result; + if (!isValid) { + let text = []; + const blocks = errors.blocks; + const invalidBlocks = blocks.filter( + (block: any) => !block.isValid + ); + for (let i = 0; i < invalidBlocks.length; i++) { + const block = invalidBlocks[i]; + for (let j = 0; j < block.errors.length; j++) { + const error = block.errors[j]; + if (block.id) { + text.push( + `
${block.id}: ${error}
` + ); + } else { + text.push(`
${error}
`); + } + } + } + this.informService.errorMessage( + text.join(''), + 'The tool is invalid' + ); + } + this.router.navigate(['policy-configuration'], { + queryParams: { + toolId: tool?.id + }, + replaceUrl: true, + }); + } + break; case TaskAction.DELETE_POLICY: - this.router.navigate(['policy-viewer'], { + this.router.navigate(['policy-viewer'], { replaceUrl: true, }); break; @@ -252,7 +295,7 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { this.redirect(this.last); return; } - this.router.navigate(['schemas'], { + this.router.navigate(['schemas'], { replaceUrl: true, }); break; @@ -275,14 +318,14 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { case TaskAction.CONNECT_USER: this.router.navigate([ this.userRole === UserRole.USER ? 'user-profile' : 'config', - ], { + ], { replaceUrl: true, }); break; case TaskAction.DELETE_TOKEN: case TaskAction.UPDATE_TOKEN: case TaskAction.CREATE_TOKEN: - this.router.navigate(['tokens'], { + this.router.navigate(['tokens'], { replaceUrl: true, }); break; @@ -293,7 +336,7 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { case TaskAction.WIZARD_CREATE_POLICY: case TaskAction.PUBLISH_POLICY: case TaskAction.DELETE_POLICY: - this.router.navigate(['policy-viewer'], { + this.router.navigate(['policy-viewer'], { replaceUrl: true, }); break; @@ -301,7 +344,7 @@ export class AsyncProgressComponent implements OnInit, OnDestroy { case TaskAction.PUBLISH_SCHEMA: case TaskAction.IMPORT_SCHEMA_FILE: case TaskAction.IMPORT_SCHEMA_MESSAGE: - this.router.navigate(['schemas'], { + this.router.navigate(['schemas'], { replaceUrl: true, }); break; diff --git a/frontend/src/app/modules/policy-engine/helpers/compare-modules-dialog/compare-modules-dialog.component.ts b/frontend/src/app/modules/policy-engine/helpers/compare-modules-dialog/compare-modules-dialog.component.ts index d88be30384..e412003c45 100644 --- a/frontend/src/app/modules/policy-engine/helpers/compare-modules-dialog/compare-modules-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/helpers/compare-modules-dialog/compare-modules-dialog.component.ts @@ -31,6 +31,9 @@ export class CompareModulesDialogComponent { ngOnInit() { this.loading = false; + setTimeout(() => { + this.onChange(); + }); } setData(data: any) { diff --git a/frontend/src/app/modules/policy-engine/helpers/compare-policy-dialog/compare-policy-dialog.component.ts b/frontend/src/app/modules/policy-engine/helpers/compare-policy-dialog/compare-policy-dialog.component.ts index a10eceb13c..18c6d9cf8f 100644 --- a/frontend/src/app/modules/policy-engine/helpers/compare-policy-dialog/compare-policy-dialog.component.ts +++ b/frontend/src/app/modules/policy-engine/helpers/compare-policy-dialog/compare-policy-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; @Component({ @@ -20,6 +20,7 @@ export class ComparePolicyDialog { constructor( public dialogRef: MatDialogRef, + private changeDetector: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: any) { this.policy = data.policy; this.policies = data.policies || []; @@ -34,6 +35,9 @@ export class ComparePolicyDialog { ngOnInit() { this.loading = false; + setTimeout(() => { + this.onChange(); + }); } setData(data: any) { @@ -62,5 +66,6 @@ export class ComparePolicyDialog { } else { this.list1 = this.policies; } + this.changeDetector.detectChanges(); } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts index c5fdb0abbb..bacd8204ff 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-configuration/policy-configuration/policy-configuration.component.ts @@ -1,8 +1,6 @@ import { CdkDropList } from '@angular/cdk/drag-drop'; import { ChangeDetectorRef, Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { MatIconRegistry } from '@angular/material/icon'; -import { DomSanitizer } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Schema, SchemaHelper, Token } from '@guardian/interfaces'; import * as yaml from 'js-yaml'; @@ -18,7 +16,17 @@ import { NewModuleDialog } from '../../helpers/new-module-dialog/new-module-dial import { SaveBeforeDialogComponent } from '../../helpers/save-before-dialog/save-before-dialog.component'; import { PolicyAction, SavePolicyDialog } from '../../helpers/save-policy-dialog/save-policy-dialog.component'; import { RegisteredService } from '../../services/registered.service'; -import { Options, PolicyBlock, PolicyTemplate, PolicyModule, PolicyStorage, ModuleTemplate, Theme, ThemeRule, ToolTemplate } from '../../structures'; +import { + Options, + PolicyBlock, + PolicyTemplate, + PolicyModule, + PolicyStorage, + ModuleTemplate, + Theme, + ThemeRule, + ToolTemplate +} from '../../structures'; import { PolicyTreeComponent } from '../policy-tree/policy-tree.component'; import { ThemeService } from '../../../../services/theme.service'; import { WizardMode, WizardService } from 'src/app/modules/policy-engine/services/wizard.service'; @@ -179,8 +187,6 @@ export class PolicyConfigurationComponent implements OnInit { private router: Router, private dialog: MatDialog, private changeDetector: ChangeDetectorRef, - private domSanitizer: DomSanitizer, - private matIconRegistry: MatIconRegistry, private informService: InformService, private registeredService: RegisteredService, private themeService: ThemeService, diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/policy-settings/policy-settings.component.scss b/frontend/src/app/modules/policy-engine/policy-configuration/policy-settings/policy-settings.component.scss index 8f1409393d..61745abf15 100644 --- a/frontend/src/app/modules/policy-engine/policy-configuration/policy-settings/policy-settings.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-configuration/policy-settings/policy-settings.component.scss @@ -492,7 +492,7 @@ border-bottom: 1px solid #000; height: 51px; overflow: hidden; - align-items: end; + align-items: flex-end; display: flex; padding-bottom: 20px; } diff --git a/frontend/src/app/modules/policy-engine/structures/policy-models/module/block.model.ts b/frontend/src/app/modules/policy-engine/structures/policy-models/module/block.model.ts index 624eecc5b6..0637ba4947 100644 --- a/frontend/src/app/modules/policy-engine/structures/policy-models/module/block.model.ts +++ b/frontend/src/app/modules/policy-engine/structures/policy-models/module/block.model.ts @@ -619,7 +619,10 @@ export class PolicyModule extends PolicyBlock { blockType: BlockType.Tool, defaultActive: true, hash: template.hash, - messageId: template.messageId + messageId: template.messageId, + inputEvents: template.config?.inputEvents, + outputEvents: template.config?.outputEvents, + variables: template.config?.variables } const tool = TemplateUtils.buildBlock(config, null, this) as PolicyTool; this._tagMap[tool.tag] = tool; @@ -651,7 +654,7 @@ export class PolicyModule extends PolicyBlock { } public setEnvironments(env: any): void { - if(env) { + if (env) { this._schemas = env.schemas; this._tools = env.tools; this._tokens = env.tokens; diff --git a/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts b/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts index 01186a4ff0..18f9291839 100644 --- a/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts +++ b/frontend/src/app/modules/policy-engine/structures/policy-models/policy/policy.model.ts @@ -524,7 +524,10 @@ export class PolicyTemplate { blockType: BlockType.Tool, defaultActive: true, hash: template.hash, - messageId: template.messageId + messageId: template.messageId, + inputEvents: template.config?.inputEvents, + outputEvents: template.config?.outputEvents, + variables: template.config?.variables } const tool = TemplateUtils.buildBlock(config, null, this) as PolicyTool; this._tagMap[tool.tag] = tool; diff --git a/frontend/src/app/modules/policy-engine/structures/policy-models/tool/block.model.ts b/frontend/src/app/modules/policy-engine/structures/policy-models/tool/block.model.ts index b755e2e35b..c9a8ebf25d 100644 --- a/frontend/src/app/modules/policy-engine/structures/policy-models/tool/block.model.ts +++ b/frontend/src/app/modules/policy-engine/structures/policy-models/tool/block.model.ts @@ -631,7 +631,10 @@ export class PolicyTool extends PolicyBlock { blockType: BlockType.Tool, defaultActive: true, hash: template.hash, - messageId: template.messageId + messageId: template.messageId, + inputEvents: template.config?.inputEvents, + outputEvents: template.config?.outputEvents, + variables: template.config?.variables } const tool = TemplateUtils.buildBlock(config, null, this) as PolicyTool; this._tagMap[tool.tag] = tool; diff --git a/frontend/src/app/modules/policy-engine/tools-list/tools-list.component.ts b/frontend/src/app/modules/policy-engine/tools-list/tools-list.component.ts index a71e720454..fa7acef0b5 100644 --- a/frontend/src/app/modules/policy-engine/tools-list/tools-list.component.ts +++ b/frontend/src/app/modules/policy-engine/tools-list/tools-list.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; -import { IUser, SchemaHelper, TagType } from '@guardian/interfaces'; +import { GenerateUUIDv4, IUser, SchemaHelper, TagType } from '@guardian/interfaces'; import { forkJoin } from 'rxjs'; import { ConfirmationDialogComponent } from 'src/app/modules/common/confirmation-dialog/confirmation-dialog.component'; import { InformService } from 'src/app/services/inform.service'; @@ -43,7 +43,7 @@ export class ToolsListComponent implements OnInit, OnDestroy { 'name', 'description', 'topic', - // 'tags', + 'tags', 'schemas', 'status', 'operation', @@ -249,12 +249,14 @@ export class ToolsListComponent implements OnInit, OnDestroy { description: result.description, menu: "show", config: { + id: GenerateUUIDv4(), blockType: 'tool' } } this.loading = true; - this.toolsService.create(tool).subscribe((result) => { - this.loadAllTools(); + this.toolsService.pushCreate(tool).subscribe((result) => { + const { taskId, expectation } = result; + this.router.navigate(['/task', taskId]); }, (e) => { this.loading = false; }); @@ -286,32 +288,13 @@ export class ToolsListComponent implements OnInit, OnDestroy { public publishTool(element: any) { this.loading = true; - this.toolsService.publish(element.id).subscribe((result) => { - const { isValid, errors } = result; - if (!isValid) { - let text = []; - const blocks = errors.blocks; - const invalidBlocks = blocks.filter( - (block: any) => !block.isValid - ); - for (let i = 0; i < invalidBlocks.length; i++) { - const block = invalidBlocks[i]; - for ( - let j = 0; - j < block.errors.length; - j++ - ) { - const error = block.errors[j]; - if (block.id) { - text.push(`
${block.id}: ${error}
`); - } else { - text.push(`
${error}
`); - } - } + this.toolsService.pushPublish(element.id).subscribe((result) => { + const { taskId, expectation } = result; + this.router.navigate(['task', taskId], { + queryParams: { + last: btoa(location.href) } - this.informService.errorMessage(text.join(''), 'The tool is invalid'); - } - this.loadAllTools(); + }); }, (e) => { this.loading = false; }); diff --git a/frontend/src/app/services/tools.service.ts b/frontend/src/app/services/tools.service.ts index 9914a1df65..eb2b75a3a7 100644 --- a/frontend/src/app/services/tools.service.ts +++ b/frontend/src/app/services/tools.service.ts @@ -29,6 +29,10 @@ export class ToolsService { return this.http.post(`${this.url}/`, tool); } + public pushCreate(tool: any): Observable<{ taskId: string, expectation: number }> { + return this.http.post(`${this.url}/push`, tool); + } + public menuList(): Observable { return this.http.get(`${this.url}/menu/all`); } diff --git a/frontend/src/app/views/admin/about-view/about-view.component.css b/frontend/src/app/views/admin/about-view/about-view.component.css index 7577f1a034..bea18547ae 100644 --- a/frontend/src/app/views/admin/about-view/about-view.component.css +++ b/frontend/src/app/views/admin/about-view/about-view.component.css @@ -32,7 +32,7 @@ .title-column { max-width: 300px; font-weight: 500; - align-items: end; + align-items: flex-end; } .value-column { diff --git a/frontend/src/app/views/schemas/schemas.component.html b/frontend/src/app/views/schemas/schemas.component.html index decd21fe6e..c6091c0921 100644 --- a/frontend/src/app/views/schemas/schemas.component.html +++ b/frontend/src/app/views/schemas/schemas.component.html @@ -52,7 +52,6 @@ Tools All - No binding {{tool.name}} ({{tool.topicId}}) diff --git a/guardian-service/package.json b/guardian-service/package.json index 6e06a965bb..2875a38139 100644 --- a/guardian-service/package.json +++ b/guardian-service/package.json @@ -12,8 +12,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@hashgraph/sdk": "2.24.2", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "^2.0.2", @@ -92,6 +92,5 @@ "test:local": "mocha tests/**/*.test.js --exit", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/guardian-service/src/api/helpers/tool-import-export-helper.ts b/guardian-service/src/api/helpers/tool-import-export-helper.ts index 22e84d854b..1813307ee8 100644 --- a/guardian-service/src/api/helpers/tool-import-export-helper.ts +++ b/guardian-service/src/api/helpers/tool-import-export-helper.ts @@ -129,9 +129,9 @@ export async function importToolByMessage( } const toolTags = components.tags?.filter((t: any) => t.entity === TagType.Tool) || []; - if (message.toolTopicId) { + if (message.tagsTopicId) { const tagMessages = await messageServer.getMessages( - message.toolTopicId, + message.tagsTopicId, MessageType.Tag, MessageAction.PublishTag ); diff --git a/guardian-service/src/api/tag.service.ts b/guardian-service/src/api/tag.service.ts index 9112d97204..df4d4442db 100644 --- a/guardian-service/src/api/tag.service.ts +++ b/guardian-service/src/api/tag.service.ts @@ -9,16 +9,26 @@ import { MessageType, PolicyModule as ModuleCollection, Policy as PolicyCollection, + PolicyTool as PolicyToolCollection, Schema as SchemaCollection, + Token as TokenCollection, Tag, TagMessage, - Token as TokenCollection, TopicConfig, UrlType, Users, VcHelper, } from '@guardian/common'; -import { GenerateUUIDv4, IRootConfig, MessageAPI, Schema, SchemaCategory, SchemaHelper, SchemaStatus, TagType } from '@guardian/interfaces'; +import { + GenerateUUIDv4, + IRootConfig, + MessageAPI, + Schema, + SchemaCategory, + SchemaHelper, + SchemaStatus, + TagType +} from '@guardian/interfaces'; /** * Publish schema tags @@ -104,6 +114,32 @@ export async function publishTokenTags( } } +/** + * Publish tool tags + * @param tool + * @param messageServer + */ +export async function publishToolTags( + tool: PolicyToolCollection, + user: IRootConfig +): Promise { + const filter: any = { + localTarget: tool.id, + entity: TagType.Tool, + status: 'Draft' + } + const tags = await DatabaseServer.getTags(filter); + const topic = await DatabaseServer.getTopicById(tool.tagsTopicId); + const topicConfig = await TopicConfig.fromObject(topic, true); + const messageServer = new MessageServer(user.hederaAccountId, user.hederaAccountKey) + .setTopicObject(topicConfig); + for (const tag of tags) { + tag.target = tool.tagsTopicId; + await publishTag(tag, messageServer); + await DatabaseServer.updateTag(tag); + } +} + /** * Publish module tags * @param module @@ -281,6 +317,18 @@ export async function getTarget(entity: TagType, id: string): Promise<{ return null; } } + case TagType.Tool: { + const item = await DatabaseServer.getToolById(id); + if (item) { + return { + id: item.id.toString(), + target: item.messageId, + topicId: item.tagsTopicId + }; + } else { + return null; + } + } default: return null; } diff --git a/guardian-service/src/api/tool.service.ts b/guardian-service/src/api/tool.service.ts index 59c6b131e6..cfc42960b9 100644 --- a/guardian-service/src/api/tool.service.ts +++ b/guardian-service/src/api/tool.service.ts @@ -35,6 +35,7 @@ import { ToolValidator } from '@policy-engine/block-validators/tool-validator'; import { PolicyConverterUtils } from '@policy-engine/policy-converter-utils'; import { importToolByFile, importToolByMessage } from './helpers'; import * as crypto from 'crypto'; +import { publishToolTags } from './tag.service'; /** * Sha256 @@ -181,6 +182,20 @@ export async function publishTool( notifier.completedAndStart('Publish schemas'); tool = await publishSchemas(tool, owner, root, notifier); + notifier.completedAndStart('Create tags topic'); + const topicHelper = new TopicHelper(root.hederaAccountId, root.hederaAccountKey); + const tagsTopic = await topicHelper.create({ + type: TopicType.TagsTopic, + name: tool.name || TopicType.TagsTopic, + description: tool.description || TopicType.TagsTopic, + owner, + policyId: tool.id.toString(), + policyUUID: tool.uuid + }, { admin: true, submit: false }); + await tagsTopic.saveKeys(); + await DatabaseServer.saveTopic(tagsTopic.toObject()); + tool.tagsTopicId = tagsTopic.topicId; + notifier.completedAndStart('Generate file'); tool = updateToolConfig(tool); const zip = await ToolImportExport.generate(tool); @@ -199,6 +214,13 @@ export async function publishTool( const result = await messageServer .sendMessage(message); + notifier.completedAndStart('Publish tags'); + try { + await publishToolTags(tool, root); + } catch (error) { + logger.error(error, ['GUARDIAN_SERVICE, TAGS']); + } + notifier.completedAndStart('Saving in DB'); tool.messageId = result.getId(); tool.status = ModuleStatus.PUBLISHED; @@ -312,7 +334,7 @@ export async function createTool( owner, targetId: tool.id.toString(), targetUUID: tool.uuid - }); + }, { admin: true, submit: true }); await topic.saveKeys(); notifier.completedAndStart('Create tool in Hedera'); @@ -462,7 +484,9 @@ export async function toolsAPI(): Promise { 'topicId', 'hash', 'messageId', - 'owner' + 'owner', + 'config', + 'configFileId' ] }); const ids = tools.map(t => t.topicId); @@ -480,6 +504,13 @@ export async function toolsAPI(): Promise { ); const map = new Map(); for (const tool of tools) { + if (tool.config) { + tool.config = { + inputEvents: tool.config.inputEvents, + outputEvents: tool.config.outputEvents, + variables: tool.config.variables + } + } tool.schemas = []; map.set(tool.topicId, tool); } diff --git a/interfaces/package.json b/interfaces/package.json index 9a359c13f1..1bc203151b 100644 --- a/interfaces/package.json +++ b/interfaces/package.json @@ -32,6 +32,5 @@ "prepack": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/interfaces/src/type/topic.type.ts b/interfaces/src/type/topic.type.ts index de1b7319a4..26ff76d430 100644 --- a/interfaces/src/type/topic.type.ts +++ b/interfaces/src/type/topic.type.ts @@ -13,4 +13,5 @@ export enum TopicType { ModuleTopic = 'MODULE_TOPIC', ContractTopic = 'CONTRACT_TOPIC', ToolTopic = 'TOOL_TOPIC', + TagsTopic = 'TAGS_TOPIC' } diff --git a/logger-service/package.json b/logger-service/package.json index b34c2d4beb..375c08a244 100644 --- a/logger-service/package.json +++ b/logger-service/package.json @@ -1,8 +1,8 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", "@nestjs/common": "^9.4.1", @@ -48,6 +48,5 @@ "start": "node dist/index.js", "watch": "nodemon src/index.ts" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/mrv-sender/package.json b/mrv-sender/package.json index 3964f3f60c..3d148e8690 100644 --- a/mrv-sender/package.json +++ b/mrv-sender/package.json @@ -1,7 +1,7 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", "@transmute/credentials-context": "0.7.0-unstable.80", "@transmute/did-context": "0.7.0-unstable.80", "@transmute/ed25519-signature-2018": "0.7.0-unstable.80", @@ -39,6 +39,5 @@ "dev:docker": "nodemon .", "start": "node dist/index.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/notification-service/package.json b/notification-service/package.json index 8bfa713c4b..554b837ec9 100644 --- a/notification-service/package.json +++ b/notification-service/package.json @@ -1,8 +1,8 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@mikro-orm/core": "5.7.12", "@mikro-orm/mongodb": "5.7.12", "@nestjs/common": "^9.4.1", @@ -47,6 +47,5 @@ "start": "node dist/index.js", "watch": "nodemon src/index.ts" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/package.json b/package.json index 62d57a7970..3561a84fb8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,5 @@ "api-tests", "notification-service" ], - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/policy-service/package.json b/policy-service/package.json index d910f5381e..27d8f31554 100644 --- a/policy-service/package.json +++ b/policy-service/package.json @@ -11,8 +11,8 @@ }, "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "2.17.0-prerelease", - "@guardian/interfaces": "2.17.0-prerelease", + "@guardian/common": "2.17.0", + "@guardian/interfaces": "2.17.0", "@hashgraph/sdk": "2.24.2", "@mattrglobal/jsonld-signatures-bbs": "^1.1.2", "@meeco/cryppo": "2.0.2", @@ -91,6 +91,5 @@ "test:local": "mocha tests/**/*.test.js", "test:stability": "mocha tests/stability.test.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/swagger-analytics.yaml b/swagger-analytics.yaml index 7c57036e59..ec258958eb 100644 --- a/swagger-analytics.yaml +++ b/swagger-analytics.yaml @@ -208,7 +208,7 @@ info: the heart of the Guardian solution is a sophisticated Policy Workflow Engine (PWE) that enables applications to offer a requirements-based tokenization implementation. - version: 2.16.0 + version: 2.17.0-prerelease contact: name: API developer url: https://envisionblockchain.com diff --git a/swagger.yaml b/swagger.yaml index e34cf97de5..fb4931c9f1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -7293,7 +7293,7 @@ info: the heart of the Guardian solution is a sophisticated Policy Workflow Engine (PWE) that enables applications to offer a requirements-based tokenization implementation. - version: 2.16.0 + version: 2.17.0-prerelease contact: name: API developer url: https://envisionblockchain.com diff --git a/topic-viewer/package.json b/topic-viewer/package.json index 9f4b5ed974..9fb16e8cfd 100644 --- a/topic-viewer/package.json +++ b/topic-viewer/package.json @@ -29,6 +29,5 @@ "dev": "tsc -w", "start": "node dist/index.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/tree-viewer/package.json b/tree-viewer/package.json index 7ddd604cc8..fda5187bb9 100644 --- a/tree-viewer/package.json +++ b/tree-viewer/package.json @@ -27,6 +27,5 @@ "dev": "tsc -w", "start": "node dist/index.js" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/worker-service/package.json b/worker-service/package.json index 338344a416..dd14c08716 100644 --- a/worker-service/package.json +++ b/worker-service/package.json @@ -1,8 +1,8 @@ { "author": "Envision Blockchain Solutions ", "dependencies": { - "@guardian/common": "^2.17.0-prerelease", - "@guardian/interfaces": "^2.17.0-prerelease", + "@guardian/common": "^2.17.0", + "@guardian/interfaces": "^2.17.0", "@hashgraph/sdk": "2.24.2", "@nestjs/common": "^9.4.1", "@nestjs/core": "^9.4.1", @@ -59,6 +59,5 @@ "start": "node dist/index.js", "test": "mocha tests/**/*.test.js --reporter mocha-junit-reporter --reporter-options mochaFile=../test_results/worker-service.xml --exit" }, - "version": "2.17.0-prerelease", - "stableVersion": "2.16.0" + "version": "2.17.0" } diff --git a/worker-service/tests/network-tests/hedera-sdk-helper.test.js b/worker-service/tests/network-tests/hedera-sdk-helper.test.js index af335cb9f0..f87fb590a0 100644 --- a/worker-service/tests/network-tests/hedera-sdk-helper.test.js +++ b/worker-service/tests/network-tests/hedera-sdk-helper.test.js @@ -16,7 +16,7 @@ async function delay(ms) { } describe('Hedera SDK Helper', function () { - const transactionTimeout = 30 * 1000; + const transactionTimeout = 90 * 1000; let sdk, accountId, accountKey, tokenId, account2Id, account2Key, token2Id, nft; diff --git a/yarn.lock b/yarn.lock index 025f1aca44..9d8de4adb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -887,7 +887,7 @@ __metadata: languageName: node linkType: hard -"@guardian/common@2.17.0-prerelease, @guardian/common@^2.17.0-prerelease, @guardian/common@workspace:common": +"@guardian/common@2.17.0, @guardian/common@^2.17.0, @guardian/common@workspace:common": version: 0.0.0-use.local resolution: "@guardian/common@workspace:common" dependencies: @@ -895,7 +895,7 @@ __metadata: "@azure/identity": ^3.2.2 "@azure/keyvault-secrets": ^4.7.0 "@google-cloud/secret-manager": ^4.2.2 - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/interfaces": ^2.17.0 "@hashgraph/sdk": 2.24.2 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": ^2.0.2 @@ -949,7 +949,7 @@ __metadata: languageName: unknown linkType: soft -"@guardian/interfaces@2.17.0-prerelease, @guardian/interfaces@^2.17.0-prerelease, @guardian/interfaces@workspace:interfaces": +"@guardian/interfaces@2.17.0, @guardian/interfaces@^2.17.0, @guardian/interfaces@workspace:interfaces": version: 0.0.0-use.local resolution: "@guardian/interfaces@workspace:interfaces" dependencies: @@ -3437,8 +3437,8 @@ __metadata: version: 0.0.0-use.local resolution: "analytics-service@workspace:analytics-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1 "@nestjs/jwt": ^10.0.3 @@ -3639,8 +3639,8 @@ __metadata: version: 0.0.0-use.local resolution: "api-gateway@workspace:api-gateway" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1 "@nestjs/jwt": ^10.0.3 @@ -4084,8 +4084,8 @@ __metadata: version: 0.0.0-use.local resolution: "auth-service@workspace:auth-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@meeco/cryppo": ^2.0.2 "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 @@ -7590,8 +7590,8 @@ __metadata: version: 0.0.0-use.local resolution: "guardian-service@workspace:guardian-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@hashgraph/sdk": 2.24.2 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": ^2.0.2 @@ -10141,8 +10141,8 @@ __metadata: version: 0.0.0-use.local resolution: "logger-service@workspace:logger-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 "@nestjs/common": ^9.4.1 @@ -10935,7 +10935,7 @@ __metadata: version: 0.0.0-use.local resolution: "mrv-sender@workspace:mrv-sender" dependencies: - "@guardian/common": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 "@transmute/credentials-context": 0.7.0-unstable.80 "@transmute/did-context": 0.7.0-unstable.80 "@transmute/ed25519-signature-2018": 0.7.0-unstable.80 @@ -11486,8 +11486,8 @@ __metadata: version: 0.0.0-use.local resolution: "notification-service@workspace:notification-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@mikro-orm/core": 5.7.12 "@mikro-orm/mongodb": 5.7.12 "@nestjs/common": ^9.4.1 @@ -12252,8 +12252,8 @@ __metadata: version: 0.0.0-use.local resolution: "policy-service@workspace:policy-service" dependencies: - "@guardian/common": 2.17.0-prerelease - "@guardian/interfaces": 2.17.0-prerelease + "@guardian/common": 2.17.0 + "@guardian/interfaces": 2.17.0 "@hashgraph/sdk": 2.24.2 "@mattrglobal/jsonld-signatures-bbs": ^1.1.2 "@meeco/cryppo": 2.0.2 @@ -15500,8 +15500,8 @@ __metadata: version: 0.0.0-use.local resolution: "worker-service@workspace:worker-service" dependencies: - "@guardian/common": ^2.17.0-prerelease - "@guardian/interfaces": ^2.17.0-prerelease + "@guardian/common": ^2.17.0 + "@guardian/interfaces": ^2.17.0 "@hashgraph/sdk": 2.24.2 "@nestjs/common": ^9.4.1 "@nestjs/core": ^9.4.1