diff --git a/api-gateway/src/api/service/analytics.ts b/api-gateway/src/api/service/analytics.ts index 945858d653..3320e1ee93 100644 --- a/api-gateway/src/api/service/analytics.ts +++ b/api-gateway/src/api/service/analytics.ts @@ -1,4 +1,4 @@ -import { Body, Controller, HttpCode, HttpException, HttpStatus, Post, Query } from '@nestjs/common'; +import { Body, Controller, Get, HttpCode, HttpException, HttpStatus, Post, Query } from '@nestjs/common'; import { ApiInternalServerErrorResponse, ApiBody, ApiOkResponse, ApiOperation, ApiTags, ApiExtraModels, ApiQuery } from '@nestjs/swagger'; import { EntityOwner, Permissions } from '@guardian/interfaces'; import { FilterDocumentsDTO, FilterModulesDTO, FilterPoliciesDTO, FilterSchemasDTO, FilterSearchPoliciesDTO, InternalServerErrorDTO, CompareDocumentsDTO, CompareModulesDTO, ComparePoliciesDTO, CompareSchemasDTO, SearchPoliciesDTO, FilterToolsDTO, CompareToolsDTO, FilterSearchBlocksDTO, SearchBlocksDTO, Examples } from '#middlewares'; @@ -947,4 +947,35 @@ export class AnalyticsApi { await InternalException(error, this.logger); } } + + /** + * Get Indexer availability + */ + @Get('/checkIndexer') + @Auth( + Permissions.POLICIES_POLICY_EXECUTE, + ) + @ApiOperation({ + summary: 'Get Indexer Availability.', + description: 'Returns Indexer Availability (true/false).', + }) + @ApiOkResponse({ + description: 'Successful operation.', + type: Boolean, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO + }) + @HttpCode(HttpStatus.OK) + async checkIndexerAvailability( + @AuthUser() user: IAuthUser + ): Promise { + const guardians = new Guardians(); + try { + return await guardians.getIndexerAvailability(); + } catch (error) { + await InternalException(error, this.logger); + } + } } diff --git a/api-gateway/src/api/service/contract.ts b/api-gateway/src/api/service/contract.ts index f5824c87bf..2fad5fae62 100644 --- a/api-gateway/src/api/service/contract.ts +++ b/api-gateway/src/api/service/contract.ts @@ -1719,5 +1719,57 @@ export class ContractsApi { await InternalException(error, this.logger); } } + + /** + * Get a list of all retire vcs from Indexer + */ + @Get('/retireIndexer') + @Auth( + Permissions.CONTRACTS_DOCUMENT_READ, + // UserRole.STANDARD_REGISTRY, + // UserRole.USER + ) + @ApiOperation({ + summary: 'Return a list of all retire vcs from Indexer.', + description: 'Returns all retire vcs from Indexer.', + }) + @ApiQuery({ + name: 'contractTopicId', + type: String, + description: 'The topic id of contract', + required: true, + example: '0.0.0000000', + }) + @ApiOkResponse({ + description: 'Successful operation.', + isArray: true, + headers: pageHeader, + schema: { + type: 'array', + items: { + type: 'object' + } + } + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error.', + type: InternalServerErrorDTO, + }) + @ApiExtraModels(RetirePoolDTO, InternalServerErrorDTO) + @HttpCode(HttpStatus.OK) + async getRetireVCsFromIndexer( + @AuthUser() user: IAuthUser, + @Response() res: any, + @Query('contractTopicId') contractTopicId: string, + ): Promise { + try { + const owner = new EntityOwner(user); + const guardians = new Guardians(); + const [vcs, count] = await guardians.getRetireVCsFromIndexer(owner, contractTopicId); + return res.header('X-Total-Count', count).send(vcs); + } catch (error) { + await InternalException(error, this.logger); + } + } //#endregion } diff --git a/api-gateway/src/helpers/guardians.ts b/api-gateway/src/helpers/guardians.ts index f9e074354d..0e8508011f 100644 --- a/api-gateway/src/helpers/guardians.ts +++ b/api-gateway/src/helpers/guardians.ts @@ -11,12 +11,14 @@ import { IContract, IDidObject, IOwner, + IRetirementMessage, IRetirePool, IRetireRequest, ISchema, IToken, ITokenInfo, IUser, + IVC, IVCDocument, IVPDocument, MessageAPI, @@ -1846,6 +1848,22 @@ export class Guardians extends NatsService { }); } + /** + * Get retire VCs from Indexer + * @param owner + * @param contractTopicId + * @returns Retire VCs from Indexer and count + */ + public async getRetireVCsFromIndexer( + owner: IOwner, + contractTopicId: string + ): Promise<[IRetirementMessage[], number]> { + return await this.sendMessage(ContractAPI.GET_RETIRE_VCS_FROM_INDEXER, { + owner, + contractTopicId + }); + } + //#endregion /** @@ -3197,4 +3215,12 @@ export class Guardians extends NatsService { public async previewSchemaRule(zip: any, owner: IOwner) { return await this.sendMessage(MessageAPI.PREVIEW_SCHEMA_RULE_FILE, { zip, owner }); } + + + /** + * Get Indexer availability + */ + public async getIndexerAvailability(): Promise { + return await this.sendMessage(MessageAPI.GET_INDEXER_AVAILABILITY, {}); + } } diff --git a/common/src/database-modules/database-server.ts b/common/src/database-modules/database-server.ts index d711484851..c3f417b4eb 100644 --- a/common/src/database-modules/database-server.ts +++ b/common/src/database-modules/database-server.ts @@ -2063,7 +2063,8 @@ export class DatabaseServer extends AbstractDatabaseServer { wasTransferNeeded: boolean, transferSerials: number[], transferAmount: number, - tokenIds: string[] + tokenIds: string[], + target: string ] > { const mintRequests = await this.getMintRequests({ @@ -2098,7 +2099,7 @@ export class DatabaseServer extends AbstractDatabaseServer { if (vpDocument.tokenId) { tokenIds.add(vpDocument.tokenId); } - + const target = mintRequests?.[0]?.target; for (const mintRequest of mintRequests) { if (mintRequest.error) { errors.push(mintRequest.error); @@ -2170,6 +2171,7 @@ export class DatabaseServer extends AbstractDatabaseServer { transferSerials, transferAmount, [...tokenIds], + target, ]; } diff --git a/common/src/interfaces/database-server.ts b/common/src/interfaces/database-server.ts index eb9d39f2ea..6484492865 100644 --- a/common/src/interfaces/database-server.ts +++ b/common/src/interfaces/database-server.ts @@ -1408,7 +1408,8 @@ export abstract class AbstractDatabaseServer { wasTransferNeeded: boolean, transferSerials: number[], transferAmount: number, - tokenIds: string[] + tokenIds: string[], + target: string, ] > diff --git a/frontend/src/app/modules/contract-engine/dialogs/user-retire-pools-dialog/user-retire-pools-dialog.component.ts b/frontend/src/app/modules/contract-engine/dialogs/user-retire-pools-dialog/user-retire-pools-dialog.component.ts index d73c15c1f2..b723b25efb 100644 --- a/frontend/src/app/modules/contract-engine/dialogs/user-retire-pools-dialog/user-retire-pools-dialog.component.ts +++ b/frontend/src/app/modules/contract-engine/dialogs/user-retire-pools-dialog/user-retire-pools-dialog.component.ts @@ -52,6 +52,7 @@ export class UserRetirePoolsDialogComponent implements OnInit { loadPools() { this.loading = true; + this.contractService .getRetirePools({ pageIndex: this.pageIndex, diff --git a/frontend/src/app/modules/policy-engine/policy-engine.module.ts b/frontend/src/app/modules/policy-engine/policy-engine.module.ts index 57bd9f8251..0c063cf91f 100644 --- a/frontend/src/app/modules/policy-engine/policy-engine.module.ts +++ b/frontend/src/app/modules/policy-engine/policy-engine.module.ts @@ -1,5 +1,5 @@ import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { MaterialModule } from 'src/app/modules/common/material.module'; import { FormsModule } from '@angular/forms'; import { DragDropModule } from '@angular/cdk/drag-drop'; @@ -295,6 +295,7 @@ import { RequestDocumentBlockDialog } from './policy-viewer/blocks/request-docum WizardService, DialogService, PolicyProgressService, + DatePipe, { provide: CONFIGURATION_ERRORS, useValue: new Map() diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.html index a38a05f6ca..69030597ae 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.html @@ -184,6 +184,21 @@
{{message.__issuer}}
+ + +
+ receipt + Retirement +
+
+
+ Contract ID: {{message.documents[0].credentialSubject[0].contractId}} +
+
+ User ID: {{message.documents[0].credentialSubject[0].user}} +
+
+
@@ -730,6 +745,75 @@ + + +
+ receipt + Retirement message +
+ +
+ +
+
Document artifact:
+
+ Open document +
+
+ +
+
+
Contract ID:
+
{{selected.documents[0].credentialSubject[0].contractId}}
+
+
+
User ID:
+
{{selected.documents[0].credentialSubject[0].user}}
+
+
+
Transaction:
+
{{selected.consensusTimestamp}}
+
+
+
+
+
Token ID:
+
{{token.tokenId}}
+
+
+
Instance ID:
+
{{token.serials.join(', ')}}
+
+
+
Amount:
+
{{token.count}}
+
+
+ +
+ +
+
Topic ID:
+
+ {{selected.topicId}} +
+
+
+
Message ID:
+
+ {{selected.consensusTimestamp}} +
+
+
+
Owner:
+
+ {{selected.owner}} +
+
+
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.ts index b7b6fa89d3..cf07f7a590 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/messages-report-block/messages-report-block.component.ts @@ -9,7 +9,12 @@ import { VCViewerDialog } from 'src/app/modules/schema-engine/vc-dialog/vc-dialo import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { PolicyHelper } from 'src/app/services/policy-helper.service'; import { WebSocketService } from 'src/app/services/web-socket.service'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { ContractType, IRetirementMessage, IVC } from '@guardian/interfaces'; +import { Observable, forkJoin } from 'rxjs'; +import { ContractService } from 'src/app/services/contract.service'; +import { AnalyticsService } from 'src/app/services/analytics.service'; +import { DatePipe } from '@angular/common'; /** * Dashboard Type @@ -259,6 +264,14 @@ export class MessagesReportBlockComponent implements OnInit { ); private lines!: Line[] | null; + gridSize: number = 0; + + mintTokenId: string; + mintTokenSerials: string[] = []; + groupedByContractRetirements: any = []; + indexerAvailable: boolean = false; + retirementMessages: any[] = []; + constructor( private element: ElementRef, private fb: UntypedFormBuilder, @@ -268,7 +281,10 @@ export class MessagesReportBlockComponent implements OnInit { private dialog: MatDialog, private dialogService: DialogService, private iconRegistry: MatIconRegistry, - private sanitizer: DomSanitizer + private sanitizer: DomSanitizer, + private contractService: ContractService, + private analyticsService: AnalyticsService, + private datePipe: DatePipe ) { iconRegistry.addSvgIconLiteral('token', sanitizer.bypassSecurityTrustHtml(` @@ -356,6 +372,7 @@ export class MessagesReportBlockComponent implements OnInit { if (this.report) { this.createReport(this.report); this.createSmallReport(); + this.loadRetirementMessages(); } } this.removeLines(); @@ -365,6 +382,92 @@ export class MessagesReportBlockComponent implements OnInit { }, 100); } + private loadRetirementMessages() { + this._messages2.forEach(message => { + if (message.__ifMintMessage) { + this.mintTokenId = message.__tokenId; + } + }); + + this.contractService + .getContracts({ + type: ContractType.RETIRE + }) + .subscribe( + (policiesResponse) => { + const contracts = policiesResponse.body || []; + const tokenContractTopicIds: string[] = []; + + if (contracts && contracts.length > 0) { + contracts.forEach(contract => { + if (contract.wipeTokenIds && contract.wipeTokenIds.length > 0 && + contract.wipeTokenIds.some((tokenId: string) => tokenId == this.mintTokenId)) { + tokenContractTopicIds.push(contract.topicId); + } + }); + } + + this.analyticsService.checkIndexer().subscribe(indexerAvailable => { + this.indexerAvailable = indexerAvailable; + if (indexerAvailable && tokenContractTopicIds.length > 0) { + const indexerCalls: Observable>[] = []; + tokenContractTopicIds.forEach(id => { + indexerCalls.push(this.contractService.getRetireVCsFromIndexer(id)) + }) + + this.loading = true; + forkJoin(indexerCalls).subscribe((results: any) => { + this.loading = false; + const retires = results.map((item: any) => item.body) + + let allRetireMessages: any = []; + retires.forEach((retirements: any[]) => { + retirements.forEach((item: any) => { + if (item.documents[0].credentialSubject[0].tokens.some((token: any) => token.tokenId === this.mintTokenId)) { + item.id = item.consensusTimestamp; + item.__ifRetireMessage = true; + item.__timestamp = this.datePipe.transform(new Date(item.documents[0].issuanceDate), 'yyyy-MM-dd, hh:mm:ss'); + allRetireMessages.push(item); + } + }); + }); + + allRetireMessages.sort((a: any, b: any) => new Date(a.documents[0].issuanceDate).getTime() - new Date(b.documents[0].issuanceDate).getTime()); + + // For different topics different ordering + let lastOrderMessageTopic1 = this._topics1?.[this._topics1.length - 1]?.messages.reduce((acc: number, item: any) => item.__order > acc ? item.__order : acc, 0) + 1; + allRetireMessages.forEach((element: any) => { + var newElement = {...element, __order: lastOrderMessageTopic1} + this._messages1.push(newElement); + this._topics1[this._topics1.length - 1].messages.push(newElement); + + lastOrderMessageTopic1++; + }); + let lastOrderMessageTopic2 = this._topics2?.[0]?.messages.reduce((acc: number, item: any) => item.__order > acc ? item.__order : acc, 0) + 1; + allRetireMessages.forEach((element: any) => { + var newElement = {...element, __order: lastOrderMessageTopic2} + this._messages2.push(newElement); + this._topics2[0].messages.push(newElement); + lastOrderMessageTopic2++; + }); + + // Todo: Need filtration by serials and token user + this.retirementMessages = [...allRetireMessages]; + + this._gridTemplateColumns1 = 'repeat(' + (this.gridSize + this.retirementMessages.length + 1) + ', 230px)'; + this._gridTemplateColumns2 = 'repeat(' + (this.gridSize + this.retirementMessages.length) + ', 230px)'; + }) + } + }) + }, + (e) => { + this.loading = false; + } + ); + } + + + private createSmallReport() { for (const topic of this._topics1) { if (topic.message?.messageType === 'INSTANCE_POLICY_TOPIC') { @@ -381,14 +484,15 @@ export class MessagesReportBlockComponent implements OnInit { this._messages2.push(message); } } - let gridSize = 0; + + this.gridSize = 0; this._messages2.sort((a, b) => a.__order > b.__order ? 1 : -1); for (let index = 0; index < this._messages2.length; index++) { const message = this._messages2[index]; message.__order = index + 1; - gridSize = Math.max(gridSize, message.__order); + this.gridSize = Math.max(this.gridSize, message.__order); } - this._gridTemplateColumns2 = 'repeat(' + gridSize + ', 230px)'; + this._gridTemplateColumns2 = 'repeat(' + this.gridSize + ', 230px)'; this._gridTemplateRows2 = 'repeat(' + this._topics2.length + ', 100px) 30px'; } @@ -434,7 +538,7 @@ export class MessagesReportBlockComponent implements OnInit { } private parseMessages() { - let gridSize = 0; + this.gridSize = 0; for (const topic of this._topics1) { if (topic.message) { this.parseMessage(topic, topic.message); @@ -442,7 +546,7 @@ export class MessagesReportBlockComponent implements OnInit { for (const message of topic.messages) { this.parseMessage(topic, message); this._messages1.push(message); - gridSize = Math.max(gridSize, message.__order); + this.gridSize = Math.max(this.gridSize, message.__order); } if (topic.__parent) { topic.__start = 100 * topic.__parent.__order; @@ -465,7 +569,7 @@ export class MessagesReportBlockComponent implements OnInit { topic.message.__rationale = topic.__rationale; } } - this._gridTemplateColumns1 = 'repeat(' + gridSize + ', 230px)'; + this._gridTemplateColumns1 = 'repeat(' + this.gridSize + ', 230px)'; this._gridTemplateRows1 = 'repeat(' + this._topics1.length + ', 100px) 30px'; } @@ -802,7 +906,7 @@ export class MessagesReportBlockComponent implements OnInit { styleClass: 'guardian-dialog', data: { row: null, - document: message.document, + document: message.document || message.documents?.[0], title: 'VC Document', type: 'VC', viewDocument: true, diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html index 125a5e7ad5..ee11404055 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.html @@ -1,26 +1,11 @@
Trust Chain
-
+ HASH/ID - - @@ -31,23 +16,18 @@
-
+

Verifiable Presentation

check_circle - +
+ pButton>

HASH

{{ item.vpDocument.hash }}

@@ -58,11 +38,9 @@

Verifiable Presentation

- + pButton> +

HASH

{{ item.vcDocument.hash }}

@@ -70,17 +48,12 @@

Verifiable Presentation

-
+

Token & Issuer

- +
@@ -91,11 +64,8 @@

Token & Issuer

Receipt Name
-
+
{{ item.mintDocument.username }}
@@ -120,11 +90,8 @@

Token & Issuer

-
+

Primary Impacts

@@ -132,11 +99,7 @@

Primary Impacts

-
+
workspace_premium @@ -145,13 +108,10 @@

Primary Impacts

VC File + *ngIf="item.document" class="open-vc">VC File
-
Primary Impacts
-
+ ">

Secondary Impacts

-
+
workspace_premium @@ -192,7 +150,8 @@

Secondary Impacts

{{ item.label }}
1" - class="vp-section" - > +
This Carbon Offset Claim has met all the requirements as issued in the policy secured to this token. @@ -249,9 +205,8 @@

Secondary Impacts

Policy Overview

- +
@@ -274,11 +229,8 @@

Policy Overview

Issuer Name
-
+
{{ policyDocument.username }}
@@ -288,28 +240,19 @@

Policy Overview

-
-
+
- -
+
-
+ account_tree -
1, @@ -347,40 +286,25 @@

Policy Overview

document.index === null), 'single-multiple-document': item.document.length === 1 - }" - class="chain-item item-type-{{item.type}}" - > + }" class="chain-item item-type-{{item.type}}">
- + {{ item.icon }} - + {{ item.title }}
-
+ " class="revoke-container"> warning Revoked with reason: "{{document.document.comment}}"
{{ item.description }}
-
+
Parties:
@@ -392,45 +316,37 @@

Policy Overview

-
- VC File +
+
-
1 - " - class="multiple-documents-count" - > - chevron_left + " class="multiple-documents-count"> + chevron_left {{ item.activeDocumentIndex }} in {{ item.document.length }} - chevron_right + chevron_right
-
+ " style="display: inline-block;">
{{ item.title }} @@ -446,13 +362,134 @@

Policy Overview

+ + + +
+
+ +
+
+
+
+ Token(s) Retirement: {{ group.documents.length }} +
+
+

Token ID {{ mintTokenId }}

+

ContractID {{ + group.contractId + }}

+
+
+ +

+ No data from the indexer. Connect it and click Refresh for full + details. +

+
+
+
+
+
+ + + + + +
+
+
+
+
+
+ {{ selectedVC.issuanceDate | date : 'YYYY-MM-dd, hh:mm:ss' }} +
+
+ +
+
+
+
+

Token {{ selectedVC.credentialSubject[0]?.tokens[0].tokenId }}

+

Instance ID {{ selectedVC.credentialSubject[0]?.tokens[0].serials.join(', ') }}

+

Amount {{ selectedVC.credentialSubject[0]?.tokens[0].count }}

+ +

Transaction + {{selectedVC.timestamp}} +

+

Transaction -

+ +

User ID {{ selectedVC.credentialSubject[0]?.user }}

+
+
+

Token {{ selectedVC.credentialSubject[0]?.tokens[1].tokenId }}

+

Instance ID {{ selectedVC.credentialSubject[0]?.tokens[1].serials.join(', ') }}

+

Amount {{ selectedVC.credentialSubject[0]?.tokens[1].count }}

+
+
+ +
+ chevron_left + + {{ (group.selectedItemIndex + 1) }} of {{ group.documents.length }} + + chevron_right +
+
+
+
+ +
+
+
+
+ {{ selectedVC.issuanceDate | date : 'YYYY-MM-dd, hh:mm:ss' }} +
+
+ +
+
+
+

Token {{ selectedVC.credentialSubject[0]?.tokens[0].tokenId }}

+

Instance ID {{ selectedVC.credentialSubject[0]?.tokens[0].serials.join(', ') }}

+

Amount {{ selectedVC.credentialSubject[0]?.tokens[0].count }}

+

Transaction {{ selectedVC.timestamp }}

+

User ID {{ selectedVC.credentialSubject[0]?.user }}

+
+ +
+ chevron_left + + {{ (group.selectedItemIndex + 1) }} of {{ group.documents.length }} + + chevron_right +
+
+
+
+
+
+
+
+
-
- +
@@ -468,4 +505,4 @@

Policy Overview

-
+
\ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss index 88314b007c..d58fa479b4 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.scss @@ -165,8 +165,8 @@ a[disabled="true"] { } .chain-item { - margin: 6px 23px; - padding: 22px 42px; + margin: 6px 8px; + padding: 16px; /* display: flex; flex-direction: column; align-content: center; @@ -175,8 +175,8 @@ a[disabled="true"] { display: inline-block; box-sizing: border-box; - width: 493px; - height: 230px; + width: 282px; + height: 190px; background: #ffffff 0% 0% no-repeat padding-box; box-shadow: 0px 3px 6px #00000029; border-radius: 8px; @@ -209,14 +209,18 @@ a[disabled="true"] { .chain-document { position: absolute; - right: 42px; - top: 23px; + right: 16px; + top: 16px; font-family: Inter; font-size: 16px; color: #0b73f8; font-weight: bold; padding: 0px 0px 0px 10px; background: #fff; + + .p-button { + padding: 6px 16px; + } } .chain-document a { @@ -236,14 +240,18 @@ a[disabled="true"] { } .chain-title { - font-family: Inter; - font-size: 18px; - color: #707070; + margin-top: 8px; display: flex; align-items: center; grid-gap: 15px; gap: 15px; font-weight: bold; + + font-family: Inter; + font-size: 12px; + font-weight: 600; + line-height: 14px; + color: #23252E; } .chain-title mat-icon { @@ -255,11 +263,16 @@ a[disabled="true"] { .chain-id { white-space: normal; - border-bottom: 1px solid rgb(112 112 112 / 64%); - margin: 0 0 25px 0; - padding: 25px 0; + border-bottom: 1px solid #E1E7EF; + margin: 0 0 16px 0; + padding: 16px 0; + font-family: Inter; - font-size: 20px; + font-size: 14px; + font-weight: 400; + line-height: 16px; + text-align: left; + /* text-transform: capitalize; */ } @@ -449,18 +462,21 @@ a.open-vp { .parties-label { font-family: Inter; - font-size: 16px; - color: #707070; - padding: 3px 0; + font-size: 12px; + font-weight: 600; + line-height: 16px; + text-align: left; + color: #23252E; } .parties-value, .nested-documents-value { font-family: Inter; - font-size: 20px; + font-size: 14px; + font-weight: 700; + line-height: 16px; + text-align: left; color: #0c77ff; - font-weight: bold; - line-height: 25px; } .parties, @@ -551,12 +567,15 @@ a.open-vp { position: absolute; right: 42px; bottom: 12px; - font-family: Inter; - font-size: 18px; color: #707070; - font-weight: bold; display: flex; align-items: center; + + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #23252E; } .multiple-documents-count mat-icon { @@ -763,4 +782,120 @@ h3 { flex-wrap: wrap; row-gap: 32px; column-gap: 16px +} + +.item-type-RETIRE { + .chain-top-block { + padding-bottom: 14px; + margin-top: 8px; + margin-bottom: 8px; + border-bottom: 1px solid #E1E7EF; + } + + .chain-title { + margin-top: 0; + } + + &.multiple-tokens { + margin: 6px 16px 6px 8px; + width: 450px; + height: 182px; + } +} + +.retirements-container { + position: relative; + display: flex; + + .chain-item.item-type-retire-group { + border: 1px solid var(--grey-grey-6, #848FA9); + } + + .retirement-group-icon { + align-self: center; + color: var(--color-grey-4, #848FA9); + } + + .multiple-documents-count { + bottom: auto; + + .mat-icon { + color: var(--color-grey-5, #AAB7C4); + } + } +} + +.retire-block { + .chain-title { + margin-top: 0; + padding-bottom: 8px; + margin-bottom: 8px; + border-bottom: 1px solid #E1E7EF; + } +} + +.retire-multiple-card-1 { + position: absolute; + top: -4px; + right: -4px; +} + +.retire-multiple-card-2 { + position: absolute; + top: -8px; + right: -8px; +} + +.retire-multiple-tokens { + display: flex; + justify-content: space-between; +} + +.retire-info { + p { + margin-bottom: 8px; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #848FA9; + + span { + margin-left: 8px; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + color: #000000; + } + } + + &.second-token { + p { + text-align: left; + } + } +} + +.retire-no-indexer-info { + display: flex; + padding: 8px; + gap: 8px; + border-radius: 8px; + border: 1px solid #AAB7C4; + background-color: #F9FAFC; + color: #AAB7C4; + + p { + margin: 0; + font-family: Inter; + font-size: 12px; + font-weight: 500; + line-height: 14px; + text-align: left; + text-underline-position: from-font; + text-decoration-skip-ink: none; + color: #6C7791; + white-space: break-spaces; + } } \ No newline at end of file diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts index 7fec338c9c..6e9ae51c5d 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/report-block/report-block.component.ts @@ -12,6 +12,9 @@ import { ITokenReport, IVCReport, IVPReport, + ContractType, + IRetirementMessage, + IVC, } from '@guardian/interfaces'; import { VCViewerDialog } from 'src/app/modules/schema-engine/vc-dialog/vc-dialog.component'; import { IPFSService } from 'src/app/services/ipfs.service'; @@ -19,7 +22,10 @@ import { PolicyEngineService } from 'src/app/services/policy-engine.service'; import { WebSocketService } from 'src/app/services/web-socket.service'; import { IconsArray } from './iconsArray'; import { DialogService } from 'primeng/dynamicdialog'; -import { HttpErrorResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { ContractService } from 'src/app/services/contract.service'; +import { AnalyticsService } from 'src/app/services/analytics.service'; +import { forkJoin, Observable } from 'rxjs'; interface IAdditionalDocument { vpDocument?: IVPReport | undefined; @@ -55,6 +61,12 @@ export class ReportBlockComponent implements OnInit { value: ['', Validators.required], }); + vpDocument: any; + mintTokenId: string; + mintTokenSerials: string[] = []; + groupedByContractRetirements: any = []; + indexerAvailable: boolean = false; + constructor( private policyEngineService: PolicyEngineService, private wsService: WebSocketService, @@ -63,7 +75,9 @@ export class ReportBlockComponent implements OnInit { private dialogService: DialogService, iconRegistry: MatIconRegistry, sanitizer: DomSanitizer, - private ipfs: IPFSService + private ipfs: IPFSService, + private contractService: ContractService, + private analyticsService: AnalyticsService ) { for (let i = 0; i < IconsArray.length; i++) { const element = IconsArray[i]; @@ -95,6 +109,134 @@ export class ReportBlockComponent implements OnInit { } } + loadRetireData() { + this.loading = true; + + this.contractService + .getContracts({ + type: ContractType.RETIRE + }) + .subscribe( + (policiesResponse) => { + const contracts = policiesResponse.body || []; + const tokenContractTopicIds: string[] = []; + + if (contracts && contracts.length > 0) { + contracts.forEach(contract => { + if (contract.wipeTokenIds && contract.wipeTokenIds.length > 0 && + contract.wipeTokenIds.some((tokenId: string) => tokenId == this.mintTokenId)) { + tokenContractTopicIds.push(contract.topicId); + } + }); + } + + this.analyticsService.checkIndexer().subscribe(indexerAvailable => { + this.indexerAvailable = indexerAvailable; + if (indexerAvailable && tokenContractTopicIds.length > 0) { + const indexerCalls: Observable>[] = []; + tokenContractTopicIds.forEach(id => { + indexerCalls.push(this.contractService.getRetireVCsFromIndexer(id)) + }) + + this.loading = true; + forkJoin([this.contractService.getRetireVCs(), ...indexerCalls]).subscribe((results: any) => { + this.loading = false; + const retires = results.map((item: any) => item.body) + + const [retiresDb, ...retiresIndexer] = retires; + const retiresDbMapped = retiresDb + .filter((item: any) => item.type == 'RETIRE') + .map((item: any) => item.document); + + const combinedRetirements = [...retiresDbMapped]; + retiresIndexer.forEach((retirements: IRetirementMessage[]) => { + retirements.forEach((item: IRetirementMessage) => { + const existInGuardianDocument = retiresDbMapped.find((retire: IVC) => retire.id === item.documents[0].id); + if (!existInGuardianDocument) { + item.documents[0].topicId = item.topicId; + item.documents[0].timestamp = item.consensusTimestamp; + item.documents[0].sequenceNumber = item.sequenceNumber; + item.documents[0].owner = item.owner; + combinedRetirements.push(item.documents[0]); + } + else { + existInGuardianDocument.topicId = item.topicId; + existInGuardianDocument.timestamp = item.consensusTimestamp; + existInGuardianDocument.sequenceNumber = item.sequenceNumber; + existInGuardianDocument.owner = item.owner; + } + }); + }); + + const tokenRetirementDocuments = combinedRetirements + .filter((item: any) => item.credentialSubject.some((subject: any) => + subject.user === this.vpDocument.document.target + && subject.tokens.some((token: any) => + token.tokenId === this.mintTokenId + && this.mintTokenSerials.length <= 0 || token.serials.some((serial: string) => this.mintTokenSerials.includes(serial) + )))); + + this.groupedByContractRetirements = Array.from( + new Map(tokenRetirementDocuments + .map((item: any) => [item.credentialSubject[0].contractId, []]) + )).map(([contractId]) => ({ + contractId, + selectedItemIndex: 0, + documents: tokenRetirementDocuments.filter((item: any) => item.credentialSubject[0].contractId === contractId) + })) + }) + } else { + this.contractService + .getRetireVCs() + .subscribe( + (policiesResponse) => { + const tokenRetirementDocuments = (policiesResponse.body || []) + .filter((item: any) => item.type == 'RETIRE' + && item.document.credentialSubject.some((subject: any) => + subject.user === this.vpDocument.document.target + && subject.tokens.some((token: any) => + token.tokenId === this.mintTokenId + && this.mintTokenSerials.length <= 0 || token.serials.some((serial: string) => this.mintTokenSerials.includes(serial) + )))).map((vc: any) => vc.document); + + this.groupedByContractRetirements = Array.from( + new Map(tokenRetirementDocuments + .map((item: any) => [item.credentialSubject[0].contractId, []]) + )).map(([contractId]) => ({ + contractId, + selectedItemIndex: 0, + documents: tokenRetirementDocuments.filter((item: any) => item.credentialSubject[0].contractId === contractId) + })) + + this.loading = false; + }, + (e) => { + this.loading = false; + } + ); + } + }) + }, + (e) => { + this.loading = false; + } + ); + } + + getSelectedRetirementVC(group: any): any { + return group.documents[group.selectedItemIndex]; + } + + onNextRetirementClick(event: any, group: any) { + event.stopPropagation(); + group.selectedItemIndex = group.documents.length > (group.selectedItemIndex + 1) ? group.selectedItemIndex + 1 : 0; + } + + onPrevRetirementClick(event: any, group: any) { + event.stopPropagation(); + group.selectedItemIndex = (group.selectedItemIndex - 1) >= 0 ? (group.selectedItemIndex - 1) : (group.documents.length - 1); + } + loadData() { this.loading = true; if (this.static) { @@ -150,6 +292,11 @@ export class ReportBlockComponent implements OnInit { this.policyCreatorDocument = report.policyCreatorDocument; this.documents = report.documents || []; + this.mintTokenId = report.mintDocument?.tokenId || ''; + this.mintTokenSerials = (report.vpDocument?.document as any).serials.map((serialItem: any) => serialItem.serial); + this.vpDocument = report.vpDocument; + this.loadRetireData(); + const mainDocument = this.createAdditionalDocument(report); if (mainDocument) { this.mainDocuments = [mainDocument]; @@ -249,7 +396,7 @@ export class ReportBlockComponent implements OnInit { type: 'VC', } }); - dialogRef.onClose.subscribe(async (result) => {}); + dialogRef.onClose.subscribe(async (result) => { }); } openVPDocument(item: any) { @@ -268,7 +415,7 @@ export class ReportBlockComponent implements OnInit { type: 'VP', } }); - dialogRef.onClose.subscribe(async (result) => {}); + dialogRef.onClose.subscribe(async (result) => { }); } openJsonDocument(item: ITokenReport) { @@ -284,7 +431,28 @@ export class ReportBlockComponent implements OnInit { type: 'JSON', } }); - dialogRef.onClose.subscribe(async (result) => {}); + dialogRef.onClose.subscribe(async (result) => { }); + } + + openRetireVCDocument( + item: any, + document?: any + ) { + const title = `Retire Document`; + const dialogRef = this.dialogService.open(VCViewerDialog, { + showHeader: false, + width: '1000px', + styleClass: 'guardian-dialog', + data: { + id: item.id, + row: item, + viewDocument: true, + document: item, + title: title, + type: 'VC', + } + }); + dialogRef.onClose.subscribe(async (result) => { }); } mapData(data: any[]) { diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts index c079aa49e1..c43cd2dca6 100644 --- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts +++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.ts @@ -389,7 +389,7 @@ export class PolicyViewerComponent implements OnInit, OnDestroy { const currentStepIndex = this.policyProgressService.getCurrentStepIndex(); for (let i = (currentStepIndex - 1); i >= 0; i--) { const step = this.steps[i]; - if (!step.blockId) { + if (!step || !step.blockId) { continue; } const hasAction = this.policyProgressService.stepHasAction(step.blockId); diff --git a/frontend/src/app/services/analytics.service.ts b/frontend/src/app/services/analytics.service.ts index 8eed152cac..9f81e3ebff 100644 --- a/frontend/src/app/services/analytics.service.ts +++ b/frontend/src/app/services/analytics.service.ts @@ -74,4 +74,8 @@ export class AnalyticsService { public searchBlocks(options: any): Observable { return this.http.post(`${this.url}/search/blocks`, options); } + + public checkIndexer(): Observable { + return this.http.get(`${this.url}/checkIndexer`); + } } \ No newline at end of file diff --git a/frontend/src/app/services/contract.service.ts b/frontend/src/app/services/contract.service.ts index 4b3a0161fb..95a3992ee8 100644 --- a/frontend/src/app/services/contract.service.ts +++ b/frontend/src/app/services/contract.service.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs'; import { API_BASE_URL } from './api'; import { ContractType, + IRetirementMessage, RetireTokenPool, RetireTokenRequest, } from '@guardian/interfaces'; @@ -15,7 +16,7 @@ import { export class ContractService { private readonly url: string = `${API_BASE_URL}/contracts`; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { } //#region Common contract endpoints public getContracts(params: { @@ -318,5 +319,14 @@ export class ContractService { }); } + public getRetireVCsFromIndexer( + contractTopicId: string + ): Observable> { + let url = `${this.url}/retireIndexer?contractTopicId=${contractTopicId}`; + return this.http.get(url, { + observe: 'response', + }); + } + //#endregion } diff --git a/guardian-service/src/api/analytics.service.ts b/guardian-service/src/api/analytics.service.ts index dfe21feb7d..ab402dcf13 100644 --- a/guardian-service/src/api/analytics.service.ts +++ b/guardian-service/src/api/analytics.service.ts @@ -731,6 +731,20 @@ export async function analyticsAPI(logger: PinoLogger): Promise { return new MessageError(error); } }); + + ApiResponse(MessageAPI.GET_INDEXER_AVAILABILITY, + async () => { + try { + const result = await new Workers().addNonRetryableTask({ + type: WorkerTaskType.ANALYTICS_GET_INDEXER_AVAILABILITY, + data: {} + }, 2); + + return new MessageResponse(result); + } catch (error) { + return new MessageResponse(false); + } + }); } @Module({ diff --git a/guardian-service/src/api/contract.service.ts b/guardian-service/src/api/contract.service.ts index 2ac2bde1e8..83e56fae66 100644 --- a/guardian-service/src/api/contract.service.ts +++ b/guardian-service/src/api/contract.service.ts @@ -3408,4 +3408,34 @@ export async function contractAPI( return new MessageError(error); } }); + + ApiResponse(ContractAPI.GET_RETIRE_VCS_FROM_INDEXER, async (msg: { + owner: IOwner, + contractTopicId: string + }) => { + try { + if (!msg) { + return new MessageError('Invalid get contract parameters'); + } + + const { owner, contractTopicId } = msg; + + if (!owner.creator) { + throw new Error('Owner is required'); + } + + const messages = await new Workers().addNonRetryableTask({ + type: WorkerTaskType.ANALYTICS_GET_RETIRE_DOCUMENTS, + data: { + payload: { options: { topicId: contractTopicId } } + } + }, 2); + + return new MessageResponse([messages, messages.length]); + } catch (error) { + await logger.error(error, ['GUARDIAN_SERVICE']); + return new MessageError(error); + } + }); + } diff --git a/guardian-service/system-schemas/system-schemas.json b/guardian-service/system-schemas/system-schemas.json index 20ce7d0751..a5ea4fcf98 100644 --- a/guardian-service/system-schemas/system-schemas.json +++ b/guardian-service/system-schemas/system-schemas.json @@ -163,8 +163,8 @@ }, "contractId": { "$comment": "{\"term\": \"user\", \"@id\": \"https://www.schema.org/text\"}", - "title": "user", - "description": "user", + "title": "contract", + "description": "contract", "type": "string", "readOnly": false }, diff --git a/indexer-api-gateway/src/api/services/analytics.ts b/indexer-api-gateway/src/api/services/analytics.ts index cb8dce1c45..8416fa79df 100644 --- a/indexer-api-gateway/src/api/services/analytics.ts +++ b/indexer-api-gateway/src/api/services/analytics.ts @@ -1,4 +1,4 @@ -import { Controller, HttpCode, HttpStatus, Body, Post } from '@nestjs/common'; +import { Controller, HttpCode, HttpStatus, Body, Post, Get } from '@nestjs/common'; import { ApiBody, ApiInternalServerErrorResponse, @@ -7,12 +7,14 @@ import { ApiTags, ApiUnprocessableEntityResponse, } from '@nestjs/swagger'; -import { IndexerMessageAPI } from '@indexer/common'; +import { IndexerMessageAPI, Message } from '@indexer/common'; import { ApiClient } from '../api-client.js'; import { InternalServerErrorDTO, + RawMessageDTO, SearchPolicyParamsDTO, SearchPolicyResultDTO, + MessageDTO, } from '#dto'; @Controller('analytics') @@ -45,4 +47,50 @@ export class AnalyticsApi extends ApiClient { body ); } + + @ApiOperation({ + summary: 'Search contract retirements', + description: 'Returns contract retirements result', + }) + @ApiBody({ + description: 'Search policy parameters', + type: RawMessageDTO, + }) + @ApiOkResponse({ + description: 'Search policy result', + type: [MessageDTO], + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO, + }) + @ApiUnprocessableEntityResponse({ + description: 'Unprocessable entity', + }) + @Post('/search/retire') + @HttpCode(HttpStatus.OK) + async getRetireDocuments(@Body() body: RawMessageDTO) { + return await this.send( + IndexerMessageAPI.GET_RETIRE_DOCUMENTS, + body + ); + } + + @Get('/checkAvailability') + @ApiOperation({ + summary: 'Get indexer availability', + description: 'Returns indexer availability', + }) + @ApiOkResponse({ + description: 'Indexer availability result', + type: Boolean, + }) + @ApiInternalServerErrorResponse({ + description: 'Internal server error', + type: InternalServerErrorDTO, + }) + @HttpCode(HttpStatus.OK) + async getIndexerAvailability(): Promise { + return await this.send(IndexerMessageAPI.GET_INDEXER_AVAILABILITY, {}); + } } diff --git a/indexer-api-gateway/src/dto/index.ts b/indexer-api-gateway/src/dto/index.ts index 267738e5c3..478aaa1723 100644 --- a/indexer-api-gateway/src/dto/index.ts +++ b/indexer-api-gateway/src/dto/index.ts @@ -11,3 +11,4 @@ export * from './relationships.dto.js'; export * from './schema-tree.dto.js'; export * from './internal-server-error.dto.js'; export * from './search-policy.dto.js'; +export * from './message.dto.js'; diff --git a/indexer-api-gateway/src/dto/message.dto.ts b/indexer-api-gateway/src/dto/message.dto.ts index a12b351009..616c427223 100644 --- a/indexer-api-gateway/src/dto/message.dto.ts +++ b/indexer-api-gateway/src/dto/message.dto.ts @@ -87,4 +87,9 @@ export class MessageDTO implements Message { example: ['0.0.4481265'], }) tokens: string[]; + @ApiProperty({ + description: 'SequenceNumber', + example: 0, + }) + sequenceNumber?: number; } diff --git a/indexer-common/src/entity/message.ts b/indexer-common/src/entity/message.ts index 27a834b1ad..15494b922f 100644 --- a/indexer-common/src/entity/message.ts +++ b/indexer-common/src/entity/message.ts @@ -90,4 +90,7 @@ export class Message implements IMessage { @Property({ nullable: true }) tokens: string[]; + + @Property({ nullable: true }) + sequenceNumber?: number; } diff --git a/indexer-common/src/messages/message-api.ts b/indexer-common/src/messages/message-api.ts index 39ec90bae7..c0ae57bde6 100644 --- a/indexer-common/src/messages/message-api.ts +++ b/indexer-common/src/messages/message-api.ts @@ -72,5 +72,8 @@ export enum IndexerMessageAPI { GET_ANALYTICS_SEARCH_POLICY = 'INDEXER_API_GET_ANALYTICS_SEARCH_POLICY', // #endregion - UPDATE_FILES = 'INDEXER_API_UPDATE_FILES' + UPDATE_FILES = 'INDEXER_API_UPDATE_FILES', + + GET_INDEXER_AVAILABILITY = "INDEXER_API_GET_INDEXER_AVAILABILITY", + GET_RETIRE_DOCUMENTS = "INDEXER_API_GET_RETIRE_DOCUMENTS" } diff --git a/indexer-interfaces/src/interfaces/message.interface.ts b/indexer-interfaces/src/interfaces/message.interface.ts index da9aadbfc7..b09aa1fa9c 100644 --- a/indexer-interfaces/src/interfaces/message.interface.ts +++ b/indexer-interfaces/src/interfaces/message.interface.ts @@ -77,4 +77,8 @@ export interface Message { * Tokens */ tokens: string[]; + /** + * Sequence number + */ + sequenceNumber?: number; } diff --git a/indexer-service/src/api/analytics.service.ts b/indexer-service/src/api/analytics.service.ts index 8d9fe2e7a9..1c127adb8d 100644 --- a/indexer-service/src/api/analytics.service.ts +++ b/indexer-service/src/api/analytics.service.ts @@ -6,10 +6,11 @@ import { MessageError, DataBaseHelper, Message, - AnyResponse + AnyResponse, + MessageCache } from '@indexer/common'; import escapeStringRegexp from 'escape-string-regexp'; -import { MessageAction, MessageType, SearchPolicyParams, SearchPolicyResult } from '@indexer/interfaces'; +import { MessageAction, MessageType, RawMessage, SearchPolicyParams, SearchPolicyResult, VCDetails } from '@indexer/interfaces'; import { HashComparator } from '../analytics/index.js'; @Controller() @@ -122,4 +123,55 @@ export class AnalyticsService { return new MessageError(error); } } + + @MessagePattern(IndexerMessageAPI.GET_RETIRE_DOCUMENTS) + async getRetireDocuments( + @Payload() + msg: RawMessage + ): Promise> { + try { + const { topicId } = msg; + const em = DataBaseHelper.getEntityManager(); + const [messages, count] = (await em.findAndCount( + Message, + { + topicId, + action: MessageAction.CreateVC, + } as any + )) as any; + + const [messagesCache] = (await em.findAndCount( + MessageCache, + { + topicId, + } as any + )) as any; + + for (const message of messages) { + let VCdocuments: VCDetails[] = []; + for (const fileName of message.files) { + try { + const file = await DataBaseHelper.loadFile(fileName); + VCdocuments.push(JSON.parse(file) as VCDetails); + } catch (error) { + } + } + message.documents = VCdocuments; + + var messageCache = messagesCache.find((cache: MessageCache) => cache.consensusTimestamp == message.consensusTimestamp); + if (messageCache) { + message.sequenceNumber = messageCache.sequenceNumber; + } + } + + return new MessageResponse(messages); + } catch (error) { + return new MessageError(error); + } + } + + @MessagePattern(IndexerMessageAPI.GET_INDEXER_AVAILABILITY) + async checkAvailability(): Promise> { + return new MessageResponse(true); + } } diff --git a/interfaces/src/interface/index.ts b/interfaces/src/interface/index.ts index d158738003..0de6f98623 100644 --- a/interfaces/src/interface/index.ts +++ b/interfaces/src/interface/index.ts @@ -42,4 +42,5 @@ export * from './policy-tool-metadata.interface.js'; export * from './sign-options.interface.js' export * from './owner.interface.js' export * from './statistic.interface.js' -export * from './schema-rules.interface.js' \ No newline at end of file +export * from './schema-rules.interface.js' +export * from './retirement-message.interface.js' \ No newline at end of file diff --git a/interfaces/src/interface/retirement-message.interface.ts b/interfaces/src/interface/retirement-message.interface.ts new file mode 100644 index 0000000000..ce2f8fac0b --- /dev/null +++ b/interfaces/src/interface/retirement-message.interface.ts @@ -0,0 +1,73 @@ +/** + * Contract + */ +export class IRetirementMessage { + /** + * Identifier + */ + id: string; + /** + * Topic identifier + */ + topicId: string; + /** + * Message identifier + */ + consensusTimestamp: string; + /** + * Owner + */ + owner: string; + /** + * UUID + */ + uuid: string; + /** + * Status + */ + status: string; + /** + * Status reason + */ + statusReason: string; + /** + * Lang + */ + lang: string; + /** + * Response type + */ + responseType: string; + /** + * Status message + */ + statusMessage: string; + /** + * Options + */ + options: any; + /** + * Analytics + */ + analytics?: any; + /** + * Files + */ + files: string[]; + /** + * Documents + */ + documents: any[]; + /** + * Topics + */ + topics: string[]; + /** + * Tokens + */ + tokens: string[]; + /** + * Sequence number + */ + sequenceNumber: string; +} diff --git a/interfaces/src/type/messages/contract-api.type.ts b/interfaces/src/type/messages/contract-api.type.ts index 0bc2e0b677..b3383aabcb 100644 --- a/interfaces/src/type/messages/contract-api.type.ts +++ b/interfaces/src/type/messages/contract-api.type.ts @@ -48,7 +48,8 @@ export enum ContractAPI { APPROVE_RETIRE = 'APPROVE_RETIRE', ADD_RETIRE_ADMIN = 'ADD_RETIRE_ADMIN', REMOVE_RETIRE_ADMIN = 'REMOVE_RETIRE_ADMIN', - GET_RETIRE_VCS = 'GET_RETIRE_VCS' + GET_RETIRE_VCS = 'GET_RETIRE_VCS', + GET_RETIRE_VCS_FROM_INDEXER = 'GET_RETIRE_VCS_FROM_INDEXER' //#endregion } diff --git a/interfaces/src/type/messages/message-api.type.ts b/interfaces/src/type/messages/message-api.type.ts index 105b6c8f14..30058c9848 100644 --- a/interfaces/src/type/messages/message-api.type.ts +++ b/interfaces/src/type/messages/message-api.type.ts @@ -244,6 +244,7 @@ export enum MessageAPI { IMPORT_SCHEMA_RULE_FILE = 'IMPORT_SCHEMA_RULE_FILE', EXPORT_SCHEMA_RULE_FILE = 'EXPORT_SCHEMA_RULE_FILE', PREVIEW_SCHEMA_RULE_FILE = 'PREVIEW_SCHEMA_RULE_FILE', + GET_INDEXER_AVAILABILITY = 'GET_INDEXER_AVAILABILITY', } /** diff --git a/interfaces/src/type/messages/workers.type.ts b/interfaces/src/type/messages/workers.type.ts index 7c5d149884..68f623c540 100644 --- a/interfaces/src/type/messages/workers.type.ts +++ b/interfaces/src/type/messages/workers.type.ts @@ -37,7 +37,9 @@ export enum WorkerTaskType { GET_TOKEN_INFO = 'get-token-info', GET_CONTRACT_EVENTS = 'get-contract-events', GET_TRANSACTIONS = 'get-transaction', - ANALYTICS_SEARCH_POLICIES= 'analytics-search-policies', + ANALYTICS_SEARCH_POLICIES = 'analytics-search-policies', + ANALYTICS_GET_INDEXER_AVAILABILITY = "analytics-get-indexer-availability", + ANALYTICS_GET_RETIRE_DOCUMENTS = 'analytics-get-retire-documents' } /** diff --git a/policy-service/src/policy-engine/blocks/report-block.ts b/policy-service/src/policy-engine/blocks/report-block.ts index 0fc8a0ee13..dcdd0af08b 100644 --- a/policy-service/src/policy-engine/blocks/report-block.ts +++ b/policy-service/src/policy-engine/blocks/report-block.ts @@ -426,7 +426,7 @@ export class ReportBlock { const vp: any = await ref.databaseServer.getVpDocument({ hash, policyId: ref.policyId }); if (vp) { - [vp.serials, vp.amount, vp.error, vp.wasTransferNeeded, vp.transferSerials, vp.transferAmount, vp.tokenIds] = await ref.databaseServer.getVPMintInformation(vp); + [vp.serials, vp.amount, vp.error, vp.wasTransferNeeded, vp.transferSerials, vp.transferAmount, vp.tokenIds, vp.target] = await ref.databaseServer.getVPMintInformation(vp); report = await this.addReportByVP(report, variables, vp, true); } else { const vc = await ref.databaseServer.getVcDocument({ hash, policyId: ref.policyId }) diff --git a/swagger-indexer.yaml b/swagger-indexer.yaml index 34b4b755d2..94412afa63 100644 --- a/swagger-indexer.yaml +++ b/swagger-indexer.yaml @@ -1865,8 +1865,59 @@ paths: application/json: schema: $ref: '#/components/schemas/InternalServerErrorDTO' - tags: + tags: &ref_2 - analytics + /analytics/search/retire: + post: + operationId: AnalyticsApi_getRetireDocuments + summary: Search contract retirements + description: Returns contract retirements result + parameters: [] + requestBody: + required: true + description: Search policy parameters + content: + application/json: + schema: + $ref: '#/components/schemas/RawMessageDTO' + responses: + '200': + description: Search policy result + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/MessageDTO' + '422': + description: Unprocessable entity + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_2 + /analytics/checkAvailability: + get: + operationId: AnalyticsApi_getIndexerAvailability + summary: Get indexer availability + description: Returns indexer availability + parameters: [] + responses: + '200': + description: Indexer availability result + content: + application/json: + schema: + type: boolean + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_2 info: title: Guardian description: >- @@ -2017,25 +2068,29 @@ components: description: Status message files: description: Files - example: &ref_2 + example: &ref_3 - QmYtKEVfpbDwn7XLHjnjap224ESi3vLiYpkbWoabnxs6cX type: array items: type: string topics: description: Topics - example: &ref_3 + example: &ref_4 - 0.0.4481265 type: array items: type: string tokens: description: Tokens - example: &ref_4 + example: &ref_5 - 0.0.4481265 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -2110,6 +2165,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -2320,22 +2376,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -2410,6 +2470,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -2458,22 +2519,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -2556,6 +2621,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -2774,22 +2840,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -2864,6 +2934,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -3007,22 +3078,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -3097,6 +3172,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -3220,22 +3296,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -3310,6 +3390,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -3426,22 +3507,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -3514,6 +3599,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -3602,22 +3688,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -3705,6 +3795,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -3957,22 +4048,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -4047,6 +4142,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -4153,22 +4249,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -4243,6 +4343,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -4291,22 +4392,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -4389,6 +4494,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -4463,22 +4569,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 required: - id - topicId @@ -4493,6 +4603,7 @@ components: - files - topics - tokens + - sequenceNumber RelationshipDTO: type: object properties: @@ -4653,22 +4764,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -4741,6 +4856,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -4817,22 +4933,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -4915,6 +5035,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -5039,22 +5160,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -5129,6 +5254,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -5177,22 +5303,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -5275,6 +5405,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -5528,22 +5659,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -5618,6 +5753,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options @@ -5817,22 +5953,26 @@ components: description: Status message files: description: Files - example: *ref_2 + example: *ref_3 type: array items: type: string topics: description: Topics - example: *ref_3 + example: *ref_4 type: array items: type: string tokens: description: Tokens - example: *ref_4 + example: *ref_5 type: array items: type: string + sequenceNumber: + type: number + description: SequenceNumber + example: 0 type: type: string description: Type @@ -5907,6 +6047,7 @@ components: - files - topics - tokens + - sequenceNumber - type - action - options diff --git a/swagger.yaml b/swagger.yaml index c3bbcec0cf..f28e90f1fa 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -822,6 +822,32 @@ paths: tags: *ref_1 security: - bearer: [] + /analytics/checkIndexer: + get: + operationId: AnalyticsApi_checkIndexerAvailability + summary: Get Indexer Availability. + description: Returns Indexer Availability (true/false). + parameters: [] + responses: + '200': + description: Successful operation. + content: + application/json: + schema: + type: boolean + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_1 + security: + - bearer: [] /artifacts: get: operationId: ArtifactApi_getArtifactsV2 @@ -2391,6 +2417,46 @@ paths: tags: *ref_3 security: - bearer: [] + /contracts/retireIndexer: + get: + operationId: ContractsApi_getRetireVCsFromIndexer + summary: Return a list of all retire vcs from Indexer. + description: Returns all retire vcs from Indexer. + parameters: + - name: contractTopicId + required: true + in: query + description: The topic id of contract + example: 0.0.0000000 + schema: + type: string + responses: + '200': + description: Successful operation. + headers: + X-Total-Count: + schema: + type: integer + description: Total items in the collection. + content: + application/json: + schema: + type: array + items: + type: object + '401': + description: Unauthorized. + '403': + description: Forbidden. + '500': + description: Internal server error. + content: + application/json: + schema: + $ref: '#/components/schemas/InternalServerErrorDTO' + tags: *ref_3 + security: + - bearer: [] /demo/registered-users: get: operationId: DemoApi_registeredUsers diff --git a/worker-service/src/api/worker.ts b/worker-service/src/api/worker.ts index bfe9c4a0fa..5230f3f76c 100644 --- a/worker-service/src/api/worker.ts +++ b/worker-service/src/api/worker.ts @@ -327,6 +327,42 @@ export class Worker extends NatsService { break; } + case WorkerTaskType.ANALYTICS_GET_RETIRE_DOCUMENTS: { + const { options } = task.data.payload; + try { + const response = await axios.post( + `${this.analyticsService}/analytics/search/retire`, + options, + { responseType: 'json' } + ); + result.data = response.data; + + } catch (error) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Indexer service is not available'; + } else { + result.error = error.message; + } + } + break; + } + + case WorkerTaskType.ANALYTICS_GET_INDEXER_AVAILABILITY: { + try { + const response = await axios.get( + `${this.analyticsService}/analytics/checkAvailability` + ); + result.data = response.data; + } catch (error) { + if (error.code === 'ECONNREFUSED') { + result.error = 'Indexer service is not available'; + } else { + result.error = error.message; + } + } + break; + } + case WorkerTaskType.HTTP_REQUEST: { const { method, url, headers, body } = task.data.payload; const response = await axios({