diff --git a/Methodology Library/International Renewable Energy Certificate (iREC)/Policies/iRec Policy 4 (Additional Data).policy b/Methodology Library/International Renewable Energy Certificate (iREC)/Policies/iRec Policy 4 (Additional Data).policy
new file mode 100644
index 0000000000..4048b743a0
Binary files /dev/null and b/Methodology Library/International Renewable Energy Certificate (iREC)/Policies/iRec Policy 4 (Additional Data).policy differ
diff --git a/common/src/hedera-modules/vcjs/vcjs.ts b/common/src/hedera-modules/vcjs/vcjs.ts
index f44dd030f0..cd2ed05ec3 100644
--- a/common/src/hedera-modules/vcjs/vcjs.ts
+++ b/common/src/hedera-modules/vcjs/vcjs.ts
@@ -80,6 +80,14 @@ export interface IDocumentOptions {
* UUID
*/
uuid?: string;
+ /**
+ * Evidence entries to embed in the VC before signing
+ */
+ evidence?: { type: string[]; dataType: string; data: string }[];
+ /**
+ * JSON-LD context URL for evidence entries
+ */
+ evidenceContext?: string;
}
/**
@@ -680,6 +688,14 @@ export class VCJS {
} else {
vc.setIssuer(new Issuer(didDocument.getDid()));
}
+ if (documentOptions?.evidenceContext) {
+ vc.addContext(documentOptions.evidenceContext);
+ }
+ if (documentOptions?.evidence?.length) {
+ for (const entry of documentOptions.evidence) {
+ vc.addEvidence(entry);
+ }
+ }
return await this.issueVerifiableCredential(vc, didDocument, signatureType, documentOptions);
}
diff --git a/frontend/src/app/modules/common/policy-comments/_toolbar-files.scss b/frontend/src/app/modules/common/policy-comments/_toolbar-files.scss
new file mode 100644
index 0000000000..4e98c047d8
--- /dev/null
+++ b/frontend/src/app/modules/common/policy-comments/_toolbar-files.scss
@@ -0,0 +1,100 @@
+.toolbar-files {
+ margin-top: 8px;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 6px;
+
+ .toolbar-file {
+ height: 32px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ border: 1px solid var(--primary-primary, #4169E2);
+ background: #f0f3fc;
+ border-radius: 6px;
+ font-family: Inter, sans-serif;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 14px;
+ vertical-align: middle;
+ overflow: hidden;
+
+ .toolbar-file-icon {
+ height: 30px;
+ width: 30px;
+ min-width: 30px;
+ padding: 7px;
+ overflow: hidden;
+ }
+
+ .toolbar-file-loading,
+ .guardian-loading {
+ position: relative !important;
+ width: 30px !important;
+ height: 30px !important;
+ padding: 6px !important;
+ background: transparent !important;
+
+ .guardian-loading-image {
+ width: 20px !important;
+ height: 20px !important;
+ }
+ }
+
+ .toolbar-file-metadata {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ .file-name {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ height: 30px;
+ line-height: 30px;
+ }
+ }
+
+ .toolbar-file-name {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 0 8px;
+ line-height: 30px;
+ }
+
+ .toolbar-file-action {
+ height: 30px;
+ width: 30px;
+ min-width: 30px;
+ padding: 7px;
+ background: #f0f3fc;
+ cursor: pointer;
+
+ &:hover {
+ background: #dce0ea;
+ }
+ }
+ }
+
+ .toolbar-file-more {
+ border: 1px solid var(--primary-primary, #4169E2);
+ height: 32px;
+ width: 32px;
+ border-radius: 8px;
+ font-family: Inter, sans-serif;
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 14px;
+ vertical-align: middle;
+ color: var(--primary-primary, #4169E2);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ user-select: none;
+ }
+}
diff --git a/frontend/src/app/modules/policy-engine/policy-configuration/blocks/documents/request-addon-config/request-addon-config.component.html b/frontend/src/app/modules/policy-engine/policy-configuration/blocks/documents/request-addon-config/request-addon-config.component.html
index 0c00f890fe..99e846f7fa 100644
--- a/frontend/src/app/modules/policy-engine/policy-configuration/blocks/documents/request-addon-config/request-addon-config.component.html
+++ b/frontend/src/app/modules/policy-engine/policy-configuration/blocks/documents/request-addon-config/request-addon-config.component.html
@@ -83,7 +83,25 @@
+ |
+ Test
-
+
-
-
- 2
- Configure Relayer Account
-
-
+
+
+
+ 2
+ Add Evidence Attachments
+
+
+
+
+
+ {{enableAdditionalData ? 3 : 2}}
+ Configure Relayer Account
+
+
@@ -100,7 +104,32 @@
-
+
+
-
+
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.scss
index 19db3efbd8..ad2372cf56 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.scss
@@ -290,6 +290,24 @@ form {
padding: 1px 16px;
}
+.evidence-container {
+ @import '../../../../../common/policy-comments/toolbar-files';
+
+ .toolbar-files {
+ margin-top: 12px;
+ flex-direction: column;
+
+ .toolbar-file {
+ display: flex;
+ width: 100%;
+ }
+ }
+
+ .toolbar-actions {
+ margin-top: 8px;
+ }
+}
+
.account-warning {
display: flex;
flex-direction: column;
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts
index 302f5e7a88..085f48534a 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/dialog/request-document-block-dialog.component.ts
@@ -16,6 +16,8 @@ import { DocumentAutosaveStorage } from 'src/app/modules/policy-engine/structure
import { TablePersistenceService } from 'src/app/services/table-persistence.service';
import { autosaveValueChanged, getMinutesAgoStream } from 'src/app/utils/autosave-utils';
import { RelayerAccountsService } from 'src/app/services/relayer-accounts.service';
+import { AttachedFile } from 'src/app/modules/common/policy-comments/attached-file';
+import { IPFSService } from 'src/app/services/ipfs.service';
@Component({
selector: 'request-document-block-dialog',
@@ -60,6 +62,19 @@ export class RequestDocumentBlockDialog {
public relayerAccounts: any[] = [];
public remoteWarning: boolean = false;
+ public enableAdditionalData: boolean = false;
+ public evidenceText: string = '';
+ public evidenceFiles: AttachedFile[] = [];
+ private _evidenceFileMap = new WeakMap ();
+
+ public get isEvidenceUploading(): boolean {
+ return this.evidenceFiles.some(f => !f.loaded && !f.error);
+ }
+
+ public get evidenceStepIndex(): number {
+ return 1;
+ }
+
public minutesAgo$ = getMinutesAgoStream(() => this.lastSavedAt);
private buttonNames: { [id: string]: string } = {
save: 'Save Draft',
@@ -99,12 +114,14 @@ export class RequestDocumentBlockDialog {
private changeDetectorRef: ChangeDetectorRef,
private indexedDb: IndexedDbRegistryService,
private tablePersist: TablePersistenceService,
+ private ipfsService: IPFSService,
) {
this.parent = this.config.data;
this.dataForm = this.fb.group({});
this.storage = new DocumentAutosaveStorage(indexedDb);
if (this.parent) {
this.parent.dialog = this;
+ this.enableAdditionalData = !!(this.parent as any).enableAdditionalData;
}
}
@@ -207,6 +224,64 @@ export class RequestDocumentBlockDialog {
}
}
+ public onEvidenceDrop($event: DragEvent) {
+ $event.preventDefault();
+ const files = $event.dataTransfer?.files;
+ if (files?.length) {
+ this.addEvidenceFiles(Array.from(files));
+ }
+ }
+
+ public onEvidenceAttach($event: any) {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.onchange = (e: any) => {
+ const files: File[] = Array.from(e.target.files || []);
+ if (files.length) {
+ this.addEvidenceFiles(files);
+ }
+ };
+ input.click();
+ }
+
+ public onDeleteEvidenceFile(file: AttachedFile) {
+ const index = this.evidenceFiles.indexOf(file);
+ if (index !== -1) {
+ this.evidenceFiles.splice(index, 1);
+ this._evidenceFileMap.delete(file);
+ }
+ }
+
+ private addEvidenceFiles(files: File[]) {
+ for (const rawFile of files) {
+ if (this.evidenceFiles.some(f => this._evidenceFileMap.get(f)?.name === rawFile.name)) {
+ continue;
+ }
+ const af = AttachedFile.fromFile('', '', '', rawFile);
+ this._evidenceFileMap.set(af, rawFile);
+ this.evidenceFiles.push(af);
+ this.ipfsService.addFile(rawFile).subscribe((cid: string) => {
+ af.cid = cid;
+ af.link = 'ipfs://' + cid;
+ af.loaded = true;
+ }, () => {
+ af.error = true;
+ });
+ }
+ }
+
+ private buildEvidence(): Array<{ dataType: 'message' | 'file'; data: string }> {
+ const entries: Array<{ dataType: 'message' | 'file'; data: string }> = [];
+ if (this.evidenceText?.trim()) {
+ entries.push({ dataType: 'message', data: this.evidenceText.trim() });
+ }
+ for (const file of this.evidenceFiles.filter(f => f.loaded && !f.error)) {
+ entries.push({ dataType: 'file', data: file.link });
+ }
+ return entries;
+ }
+
private async onSubmit(draft?: boolean) {
const data = this.dataForm.getRawValue();
this.loading = true;
@@ -217,13 +292,16 @@ export class RequestDocumentBlockDialog {
const draftId = this.parent instanceof RequestDocumentBlockComponent ? this.parent.draftId : null;
this.storage.delete(this.autosaveId);
+ const evidence = this.enableAdditionalData ? this.buildEvidence() : undefined;
+
this.policyEngineService
.setBlockData(this.id, this.policyId, {
document: data,
ref: this.docRef,
draft: draft,
draftId: draftId,
- relayerAccount: this.getRelayerAccount()
+ relayerAccount: this.getRelayerAccount(),
+ ...(evidence?.length ? { evidence } : {})
})
.subscribe(() => {
setTimeout(() => {
@@ -245,13 +323,14 @@ export class RequestDocumentBlockDialog {
return;
}
if (this.dataForm.valid || draft) {
- if (this.relayerAccount) {
- if (this.isStep(0)) {
- this.setStep(1);
- this.loadRelayerAccounts();
- } else {
- await this.onSubmit(draft);
- }
+ if (this.enableAdditionalData && this.isStep(0)) {
+ this.setStep(1);
+ } else if (this.enableAdditionalData && this.isStep(1) && this.relayerAccount) {
+ this.setStep(2);
+ this.loadRelayerAccounts();
+ } else if (!this.enableAdditionalData && this.relayerAccount && this.isStep(0)) {
+ this.setStep(1);
+ this.loadRelayerAccounts();
} else {
await this.onSubmit(draft);
}
@@ -310,11 +389,11 @@ export class RequestDocumentBlockDialog {
}
public getButtonName(item: any) {
- if (this.relayerAccount && item.id === 'submit') {
- if (this.isStep(0)) {
+ if (item.id === 'submit') {
+ if (this.enableAdditionalData && this.isStep(0)) {
+ return 'Add Evidence Attachments';
+ } else if (this.relayerAccount && (this.isStep(this.enableAdditionalData ? 1 : 0))) {
return this.buttonNames['relayerAccount'];
- } else {
- return this.buttonNames['submit'];
}
}
return this.buttonNames[item.id] || item.text;
@@ -333,7 +412,8 @@ export class RequestDocumentBlockDialog {
public ifRelayerAccountDisabled() {
if (this.relayerAccount) {
- if (this.isStep(1)) {
+ const relayerStep = this.enableAdditionalData ? 2 : 1;
+ if (this.isStep(relayerStep)) {
if (this.relayerAccountType === 'account') {
return false;
} else if (this.relayerAccountType === 'relayerAccount') {
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html
index 2ffc259312..ba0a82ed3c 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.html
@@ -55,23 +55,27 @@ {{ title }}
-
+
-
-
- 2
- Configure Relayer Account
-
-
+
+
+
+ 2
+ Add Evidence Attachments
+
+
+
+
+
+ {{enableAdditionalData ? 3 : 2}}
+ Configure Relayer Account
+
+
@@ -103,7 +107,36 @@ {{ title }}
>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ title }}
-
-
-
+
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
index 9c54b436ed..04805b8fbf 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
@@ -586,7 +586,27 @@
padding: 1px 16px;
}
+.evidence-container {
+ @import '../../../../common/policy-comments/toolbar-files';
+ .toolbar-files {
+ margin-top: 12px;
+ flex-direction: column;
+
+ .toolbar-file {
+ display: flex;
+ width: 100%;
+ }
+ }
+
+ .toolbar-actions {
+ margin-top: 8px;
+ }
+}
+
+.evidence-buttons {
+ gap: 8px;
+}
.account-warning {
display: flex;
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts
index e6b49d74c9..9d5cc1b32e 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.ts
@@ -21,6 +21,8 @@ import { IndexedDbRegistryService } from 'src/app/services/indexed-db-registry.s
import { TablePersistenceService } from 'src/app/services/table-persistence.service';
import { PolicyStatus } from '@guardian/interfaces';
import { RelayerAccountsService } from 'src/app/services/relayer-accounts.service';
+import { AttachedFile } from 'src/app/modules/common/policy-comments/attached-file';
+import { IPFSService } from 'src/app/services/ipfs.service';
interface IRequestDocumentData {
readonly: boolean;
@@ -31,6 +33,7 @@ interface IRequestDocumentData {
restoreData: any;
data: any;
relayerAccount: boolean;
+ enableAdditionalData: boolean;
draft: boolean;
editType: 'new' | 'edit';
uiMetaData: {
@@ -110,6 +113,18 @@ export class RequestDocumentBlockComponent
public submitText: string = 'Validate & Create';
public isLocalUser: boolean = true;
public remoteWarning: boolean = false;
+ public enableAdditionalData: boolean = false;
+ public evidenceText: string = '';
+ public evidenceFiles: AttachedFile[] = [];
+ private _evidenceFileMap = new WeakMap ();
+
+ public get isEvidenceUploading(): boolean {
+ return this.evidenceFiles.some(f => !f.loaded && !f.error);
+ }
+
+ public get evidenceStepIndex(): number {
+ return 1;
+ }
public get needRemoteWarning () {
return !this.isLocalUser && this.relayerAccountType !== 'account';
@@ -134,6 +149,7 @@ export class RequestDocumentBlockComponent
private savepointFlow: SavepointFlowService,
private indexedDb: IndexedDbRegistryService,
private tablePersist: TablePersistenceService,
+ private ipfsService: IPFSService,
) {
super(policyEngineService, profile, wsService);
this.dataForm = this.fb.group({});
@@ -232,6 +248,7 @@ export class RequestDocumentBlockComponent
this.schema = new Schema(schema);
this.hideFields = {};
this.relayerAccount = !!data.relayerAccount && !this.dryRun;
+ this.enableAdditionalData = !!data.enableAdditionalData && !this.dryRun;
this.draft = isDraft;
this.draftId = (isDraft && row) ? row.id : null;
if (uiMetaData.privateFields) {
@@ -275,7 +292,9 @@ export class RequestDocumentBlockComponent
this.disabled = false;
this.isExist = false;
}
- if (this.relayerAccount) {
+ if (this.enableAdditionalData) {
+ this.submitText = 'Add Evidence Attachments';
+ } else if (this.relayerAccount) {
this.submitText = 'Select Relayer Account';
} else {
this.submitText = (this.edit && !this.draft) ? 'Validate & Update' : 'Validate & Create';
@@ -359,13 +378,14 @@ export class RequestDocumentBlockComponent
return;
}
if (this.dataForm.valid || draft) {
- if (this.relayerAccount) {
- if (this.isStep(0)) {
- this.setStep(1);
- this.loadRelayerAccounts();
- } else {
- await this.onSubmit(draft);
- }
+ if (this.enableAdditionalData && this.isStep(0)) {
+ this.setStep(1);
+ } else if (this.enableAdditionalData && this.isStep(1) && this.relayerAccount) {
+ this.setStep(2);
+ this.loadRelayerAccounts();
+ } else if (!this.enableAdditionalData && this.relayerAccount && this.isStep(0)) {
+ this.setStep(1);
+ this.loadRelayerAccounts();
} else {
await this.onSubmit(draft);
}
@@ -397,6 +417,8 @@ export class RequestDocumentBlockComponent
prepareVcData(data);
+ const evidence = this.enableAdditionalData ? this.buildEvidence() : undefined;
+
let requestSucceeded = false;
this.policyEngineService
@@ -405,7 +427,8 @@ export class RequestDocumentBlockComponent
ref: this.ref,
draft,
draftId: this.draftId,
- relayerAccount: this.getRelayerAccount()
+ relayerAccount: this.getRelayerAccount(),
+ ...(evidence?.length ? { evidence } : {})
})
.pipe(
finalize(async () => {
@@ -440,6 +463,64 @@ export class RequestDocumentBlockComponent
}
}
+ public onEvidenceDrop($event: DragEvent) {
+ $event.preventDefault();
+ const files = $event.dataTransfer?.files;
+ if (files?.length) {
+ this.addEvidenceFiles(Array.from(files));
+ }
+ }
+
+ public onEvidenceAttach($event: any) {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.onchange = (e: any) => {
+ const files: File[] = Array.from(e.target.files || []);
+ if (files.length) {
+ this.addEvidenceFiles(files);
+ }
+ };
+ input.click();
+ }
+
+ public onDeleteEvidenceFile(file: AttachedFile) {
+ const index = this.evidenceFiles.indexOf(file);
+ if (index !== -1) {
+ this.evidenceFiles.splice(index, 1);
+ this._evidenceFileMap.delete(file);
+ }
+ }
+
+ private addEvidenceFiles(files: File[]) {
+ for (const rawFile of files) {
+ if (this.evidenceFiles.some(f => this._evidenceFileMap.get(f)?.name === rawFile.name)) {
+ continue;
+ }
+ const af = AttachedFile.fromFile('', '', '', rawFile);
+ this._evidenceFileMap.set(af, rawFile);
+ this.evidenceFiles.push(af);
+ this.ipfsService.addFile(rawFile).subscribe((cid: string) => {
+ af.cid = cid;
+ af.link = 'ipfs://' + cid;
+ af.loaded = true;
+ }, () => {
+ af.error = true;
+ });
+ }
+ }
+
+ private buildEvidence(): Array<{ dataType: 'message' | 'file'; data: string }> {
+ const entries: Array<{ dataType: 'message' | 'file'; data: string }> = [];
+ if (this.evidenceText?.trim()) {
+ entries.push({ dataType: 'message', data: this.evidenceText.trim() });
+ }
+ for (const file of this.evidenceFiles.filter(f => f.loaded && !f.error)) {
+ entries.push({ dataType: 'file', data: file.link });
+ }
+ return entries;
+ }
+
public preset(document: any) {
this.presetDocument = document;
this.changeDetectorRef.detectChanges();
@@ -653,7 +734,8 @@ export class RequestDocumentBlockComponent
public ifRelayerAccountDisabled() {
if (this.relayerAccount) {
- if (this.isStep(1)) {
+ const relayerStep = this.enableAdditionalData ? 2 : 1;
+ if (this.isStep(relayerStep)) {
if (this.relayerAccountType === 'account') {
return false;
} else if (this.relayerAccountType === 'relayerAccount') {
diff --git a/frontend/src/app/modules/schema-engine/document-view/document-view.component.html b/frontend/src/app/modules/schema-engine/document-view/document-view.component.html
index dfb0143d02..d3e8395a71 100644
--- a/frontend/src/app/modules/schema-engine/document-view/document-view.component.html
+++ b/frontend/src/app/modules/schema-engine/document-view/document-view.component.html
@@ -102,16 +102,32 @@
diff --git a/frontend/src/app/modules/schema-engine/document-view/document-view.component.scss b/frontend/src/app/modules/schema-engine/document-view/document-view.component.scss
index dcbed1bc2c..2317557c69 100644
--- a/frontend/src/app/modules/schema-engine/document-view/document-view.component.scss
+++ b/frontend/src/app/modules/schema-engine/document-view/document-view.component.scss
@@ -190,6 +190,49 @@ form {
}
}
+.evidence-entries {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-top: 8px;
+
+ .evidence-entry {
+ display: flex;
+ align-items: flex-start;
+ padding: 8px 12px;
+ background: #F3F6FB;
+ border: 1px solid #E1E7EF;
+ border-radius: 6px;
+ }
+
+ .evidence-entry-message,
+ .evidence-entry-file {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ font-family: Inter;
+ font-size: 13px;
+ line-height: 18px;
+ color: #23252E;
+ word-break: break-word;
+
+ svg-icon {
+ flex-shrink: 0;
+ position: relative;
+ top: 1px;
+ }
+
+ a {
+ color: #4169E1;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+}
+
*[readonly] {
opacity: 0.6 !important;
cursor: not-allowed !important;
diff --git a/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts b/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts
index e184b3318d..a27af7ac18 100644
--- a/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts
+++ b/frontend/src/app/modules/schema-engine/document-view/document-view.component.ts
@@ -52,6 +52,8 @@ export class DocumentViewComponent implements OnInit {
public subjects: any[] = [];
public proofJson!: string;
public evidenceJson!: string;
+ public evidenceEntries: { dataType: string; data: string }[] = [];
+ public hasStructuredEvidence: boolean = false;
public pageIndex: number = 0;
public pageSize: number = 5;
public schemaMap: { [x: string]: Schema | null } = {};
@@ -85,6 +87,7 @@ export class DocumentViewComponent implements OnInit {
this.issuerOptions = [];
this.proofJson = this.document.proof ? JSON.stringify(this.document.proof, null, 4) : '';
this.evidenceJson = this.document.evidence ? JSON.stringify(this.document.evidence, null, 4) : '';
+ this.parseEvidenceEntries();
this.isIssuerObject = typeof this.document.issuer === 'object';
if (this.isIssuerObject) {
for (const key in this.document.issuer) {
@@ -131,6 +134,26 @@ export class DocumentViewComponent implements OnInit {
document.body.classList.remove('resizing');
}
+ private parseEvidenceEntries(): void {
+ const evidence = this.document?.evidence;
+ if (!Array.isArray(evidence) || evidence.length === 0) {
+ this.hasStructuredEvidence = false;
+ return;
+ }
+ const isStructured = evidence.every(
+ (e: any) => e && typeof e.dataType === 'string' && typeof e.data === 'string'
+ );
+ if (isStructured) {
+ this.hasStructuredEvidence = true;
+ this.evidenceEntries = evidence.map((e: any) => ({
+ dataType: e.dataType,
+ data: e.data,
+ }));
+ } else {
+ this.hasStructuredEvidence = false;
+ }
+ }
+
private loadData() {
const requests: any = {};
diff --git a/guardian-service/src/api/helpers/default-schemas.ts b/guardian-service/src/api/helpers/default-schemas.ts
index f2de2c3309..b0380cd1a4 100644
--- a/guardian-service/src/api/helpers/default-schemas.ts
+++ b/guardian-service/src/api/helpers/default-schemas.ts
@@ -66,6 +66,10 @@ export async function setDefaultSchema() {
throw new Error(`You need to fill ${SchemaEntity.POLICY_EXPORT_PROOF} field in system-schemas.json file`);
}
+ if (!map.hasOwnProperty(SchemaEntity.EVIDENCE_ATTACHMENTS)) {
+ throw new Error(`You need to fill ${SchemaEntity.EVIDENCE_ATTACHMENTS} field in system-schemas.json file`);
+ }
+
const fn = async (schema: any) => {
const existingSchemas = await DatabaseServer.getSchema({
uuid: schema.uuid,
@@ -101,4 +105,5 @@ export async function setDefaultSchema() {
await fn(map[SchemaEntity.POLICY_DISCUSSION]);
await fn(map[SchemaEntity.POLICY_COMMENT]);
await fn(map[SchemaEntity.POLICY_EXPORT_PROOF]);
+ await fn(map[SchemaEntity.EVIDENCE_ATTACHMENTS]);
}
diff --git a/guardian-service/src/helpers/import-helpers/policy/policy-import-helper.ts b/guardian-service/src/helpers/import-helpers/policy/policy-import-helper.ts
index ecf5da3e23..864bca55cd 100644
--- a/guardian-service/src/helpers/import-helpers/policy/policy-import-helper.ts
+++ b/guardian-service/src/helpers/import-helpers/policy/policy-import-helper.ts
@@ -48,7 +48,8 @@ export class PolicyImportExportHelper {
DatabaseServer.getSystemSchema(SchemaEntity.TOKEN_DATA_SOURCE),
DatabaseServer.getSystemSchema(SchemaEntity.POLICY_COMMENT),
DatabaseServer.getSystemSchema(SchemaEntity.POLICY_DISCUSSION),
- DatabaseServer.getSystemSchema(SchemaEntity.POLICY_EXPORT_PROOF)
+ DatabaseServer.getSystemSchema(SchemaEntity.POLICY_EXPORT_PROOF),
+ DatabaseServer.getSystemSchema(SchemaEntity.EVIDENCE_ATTACHMENTS)
]);
for (const schema of schemas) {
diff --git a/guardian-service/system-schemas/system-schemas.json b/guardian-service/system-schemas/system-schemas.json
index 715d3d1cd5..555b4443c0 100644
--- a/guardian-service/system-schemas/system-schemas.json
+++ b/guardian-service/system-schemas/system-schemas.json
@@ -1609,6 +1609,36 @@
],
"additionalProperties": false
}
+ },
+ {
+ "uuid": "EvidenceAttachments",
+ "name": "EvidenceAttachments",
+ "entity": "EVIDENCE_ATTACHMENTS",
+ "document": {
+ "$id": "#EvidenceAttachments",
+ "$comment": "{ \"@id\": \"#EvidenceAttachments\", \"term\": \"EvidenceAttachments\" }",
+ "title": "EvidenceAttachments",
+ "description": "EvidenceAttachments",
+ "type": "object",
+ "properties": {
+ "dataType": {
+ "title": "dataType",
+ "description": "dataType",
+ "type": "string",
+ "$comment": "{\"term\": \"dataType\", \"@id\": \"https://www.schema.org/text\"}",
+ "readOnly": false
+ },
+ "data": {
+ "title": "data",
+ "description": "data",
+ "type": "string",
+ "$comment": "{\"term\": \"data\", \"@id\": \"https://www.schema.org/text\"}",
+ "readOnly": false
+ }
+ },
+ "required": ["dataType", "data"],
+ "additionalProperties": false
+ }
}
-]
\ No newline at end of file
+]
diff --git a/interfaces/src/type/schema-entity.type.ts b/interfaces/src/type/schema-entity.type.ts
index 40611589eb..98a2574bfb 100644
--- a/interfaces/src/type/schema-entity.type.ts
+++ b/interfaces/src/type/schema-entity.type.ts
@@ -22,5 +22,6 @@ export enum SchemaEntity {
USER_PERMISSIONS = 'USER_PERMISSIONS',
POLICY_DISCUSSION = 'POLICY_DISCUSSION_V2',
POLICY_COMMENT = 'POLICY_COMMENT',
- POLICY_EXPORT_PROOF = 'POLICY_EXPORT_PROOF'
+ POLICY_EXPORT_PROOF = 'POLICY_EXPORT_PROOF',
+ EVIDENCE_ATTACHMENTS = 'EVIDENCE_ATTACHMENTS'
}
diff --git a/policy-service/src/policy-engine/blocks/reassigning.block.ts b/policy-service/src/policy-engine/blocks/reassigning.block.ts
index 076ae67785..bd0a30604e 100644
--- a/policy-service/src/policy-engine/blocks/reassigning.block.ts
+++ b/policy-service/src/policy-engine/blocks/reassigning.block.ts
@@ -9,7 +9,7 @@ import { PolicyUser } from '../policy-user.js';
import { PolicyUtils } from '../helpers/utils.js';
import { ExternalDocuments, ExternalEvent, ExternalEventType } from '../interfaces/external-event.js';
import { Inject } from '../../helpers/decorators/inject.js';
-import { LocationType } from '@guardian/interfaces';
+import { LocationType, SchemaEntity } from '@guardian/interfaces';
import { PolicyActionsUtils } from '../policy-actions/utils.js';
import { RecordActionStep } from '../record-action-step.js';
@@ -94,12 +94,22 @@ export class ReassigningBlock {
const uuid = await ref.components.generateUUID(actionStatus?.id);
const credentialSubject = document.document.credentialSubject[0];
+ const originalEvidence = document.document.evidence;
+ let evidenceOptions: { evidence?: any[]; evidenceContext?: string } = {};
+ if (originalEvidence?.length) {
+ const evidenceSchema = await PolicyUtils.loadSchemaByType(ref, SchemaEntity.EVIDENCE_ATTACHMENTS);
+ const evidenceContext = PolicyUtils.getSchemaContext(ref, evidenceSchema);
+ evidenceOptions = {
+ evidence: (Array.isArray(originalEvidence) ? originalEvidence : [originalEvidence]),
+ evidenceContext,
+ };
+ }
const vc = await PolicyActionsUtils.signVC({
ref,
subject: credentialSubject,
issuer,
relayerAccount,
- options: { uuid, group: groupContext },
+ options: { uuid, group: groupContext, ...evidenceOptions },
userId: user.userId
});
diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts
index 7dd1e71290..a4e9a0bb4c 100644
--- a/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts
+++ b/policy-service/src/policy-engine/blocks/request-vc-document-block-addon.ts
@@ -22,6 +22,7 @@ import {
CheckResult,
removeObjectProperties,
LocationType,
+ SchemaEntity,
} from '@guardian/interfaces';
import { BlockActionError } from '../errors/block-action-error.js';
import { PolicyUtils } from '../helpers/utils.js';
@@ -259,12 +260,22 @@ export class RequestVcDocumentBlockAddon {
const groupContext = await PolicyUtils.getGroupContext(ref, user);
const uuid = await ref.components.generateUUID(actionStatus?.id);
+ let evidenceOptions: { evidence?: { type: string[]; dataType: string; data: string }[]; evidenceContext?: string } = {};
+ if (ref.options.enableAdditionalData && _data.evidence?.length) {
+ const evidenceSchema = await PolicyUtils.loadSchemaByType(ref, SchemaEntity.EVIDENCE_ATTACHMENTS);
+ const evidenceContext = PolicyUtils.getSchemaContext(ref, evidenceSchema);
+ evidenceOptions = {
+ evidence: _data.evidence.map((e: any) => ({ type: ['Evidence'], dataType: e.dataType, data: e.data })),
+ evidenceContext,
+ };
+ }
+
const vc = await PolicyActionsUtils.signVC({
ref,
subject: credentialSubject,
issuer: user.did,
relayerAccount,
- options: { uuid, group: groupContext },
+ options: { uuid, group: groupContext, ...evidenceOptions },
userId: user.userId
});
let item = PolicyUtils.createVC(ref, documentOwner, vc, actionStatus?.id);
diff --git a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts
index b7499a7e31..ba4f690d9b 100644
--- a/policy-service/src/policy-engine/blocks/request-vc-document-block.ts
+++ b/policy-service/src/policy-engine/blocks/request-vc-document-block.ts
@@ -1,4 +1,4 @@
-import { CheckResult, LocationType, removeObjectProperties, Schema, SchemaHelper } from '@guardian/interfaces';
+import { CheckResult, LocationType, removeObjectProperties, Schema, SchemaEntity, SchemaHelper } from '@guardian/interfaces';
import { PolicyUtils } from '../helpers/utils.js';
import { BlockActionError } from '../errors/index.js';
import { ActionCallback, StateField } from '../helpers/decorators/index.js';
@@ -150,6 +150,7 @@ export class RequestVcDocumentBlock {
presetFields: options.presetFields,
editType: options.editType || 'new',
relayerAccount: !!options.relayerAccount,
+ enableAdditionalData: !!options.enableAdditionalData,
uiMetaData: options.uiMetaData || {},
hideFields: options.hideFields || [],
data: sources && sources.length && sources[0] || null,
@@ -239,7 +240,7 @@ export class RequestVcDocumentBlock {
}
//Create Verifiable Credential
- const item = await this.createVerifiableCredential(user, documentOwner, relayerAccount, credentialSubject, actionStatus?.id);
+ const item = await this.createVerifiableCredential(user, documentOwner, relayerAccount, credentialSubject, actionStatus?.id, data.evidence);
PolicyUtils.setDocumentRef(item, documentRef);
//Update metadata
@@ -432,21 +433,33 @@ export class RequestVcDocumentBlock {
owner: PolicyUser,
relayerAccount: string,
credentialSubject: any,
- actionStatusId: string
+ actionStatusId: string,
+ evidence?: { dataType: string; data: string }[]
): Promise {
const ref = PolicyComponentsUtils.GetBlockRef(this);
const groupContext = await PolicyUtils.getGroupContext(ref, issuer);
const uuid = await ref.components.generateUUID(actionStatusId);
+ let evidenceOptions: { evidence?: { type: string[]; dataType: string; data: string }[]; evidenceContext?: string } = {};
+ if (ref.options.enableAdditionalData && evidence?.length) {
+ const evidenceSchema = await PolicyUtils.loadSchemaByType(ref, SchemaEntity.EVIDENCE_ATTACHMENTS);
+ const evidenceContext = PolicyUtils.getSchemaContext(ref, evidenceSchema);
+ evidenceOptions = {
+ evidence: evidence.map(e => ({ type: ['Evidence'], dataType: e.dataType, data: e.data })),
+ evidenceContext,
+ };
+ }
+
const vc = await PolicyActionsUtils.signVC({
ref,
subject: credentialSubject,
issuer: issuer.did,
relayerAccount,
- options: { uuid, group: groupContext },
+ options: { uuid, group: groupContext, ...evidenceOptions },
userId: issuer.userId
});
+
const item = PolicyUtils.createVC(ref, owner, vc, actionStatusId);
const tags = await PolicyUtils.getBlockTags(ref);
diff --git a/policy-service/src/policy-engine/policy-engine.interface.ts b/policy-service/src/policy-engine/policy-engine.interface.ts
index f4c4691f92..58719fc960 100644
--- a/policy-service/src/policy-engine/policy-engine.interface.ts
+++ b/policy-service/src/policy-engine/policy-engine.interface.ts
@@ -1054,6 +1054,10 @@ export interface IPolicyDocument extends IPolicyDBDocument {
* sourceTag
*/
__sourceTag__?: string;
+ /**
+ * Evidence entries from additional data step
+ */
+ evidence?: { dataType: string; data: string }[];
}
/**
|