diff --git a/apps/console/src/__generated__/core/CompliancePageDocumentListItem_updateVisibilityMutation.graphql.ts b/apps/console/src/__generated__/core/CompliancePageDocumentListItem_updateVisibilityMutation.graphql.ts index c3bb3b8d2..a41104e4c 100644 --- a/apps/console/src/__generated__/core/CompliancePageDocumentListItem_updateVisibilityMutation.graphql.ts +++ b/apps/console/src/__generated__/core/CompliancePageDocumentListItem_updateVisibilityMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<91bce548869c16de31b1edc3445874d2>> + * @generated SignedSource<<0948c131a30705ee3921f60bbd40c3cb>> * @lightSyntaxTransform * @nogrep */ @@ -14,7 +14,7 @@ export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "S export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type UpdateDocumentInput = { - approverId?: string | null | undefined; + approverIds?: ReadonlyArray | null | undefined; classification?: DocumentClassification | null | undefined; content?: string | null | undefined; documentType?: DocumentType | null | undefined; diff --git a/apps/console/src/__generated__/core/CreateDocumentDialogMutation.graphql.ts b/apps/console/src/__generated__/core/CreateDocumentDialogMutation.graphql.ts index 40b93a145..1eef28884 100644 --- a/apps/console/src/__generated__/core/CreateDocumentDialogMutation.graphql.ts +++ b/apps/console/src/__generated__/core/CreateDocumentDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<4aabb7a5e515082b00152627b1f7c2f1>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -14,7 +14,7 @@ export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "S export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type CreateDocumentInput = { - approverId: string; + approverIds: ReadonlyArray; classification: DocumentClassification; content: string; documentType: DocumentType; @@ -210,22 +210,50 @@ return { }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v3/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" }, { "alias": "lastVersion", @@ -358,12 +386,12 @@ return { ] }, "params": { - "cacheID": "1ec326debfa93388837c2779cb693dca", + "cacheID": "927e922143fa7eac4270e7ac0ae7da39", "id": null, "metadata": {}, "name": "CreateDocumentDialogMutation", "operationKind": "mutation", - "text": "mutation CreateDocumentDialogMutation(\n $input: CreateDocumentInput!\n) {\n createDocument(input: $input) {\n documentEdge {\n node {\n id\n ...DocumentListItemFragment\n }\n }\n }\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approver {\n id\n fullName\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" + "text": "mutation CreateDocumentDialogMutation(\n $input: CreateDocumentInput!\n) {\n createDocument(input: $input) {\n documentEdge {\n node {\n id\n ...DocumentListItemFragment\n }\n }\n }\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts index 75f835e5a..6df7f71c0 100644 --- a/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutDrawerMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<5cb0be6e5ab775bdf7d509d9789e9821>> + * @generated SignedSource<<54fdf6ba6490381a269cd77379fe1810>> * @lightSyntaxTransform * @nogrep */ @@ -13,7 +13,7 @@ export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "S export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type UpdateDocumentInput = { - approverId?: string | null | undefined; + approverIds?: ReadonlyArray | null | undefined; classification?: DocumentClassification | null | undefined; content?: string | null | undefined; documentType?: DocumentType | null | undefined; @@ -27,9 +27,13 @@ export type DocumentLayoutDrawerMutation$variables = { export type DocumentLayoutDrawerMutation$data = { readonly updateDocument: { readonly document: { - readonly approver: { - readonly fullName: string; - readonly id: string; + readonly approvers: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly fullName: string; + readonly id: string; + }; + }>; }; readonly classification: DocumentClassification; readonly documentType: DocumentType; @@ -97,22 +101,50 @@ v2 = [ }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v1/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" } ], "storageKey": null @@ -139,16 +171,16 @@ return { "selections": (v2/*: any*/) }, "params": { - "cacheID": "717ce480e61acba39409ff42ee5344cb", + "cacheID": "e92e7774c449389137a3e5fb1b037e8a", "id": null, "metadata": {}, "name": "DocumentLayoutDrawerMutation", "operationKind": "mutation", - "text": "mutation DocumentLayoutDrawerMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n documentType\n classification\n approver {\n id\n fullName\n }\n }\n }\n}\n" + "text": "mutation DocumentLayoutDrawerMutation(\n $input: UpdateDocumentInput!\n) {\n updateDocument(input: $input) {\n document {\n id\n documentType\n classification\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "815cb617b0685f018afe95e97c910771"; +(node as any).hash = "6e9b5aa2c9ba01c18bf26c514c3fbcd3"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentLayoutDrawer_documentFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutDrawer_documentFragment.graphql.ts index a6ea15ad0..dcc31ca64 100644 --- a/apps/console/src/__generated__/core/DocumentLayoutDrawer_documentFragment.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutDrawer_documentFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<547f6c8756b51ff46a73158262f78e8e>> * @lightSyntaxTransform * @nogrep */ @@ -12,6 +12,14 @@ import { ReaderFragment } from 'relay-runtime'; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; import { FragmentRefs } from "relay-runtime"; export type DocumentLayoutDrawer_documentFragment$data = { + readonly approvers: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly fullName: string; + readonly id: string; + }; + }>; + }; readonly canUpdate: boolean; readonly documentType: DocumentType; readonly id: string; @@ -22,19 +30,21 @@ export type DocumentLayoutDrawer_documentFragment$key = { readonly " $fragmentSpreads": FragmentRefs<"DocumentLayoutDrawer_documentFragment">; }; -const node: ReaderFragment = { +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "DocumentLayoutDrawer_documentFragment", "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null - }, + (v0/*: any*/), { "alias": null, "args": null, @@ -54,12 +64,60 @@ const node: ReaderFragment = { "kind": "ScalarField", "name": "permission", "storageKey": "permission(action:\"core:document:update\")" + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", + "kind": "LinkedField", + "name": "approvers", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "approvers(first:100)" } ], "type": "Document", "abstractKey": null }; +})(); -(node as any).hash = "0f2e539cf30c4e116fe81392605b1481"; +(node as any).hash = "ffc32318ed0df6b2992eca91bef297e5"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentLayoutDrawer_versionFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutDrawer_versionFragment.graphql.ts index da901dda2..3124b8fd5 100644 --- a/apps/console/src/__generated__/core/DocumentLayoutDrawer_versionFragment.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutDrawer_versionFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<7464a4e3e5b2b5b72a6ecda47b5dace7>> * @lightSyntaxTransform * @nogrep */ @@ -13,10 +13,6 @@ export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "S export type DocumentStatus = "DRAFT" | "PUBLISHED"; import { FragmentRefs } from "relay-runtime"; export type DocumentLayoutDrawer_versionFragment$data = { - readonly approver: { - readonly fullName: string; - readonly id: string; - }; readonly classification: DocumentClassification; readonly id: string; readonly publishedAt: string | null | undefined; @@ -30,45 +26,24 @@ export type DocumentLayoutDrawer_versionFragment$key = { readonly " $fragmentSpreads": FragmentRefs<"DocumentLayoutDrawer_versionFragment">; }; -const node: ReaderFragment = (function(){ -var v0 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}; -return { +const node: ReaderFragment = { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "DocumentLayoutDrawer_versionFragment", "selections": [ - (v0/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", - "name": "classification", + "name": "id", "storageKey": null }, { "alias": null, "args": null, - "concreteType": "Profile", - "kind": "LinkedField", - "name": "approver", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - } - ], + "kind": "ScalarField", + "name": "classification", "storageKey": null }, { @@ -103,8 +78,7 @@ return { "type": "DocumentVersion", "abstractKey": null }; -})(); -(node as any).hash = "63b956326e98c7620715250f4e7d1129"; +(node as any).hash = "ebe62fad4b4044be35adb9ba4979a45a"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts b/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts index ded84a2f8..666e247d0 100644 --- a/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<41d49c06d7dfad15f6c1d39cd9eddc80>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -275,32 +275,13 @@ v19 = { "storageKey": null }, v20 = { - "alias": null, - "args": null, - "concreteType": "Profile", - "kind": "LinkedField", - "name": "approver", - "plural": false, - "selections": [ - (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - } - ], - "storageKey": null -}, -v21 = { "alias": null, "args": null, "kind": "ScalarField", "name": "updatedAt", "storageKey": null }, -v22 = { +v21 = { "alias": null, "args": null, "kind": "ScalarField", @@ -572,6 +553,53 @@ return { "name": "documentType", "storageKey": null }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", + "kind": "LinkedField", + "name": "approvers", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "approvers(first:100)" + }, { "condition": "versionSpecified", "kind": "Condition", @@ -608,7 +636,6 @@ return { (v19/*: any*/), (v20/*: any*/), (v21/*: any*/), - (v22/*: any*/), (v8/*: any*/), (v9/*: any*/) ], @@ -653,7 +680,6 @@ return { (v19/*: any*/), (v20/*: any*/), (v21/*: any*/), - (v22/*: any*/), (v8/*: any*/), (v9/*: any*/) ], @@ -668,12 +694,12 @@ return { ] }, "params": { - "cacheID": "3e4d94e71ac7cf5c557b5ee59e0917ed", + "cacheID": "4291fe2afc2c86c77cbfd01d05f16e75", "id": null, "metadata": {}, "name": "DocumentLayoutQuery", "operationKind": "query", - "text": "query DocumentLayoutQuery(\n $documentId: ID!\n $versionId: ID!\n $versionSpecified: Boolean!\n) {\n version: node(id: $versionId) @include(if: $versionSpecified) {\n __typename\n ... on DocumentVersion {\n id\n status\n ...DocumentActionsDropdown_versionFragment\n ...DocumentLayoutDrawer_versionFragment\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n id\n }\n document: node(id: $documentId) {\n __typename\n ... on Document {\n id\n title\n canPublish: permission(action: \"core:document-version:publish\")\n controlInfo: controls(first: 0) {\n totalCount\n }\n ...DocumentTitleFormFragment\n ...DocumentActionsDropdown_documentFragment\n ...DocumentLayoutDrawer_documentFragment\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: $versionSpecified) {\n edges {\n node {\n id\n status\n ...DocumentActionsDropdown_versionFragment\n ...DocumentLayoutDrawer_versionFragment\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n }\n id\n }\n}\n\nfragment DocumentActionsDropdown_documentFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n versions(first: 20) {\n totalCount\n }\n ...UpdateVersionDialogFragment\n}\n\nfragment DocumentActionsDropdown_versionFragment on DocumentVersion {\n id\n version\n status\n canDeleteDraft: permission(action: \"core:document-version:delete-draft\")\n}\n\nfragment DocumentLayoutDrawer_documentFragment on Document {\n id\n documentType\n canUpdate: permission(action: \"core:document:update\")\n}\n\nfragment DocumentLayoutDrawer_versionFragment on DocumentVersion {\n id\n classification\n approver {\n id\n fullName\n }\n version\n status\n updatedAt\n publishedAt\n}\n\nfragment DocumentTitleFormFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n}\n\nfragment UpdateVersionDialogFragment on Document {\n id\n versions(first: 20) {\n edges {\n node {\n id\n status\n content\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + "text": "query DocumentLayoutQuery(\n $documentId: ID!\n $versionId: ID!\n $versionSpecified: Boolean!\n) {\n version: node(id: $versionId) @include(if: $versionSpecified) {\n __typename\n ... on DocumentVersion {\n id\n status\n ...DocumentActionsDropdown_versionFragment\n ...DocumentLayoutDrawer_versionFragment\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n id\n }\n document: node(id: $documentId) {\n __typename\n ... on Document {\n id\n title\n canPublish: permission(action: \"core:document-version:publish\")\n controlInfo: controls(first: 0) {\n totalCount\n }\n ...DocumentTitleFormFragment\n ...DocumentActionsDropdown_documentFragment\n ...DocumentLayoutDrawer_documentFragment\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) @skip(if: $versionSpecified) {\n edges {\n node {\n id\n status\n ...DocumentActionsDropdown_versionFragment\n ...DocumentLayoutDrawer_versionFragment\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n }\n id\n }\n}\n\nfragment DocumentActionsDropdown_documentFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n versions(first: 20) {\n totalCount\n }\n ...UpdateVersionDialogFragment\n}\n\nfragment DocumentActionsDropdown_versionFragment on DocumentVersion {\n id\n version\n status\n canDeleteDraft: permission(action: \"core:document-version:delete-draft\")\n}\n\nfragment DocumentLayoutDrawer_documentFragment on Document {\n id\n documentType\n canUpdate: permission(action: \"core:document:update\")\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n}\n\nfragment DocumentLayoutDrawer_versionFragment on DocumentVersion {\n id\n classification\n version\n status\n updatedAt\n publishedAt\n}\n\nfragment DocumentTitleFormFragment on Document {\n id\n title\n canUpdate: permission(action: \"core:document:update\")\n}\n\nfragment UpdateVersionDialogFragment on Document {\n id\n versions(first: 20) {\n edges {\n node {\n id\n status\n content\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/__generated__/core/DocumentListItemFragment.graphql.ts b/apps/console/src/__generated__/core/DocumentListItemFragment.graphql.ts index e57f92909..d5aebcc12 100644 --- a/apps/console/src/__generated__/core/DocumentListItemFragment.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentListItemFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -14,9 +14,13 @@ export type DocumentStatus = "DRAFT" | "PUBLISHED"; export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; import { FragmentRefs } from "relay-runtime"; export type DocumentListItemFragment$data = { - readonly approver: { - readonly fullName: string; - readonly id: string; + readonly approvers: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly fullName: string; + readonly id: string; + }; + }>; }; readonly canDelete: boolean; readonly classification: DocumentClassification; @@ -118,22 +122,50 @@ return { }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v0/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" }, { "alias": "lastVersion", @@ -244,6 +276,6 @@ return { }; })(); -(node as any).hash = "18456198eadac7ae24bbeebf0f75b5c9"; +(node as any).hash = "440108e302ffc0f15018c14a9e922e88"; export default node; diff --git a/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts b/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts index b274ec5fc..8f67c8dc4 100644 --- a/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentTitleFormMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7ed1236265330f981095a621f4462123>> + * @generated SignedSource<<5e613b9c850d6b80e1f133ee1d190845>> * @lightSyntaxTransform * @nogrep */ @@ -14,7 +14,7 @@ export type DocumentClassification = "CONFIDENTIAL" | "INTERNAL" | "PUBLIC" | "S export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE"; export type TrustCenterVisibility = "NONE" | "PRIVATE" | "PUBLIC"; export type UpdateDocumentInput = { - approverId?: string | null | undefined; + approverIds?: ReadonlyArray | null | undefined; classification?: DocumentClassification | null | undefined; content?: string | null | undefined; documentType?: DocumentType | null | undefined; diff --git a/apps/console/src/__generated__/core/DocumentsListQuery.graphql.ts b/apps/console/src/__generated__/core/DocumentsListQuery.graphql.ts index 53adf3cdc..aa3958cd5 100644 --- a/apps/console/src/__generated__/core/DocumentsListQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentsListQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -298,22 +298,50 @@ return { }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v12/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v12/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" }, { "alias": "lastVersion", @@ -507,12 +535,12 @@ return { ] }, "params": { - "cacheID": "0e6f334d4bea09ecf47fe18b87ef3b84", + "cacheID": "232b7e2c41d7603f54a3de844506ce08", "id": null, "metadata": {}, "name": "DocumentsListQuery", "operationKind": "query", - "text": "query DocumentsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: DocumentOrder = {field: TITLE, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DocumentListFragment_16fISc\n id\n }\n}\n\nfragment DocumentListFragment_16fISc on Organization {\n documents(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canRequestSignatures: permission(action: \"core:document-version:request-signature\")\n ...DocumentListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approver {\n id\n fullName\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" + "text": "query DocumentsListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: DocumentOrder = {field: TITLE, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...DocumentListFragment_16fISc\n id\n }\n}\n\nfragment DocumentListFragment_16fISc on Organization {\n documents(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canRequestSignatures: permission(action: \"core:document-version:request-signature\")\n ...DocumentListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/__generated__/core/DocumentsPageQuery.graphql.ts b/apps/console/src/__generated__/core/DocumentsPageQuery.graphql.ts index ad4393d4a..b8c0fd6e0 100644 --- a/apps/console/src/__generated__/core/DocumentsPageQuery.graphql.ts +++ b/apps/console/src/__generated__/core/DocumentsPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -313,22 +313,50 @@ return { }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v8/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v8/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" }, { "alias": "lastVersion", @@ -523,12 +551,12 @@ return { ] }, "params": { - "cacheID": "5d465e00a354c06dc571df056624dd39", + "cacheID": "638d1d0f41916e750ecb5ac9312b0c8c", "id": null, "metadata": {}, "name": "DocumentsPageQuery", "operationKind": "query", - "text": "query DocumentsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canCreateDocument: permission(action: \"core:document:create\")\n ...DocumentListFragment_1WMmEg\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n canSendSigningNotifications: permission(action: \"core:document:send-signing-notifications\")\n id\n }\n }\n }\n }\n id\n }\n}\n\nfragment DocumentListFragment_1WMmEg on Organization {\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canRequestSignatures: permission(action: \"core:document-version:request-signature\")\n ...DocumentListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approver {\n id\n fullName\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" + "text": "query DocumentsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canCreateDocument: permission(action: \"core:document:create\")\n ...DocumentListFragment_1WMmEg\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n canSendSigningNotifications: permission(action: \"core:document:send-signing-notifications\")\n id\n }\n }\n }\n }\n id\n }\n}\n\nfragment DocumentListFragment_1WMmEg on Organization {\n documents(first: 50, orderBy: {field: TITLE, direction: ASC}) {\n edges {\n node {\n id\n canUpdate: permission(action: \"core:document:update\")\n canDelete: permission(action: \"core:document:delete\")\n canRequestSignatures: permission(action: \"core:document-version:request-signature\")\n ...DocumentListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/__generated__/core/PublishDocumentsDialogMutation.graphql.ts b/apps/console/src/__generated__/core/PublishDocumentsDialogMutation.graphql.ts index eb5304c12..bf60fb3f6 100644 --- a/apps/console/src/__generated__/core/PublishDocumentsDialogMutation.graphql.ts +++ b/apps/console/src/__generated__/core/PublishDocumentsDialogMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<3d906005c4507bfced1f9d7036939157>> * @lightSyntaxTransform * @nogrep */ @@ -192,22 +192,50 @@ return { }, { "alias": null, - "args": null, - "concreteType": "Profile", + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "ProfileConnection", "kind": "LinkedField", - "name": "approver", + "name": "approvers", "plural": false, "selections": [ - (v2/*: any*/), { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fullName", + "concreteType": "ProfileEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Profile", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null + } + ], "storageKey": null } ], - "storageKey": null + "storageKey": "approvers(first:100)" }, { "alias": "lastVersion", @@ -324,12 +352,12 @@ return { ] }, "params": { - "cacheID": "0672eb52643ef05e90565df777644a7b", + "cacheID": "a62979260c1527e43c12c1effa025815", "id": null, "metadata": {}, "name": "PublishDocumentsDialogMutation", "operationKind": "mutation", - "text": "mutation PublishDocumentsDialogMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentListItemFragment\n }\n }\n }\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approver {\n id\n fullName\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" + "text": "mutation PublishDocumentsDialogMutation(\n $input: BulkPublishDocumentVersionsInput!\n) {\n bulkPublishDocumentVersions(input: $input) {\n documentEdges {\n node {\n id\n ...DocumentListItemFragment\n }\n }\n }\n}\n\nfragment DocumentListItemFragment on Document {\n id\n title\n documentType\n classification\n updatedAt\n canDelete: permission(action: \"core:document:delete\")\n approvers(first: 100) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n lastVersion: versions(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n status\n version\n signatures(first: 0, filter: {activeContract: true}) {\n totalCount\n }\n signedSignatures: signatures(first: 0, filter: {states: [SIGNED], activeContract: true}) {\n totalCount\n }\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/hooks/forms/useDocumentForm.tsx b/apps/console/src/hooks/forms/useDocumentForm.tsx index 0bcbc4b90..813e9de77 100644 --- a/apps/console/src/hooks/forms/useDocumentForm.tsx +++ b/apps/console/src/hooks/forms/useDocumentForm.tsx @@ -5,7 +5,7 @@ import { useFormWithSchema } from "../useFormWithSchema"; export const documentSchema = z.object({ title: z.string().min(1, "Title is required"), content: z.string().min(1, "Content is required"), - approverId: z.string().min(1, "Approver is required"), + approverIds: z.array(z.string()).min(1, "At least one approver is required"), documentType: z.enum(["OTHER", "ISMS", "POLICY", "PROCEDURE"]), classification: z.enum(["PUBLIC", "INTERNAL", "CONFIDENTIAL", "SECRET"]), }); diff --git a/apps/console/src/pages/organizations/documents/_components/CreateDocumentDialog.tsx b/apps/console/src/pages/organizations/documents/_components/CreateDocumentDialog.tsx index aec890ed3..4d7ea4d13 100644 --- a/apps/console/src/pages/organizations/documents/_components/CreateDocumentDialog.tsx +++ b/apps/console/src/pages/organizations/documents/_components/CreateDocumentDialog.tsx @@ -20,7 +20,7 @@ import type { CreateDocumentDialogMutation } from "#/__generated__/core/CreateDo import { ControlledField } from "#/components/form/ControlledField"; import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; -import { PeopleSelectField } from "#/components/form/PeopleSelectField"; +import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField"; import { documentSchema, useDocumentForm } from "#/hooks/forms/useDocumentForm"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useOrganizationId } from "#/hooks/useOrganizationId"; @@ -143,12 +143,12 @@ export function CreateDocumentDialog({ trigger, connection }: Props) { - diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx index fd9be690f..a1277553d 100644 --- a/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx +++ b/apps/console/src/pages/organizations/documents/_components/DocumentLayoutDrawer.tsx @@ -12,7 +12,7 @@ import type { DocumentLayoutDrawerMutation } from "#/__generated__/core/Document import { ControlledField } from "#/components/form/ControlledField"; import { DocumentClassificationOptions } from "#/components/form/DocumentClassificationOptions"; import { DocumentTypeOptions } from "#/components/form/DocumentTypeOptions"; -import { PeopleSelectField } from "#/components/form/PeopleSelectField"; +import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useOrganizationId } from "#/hooks/useOrganizationId"; @@ -22,6 +22,14 @@ const documentFragment = graphql` id documentType canUpdate: permission(action: "core:document:update") + approvers(first: 100) { + edges { + node { + id + fullName + } + } + } } `; @@ -29,10 +37,6 @@ const versionFragment = graphql` fragment DocumentLayoutDrawer_versionFragment on DocumentVersion { id classification - approver { - id - fullName - } version status updatedAt @@ -47,9 +51,13 @@ const updateDocumentMutation = graphql` id documentType classification - approver { - id - fullName + approvers(first: 100) { + edges { + node { + id + fullName + } + } } } } @@ -57,7 +65,7 @@ const updateDocumentMutation = graphql` `; const schema = z.object({ - approverId: z.string().min(1, "Approver is required"), + approverIds: z.array(z.string()).min(1, "At least one approver is required"), documentType: z.enum(documentTypes), classification: z.enum(documentClassifications), }); @@ -80,11 +88,13 @@ export function DocumentLayoutDrawer(props: { const isDraft = version.status === "DRAFT"; + const approvers = document.approvers.edges.map(e => e.node); + const { control, handleSubmit, reset } = useFormWithSchema( schema, { defaultValues: { - approverId: version.approver.id, + approverIds: approvers.map(a => a.id), documentType: document.documentType, classification: version.classification, }, @@ -100,12 +110,12 @@ export function DocumentLayoutDrawer(props: { }, ); - const handleUpdateApprover = async (data: { approverId: string }) => { + const handleUpdateApprover = async (data: { approverIds: string[] }) => { await updateDocument({ variables: { input: { id: document.id, - approverId: data.approverId, + approverIds: data.approverIds, }, }, onSuccess: () => { @@ -151,7 +161,7 @@ export function DocumentLayoutDrawer(props: {
{__("Properties")}
- + {isEditingApprover ? ( - ) @@ -174,10 +185,14 @@ export function DocumentLayoutDrawer(props: { onEdit={() => setIsEditingApprover(true)} canEdit={document.canUpdate} > - - - {version.approver.fullName} - +
+ {approvers.map(approver => ( + + + {approver.fullName} + + ))} +
)}
diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentList.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentList.tsx index 3f871ff06..b63d02bef 100644 --- a/apps/console/src/pages/organizations/documents/_components/DocumentList.tsx +++ b/apps/console/src/pages/organizations/documents/_components/DocumentList.tsx @@ -167,7 +167,7 @@ export function DocumentList(props: { {__("Type")} {__("Classification")} - {__("Approver")} + {__("Approvers")} {__("Last update")} {__("Signatures")} {hasAnyAction && } diff --git a/apps/console/src/pages/organizations/documents/_components/DocumentListItem.tsx b/apps/console/src/pages/organizations/documents/_components/DocumentListItem.tsx index 866759ddd..7897aa344 100644 --- a/apps/console/src/pages/organizations/documents/_components/DocumentListItem.tsx +++ b/apps/console/src/pages/organizations/documents/_components/DocumentListItem.tsx @@ -17,9 +17,13 @@ const fragment = graphql` classification updatedAt canDelete: permission(action: "core:document:delete") - approver { - id - fullName + approvers(first: 100) { + edges { + node { + id + fullName + } + } } lastVersion: versions(first: 1 orderBy: { field: CREATED_AT direction: DESC }) { edges { @@ -129,8 +133,12 @@ export function DocumentListItem(props: {
- - {document.approver?.fullName} + {document.approvers.edges.map(({ node }) => ( +
+ + {node.fullName} +
+ ))}
{formatDate(document.updatedAt)} diff --git a/e2e/console/document_test.go b/e2e/console/document_test.go index cccdc249e..b3122d84d 100644 --- a/e2e/console/document_test.go +++ b/e2e/console/document_test.go @@ -113,7 +113,7 @@ func TestDocument_Create(t *testing.T) { input := map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, } for k, v := range tt.input { input[k] = v @@ -172,7 +172,7 @@ func TestDocument_Create_Validation(t *testing.T) { wantErrorContains: "organizationId", }, { - name: "missing approverId", + name: "missing approverIds", input: map[string]any{ "title": "Test Document", "content": "Test content", @@ -180,7 +180,7 @@ func TestDocument_Create_Validation(t *testing.T) { "classification": "INTERNAL", }, skipApprover: true, - wantErrorContains: "approverId", + wantErrorContains: "approverIds", }, { name: "title with HTML tags", @@ -293,7 +293,7 @@ func TestDocument_Create_Validation(t *testing.T) { input["organizationId"] = owner.GetOrganizationID().String() } if !tt.skipApprover { - input["approverId"] = approverProfileID.String() + input["approverIds"] = []string{approverProfileID.String()} } for k, v := range tt.input { input[k] = v @@ -655,7 +655,7 @@ func TestDocument_Timestamps(t *testing.T) { err := owner.Execute(query, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, "title": "Timestamp Test Document", "content": "Test content", "documentType": "POLICY", @@ -736,15 +736,20 @@ func TestDocument_SubResolvers(t *testing.T) { approverProfileID := testutil.NewClientInOrg(t, testutil.RoleViewer, owner).GetProfileID() documentID := factory.NewDocument(owner, approverProfileID.String()).WithTitle("SubResolver Test Document").Create() - t.Run("approver sub-resolver", func(t *testing.T) { + t.Run("approvers sub-resolver", func(t *testing.T) { query := ` query($id: ID!) { node(id: $id) { ... on Document { id - approver { - id - fullName + approvers { + totalCount + edges { + node { + id + fullName + } + } } } } @@ -753,17 +758,24 @@ func TestDocument_SubResolvers(t *testing.T) { var result struct { Node struct { - ID string `json:"id"` - Approver struct { - ID string `json:"id"` - FullName string `json:"fullName"` - } `json:"approver"` + ID string `json:"id"` + Approvers struct { + TotalCount int `json:"totalCount"` + Edges []struct { + Node struct { + ID string `json:"id"` + FullName string `json:"fullName"` + } `json:"node"` + } `json:"edges"` + } `json:"approvers"` } `json:"node"` } err := owner.Execute(query, map[string]any{"id": documentID}, &result) require.NoError(t, err) - assert.Equal(t, approverProfileID.String(), result.Node.Approver.ID) + assert.Equal(t, 1, result.Node.Approvers.TotalCount) + require.Len(t, result.Node.Approvers.Edges, 1) + assert.Equal(t, approverProfileID.String(), result.Node.Approvers.Edges[0].Node.ID) }) t.Run("organization sub-resolver", func(t *testing.T) { @@ -815,7 +827,7 @@ func TestDocument_RBAC(t *testing.T) { `, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, "title": "RBAC Test Document", "content": "Test content", "documentType": "POLICY", @@ -839,7 +851,7 @@ func TestDocument_RBAC(t *testing.T) { `, map[string]any{ "input": map[string]any{ "organizationId": admin.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, "title": "RBAC Test Document", "content": "Test content", "documentType": "POLICY", @@ -863,7 +875,7 @@ func TestDocument_RBAC(t *testing.T) { `, map[string]any{ "input": map[string]any{ "organizationId": viewer.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, "title": "RBAC Test Document", "content": "Test content", "documentType": "POLICY", @@ -1088,7 +1100,7 @@ func TestDocument_MaxLength_Validation(t *testing.T) { _, err := owner.Do(query, map[string]any{ "input": map[string]any{ "organizationId": owner.GetOrganizationID().String(), - "approverId": approverProfileID.String(), + "approverIds": []string{approverProfileID.String()}, "title": longTitle, "content": "Test content", "documentType": "POLICY", diff --git a/e2e/console/document_version_test.go b/e2e/console/document_version_test.go index 7d3c3ea97..96281510b 100644 --- a/e2e/console/document_version_test.go +++ b/e2e/console/document_version_test.go @@ -68,7 +68,7 @@ func createTestDocument(t *testing.T, owner *testutil.Client) (docID string, doc "organizationId": owner.GetOrganizationID().String(), "title": "Test Document", "content": "Initial content", - "approverId": profileID.String(), + "approverIds": []string{profileID.String()}, "documentType": "POLICY", "classification": "INTERNAL", }, diff --git a/e2e/console/mapping_test.go b/e2e/console/mapping_test.go index 22de50968..9bc292400 100644 --- a/e2e/console/mapping_test.go +++ b/e2e/console/mapping_test.go @@ -394,7 +394,7 @@ func TestControlDocumentMapping_CreateDelete(t *testing.T) { "organizationId": owner.GetOrganizationID().String(), "title": "Document for Control Mapping", "content": "Document content", - "ownerId": profileID.String(), + "approverIds": []string{profileID.String()}, "documentType": "POLICY", "classification": "INTERNAL", }, @@ -784,7 +784,7 @@ func TestRiskDocumentMapping_CreateDelete(t *testing.T) { "organizationId": owner.GetOrganizationID().String(), "title": "Document for Risk Mapping", "content": "Document content", - "ownerId": profileID.String(), + "approverIds": []string{profileID.String()}, "documentType": "POLICY", "classification": "INTERNAL", }, diff --git a/e2e/internal/factory/factory.go b/e2e/internal/factory/factory.go index 94dfeeea2..5d73f58fc 100644 --- a/e2e/internal/factory/factory.go +++ b/e2e/internal/factory/factory.go @@ -784,7 +784,7 @@ func CreateDocument(c *testutil.Client, approverID string, attrs ...Attrs) strin input := map[string]any{ "organizationId": c.GetOrganizationID().String(), - "approverId": approverID, + "approverIds": []string{approverID}, "title": a.getString("title", SafeName("Document")), "content": a.getString("content", "Document content"), "documentType": a.getString("documentType", "POLICY"), diff --git a/packages/ui/src/Molecules/Table/DataTable.stories.tsx b/packages/ui/src/Molecules/Table/DataTable.stories.tsx index 80a4c0a20..a0b9c4708 100644 --- a/packages/ui/src/Molecules/Table/DataTable.stories.tsx +++ b/packages/ui/src/Molecules/Table/DataTable.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { type FC, Fragment, useState } from "react"; +// eslint-disable-next-line import-x/no-unresolved -- storybook v10 subpath export import { fn } from "storybook/test"; import { Badge } from "../../Atoms/Badge/Badge"; @@ -15,7 +16,8 @@ export default { title: "Atoms/DataTable/Cells", component: Fragment as Component, argTypes: {}, - args: { onUpdate: fn() }, + // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- fn type unresolvable due to storybook/test subpath + args: { onUpdate: fn() as (key: string, value: unknown) => void }, } satisfies Meta; type Story = StoryObj; diff --git a/pkg/coredata/document.go b/pkg/coredata/document.go index 145ba834e..8020a5ac1 100644 --- a/pkg/coredata/document.go +++ b/pkg/coredata/document.go @@ -32,7 +32,6 @@ type ( Document struct { ID gid.GID `db:"id"` OrganizationID gid.GID `db:"organization_id"` - ApproverID gid.GID `db:"approver_profile_id"` Title string `db:"title"` DocumentType DocumentType `db:"document_type"` Classification DocumentClassification `db:"classification"` @@ -83,7 +82,6 @@ func (p *Document) LoadByID( SELECT id, organization_id, - approver_profile_id, title, document_type, classification, @@ -135,7 +133,6 @@ func (p *Document) LoadByIDWithFilter( SELECT id, organization_id, - approver_profile_id, title, document_type, classification, @@ -188,7 +185,6 @@ func (p *Documents) LoadByIDs( SELECT id, organization_id, - approver_profile_id, title, document_type, classification, @@ -270,7 +266,6 @@ func (p *Documents) LoadByOrganizationID( SELECT id, organization_id, - approver_profile_id, title, document_type, classification, @@ -321,7 +316,6 @@ func (p *Documents) LoadAllByOrganizationID( SELECT id, organization_id, - approver_profile_id, title, document_type, classification, @@ -371,7 +365,6 @@ INSERT INTO tenant_id, id, organization_id, - approver_profile_id, title, document_type, classification, @@ -384,7 +377,6 @@ VALUES ( @tenant_id, @document_id, @organization_id, - @approver_profile_id, @title, @document_type, @classification, @@ -399,7 +391,6 @@ VALUES ( "tenant_id": scope.GetTenantID(), "document_id": p.ID, "organization_id": p.OrganizationID, - "approver_profile_id": p.ApproverID, "title": p.Title, "document_type": p.DocumentType, "classification": p.Classification, @@ -460,7 +451,6 @@ UPDATE SET title = @title, current_published_version = @current_published_version, - approver_profile_id = @approver_profile_id, document_type = @document_type, classification = @classification, trust_center_visibility = @trust_center_visibility, @@ -477,7 +467,6 @@ WHERE "updated_at": time.Now(), "title": p.Title, "current_published_version": p.CurrentPublishedVersion, - "approver_profile_id": p.ApproverID, "document_type": p.DocumentType, "classification": p.Classification, "trust_center_visibility": p.TrustCenterVisibility, @@ -548,7 +537,6 @@ WITH scoped_documents AS ( SELECT scoped_documents.id, scoped_documents.organization_id, - scoped_documents.approver_profile_id, scoped_documents.title, scoped_documents.document_type, scoped_documents.classification, @@ -638,7 +626,6 @@ WITH scoped_documents AS ( SELECT scoped_documents.id, scoped_documents.organization_id, - scoped_documents.approver_profile_id, scoped_documents.title, scoped_documents.document_type, scoped_documents.classification, diff --git a/pkg/coredata/document_approver.go b/pkg/coredata/document_approver.go new file mode 100644 index 000000000..5fe502498 --- /dev/null +++ b/pkg/coredata/document_approver.go @@ -0,0 +1,154 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "context" + "fmt" + "maps" + "time" + + "github.com/jackc/pgx/v5" + "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" +) + +type ( + DocumentApprover struct { + DocumentID gid.GID `db:"document_id"` + ApproverProfileID gid.GID `db:"approver_profile_id"` + OrganizationID gid.GID `db:"organization_id"` + TenantID gid.TenantID `db:"tenant_id"` + CreatedAt time.Time `db:"created_at"` + } + + DocumentApprovers []*DocumentApprover +) + +func (da DocumentApprover) Insert( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +INSERT INTO + document_approvers ( + document_id, + approver_profile_id, + organization_id, + tenant_id, + created_at + ) +VALUES ( + @document_id, + @approver_profile_id, + @organization_id, + @tenant_id, + @created_at +) +ON CONFLICT (document_id, approver_profile_id) DO NOTHING; +` + + args := pgx.StrictNamedArgs{ + "document_id": da.DocumentID, + "approver_profile_id": da.ApproverProfileID, + "organization_id": da.OrganizationID, + "tenant_id": scope.GetTenantID(), + "created_at": da.CreatedAt, + } + _, err := conn.Exec(ctx, q, args) + + if err != nil { + return fmt.Errorf("cannot insert document approver: %w", err) + } + + return nil +} + +func (da *DocumentApprovers) LoadByDocumentID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentID gid.GID, +) error { + q := ` +SELECT + document_id, + approver_profile_id, + organization_id, + tenant_id, + created_at +FROM + document_approvers +WHERE + %s + AND document_id = @document_id +ORDER BY created_at ASC +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_id": documentID} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query document approvers: %w", err) + } + + approvers, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[DocumentApprover]) + if err != nil { + return fmt.Errorf("cannot collect document approvers: %w", err) + } + + *da = approvers + + return nil +} + +func (da *DocumentApprovers) DeleteByDocumentID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentID gid.GID, +) error { + q := ` +DELETE FROM + document_approvers +WHERE + %s + AND document_id = @document_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_id": documentID} + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot delete document approvers: %w", err) + } + + return nil +} + +func (da *DocumentApprovers) ApproverProfileIDs() []gid.GID { + ids := make([]gid.GID, len(*da)) + for i, a := range *da { + ids[i] = a.ApproverProfileID + } + return ids +} diff --git a/pkg/coredata/document_version.go b/pkg/coredata/document_version.go index ae444019d..7a0fafeab 100644 --- a/pkg/coredata/document_version.go +++ b/pkg/coredata/document_version.go @@ -34,7 +34,6 @@ type ( OrganizationID gid.GID `db:"organization_id"` DocumentID gid.GID `db:"document_id"` Title string `db:"title"` - ApproverID gid.GID `db:"approver_profile_id"` VersionNumber int `db:"version_number"` Classification DocumentClassification `db:"classification"` Content string `db:"content"` @@ -77,7 +76,6 @@ SELECT organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -139,7 +137,6 @@ SELECT organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -190,7 +187,6 @@ INSERT INTO document_versions ( organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -205,7 +201,6 @@ VALUES ( @organization_id, @document_id, @title, - @approver_profile_id, @version_number, @classification, @content, @@ -216,19 +211,18 @@ VALUES ( ) ` args := pgx.StrictNamedArgs{ - "tenant_id": scope.GetTenantID(), - "id": dv.ID, - "organization_id": dv.OrganizationID, - "document_id": dv.DocumentID, - "title": dv.Title, - "approver_profile_id": dv.ApproverID, - "version_number": dv.VersionNumber, - "classification": dv.Classification, - "content": dv.Content, - "changelog": dv.Changelog, - "status": dv.Status, - "created_at": dv.CreatedAt, - "updated_at": dv.UpdatedAt, + "tenant_id": scope.GetTenantID(), + "id": dv.ID, + "organization_id": dv.OrganizationID, + "document_id": dv.DocumentID, + "title": dv.Title, + "version_number": dv.VersionNumber, + "classification": dv.Classification, + "content": dv.Content, + "changelog": dv.Changelog, + "status": dv.Status, + "created_at": dv.CreatedAt, + "updated_at": dv.UpdatedAt, } _, err := conn.Exec(ctx, q, args) @@ -260,7 +254,6 @@ SELECT organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -313,7 +306,6 @@ SELECT organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -364,7 +356,6 @@ SELECT organization_id, document_id, title, - approver_profile_id, version_number, classification, content, @@ -413,7 +404,6 @@ func (dv DocumentVersion) Update( q := ` UPDATE document_versions SET title = @title, - approver_profile_id = @approver_profile_id, changelog = @changelog, status = @status, content = @content, @@ -429,7 +419,6 @@ WHERE %s args := pgx.StrictNamedArgs{ "document_version_id": dv.ID, "title": dv.Title, - "approver_profile_id": dv.ApproverID, "changelog": dv.Changelog, "status": dv.Status, "content": dv.Content, diff --git a/pkg/coredata/document_version_approver.go b/pkg/coredata/document_version_approver.go new file mode 100644 index 000000000..d9b088fc9 --- /dev/null +++ b/pkg/coredata/document_version_approver.go @@ -0,0 +1,154 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "context" + "fmt" + "maps" + "time" + + "github.com/jackc/pgx/v5" + "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" +) + +type ( + DocumentVersionApprover struct { + DocumentVersionID gid.GID `db:"document_version_id"` + ApproverProfileID gid.GID `db:"approver_profile_id"` + OrganizationID gid.GID `db:"organization_id"` + TenantID gid.TenantID `db:"tenant_id"` + CreatedAt time.Time `db:"created_at"` + } + + DocumentVersionApprovers []*DocumentVersionApprover +) + +func (dva DocumentVersionApprover) Insert( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +INSERT INTO + document_version_approvers ( + document_version_id, + approver_profile_id, + organization_id, + tenant_id, + created_at + ) +VALUES ( + @document_version_id, + @approver_profile_id, + @organization_id, + @tenant_id, + @created_at +) +ON CONFLICT (document_version_id, approver_profile_id) DO NOTHING; +` + + args := pgx.StrictNamedArgs{ + "document_version_id": dva.DocumentVersionID, + "approver_profile_id": dva.ApproverProfileID, + "organization_id": dva.OrganizationID, + "tenant_id": scope.GetTenantID(), + "created_at": dva.CreatedAt, + } + _, err := conn.Exec(ctx, q, args) + + if err != nil { + return fmt.Errorf("cannot insert document version approver: %w", err) + } + + return nil +} + +func (dva *DocumentVersionApprovers) LoadByDocumentVersionID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentVersionID gid.GID, +) error { + q := ` +SELECT + document_version_id, + approver_profile_id, + organization_id, + tenant_id, + created_at +FROM + document_version_approvers +WHERE + %s + AND document_version_id = @document_version_id +ORDER BY created_at ASC +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_version_id": documentVersionID} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query document version approvers: %w", err) + } + + approvers, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[DocumentVersionApprover]) + if err != nil { + return fmt.Errorf("cannot collect document version approvers: %w", err) + } + + *dva = approvers + + return nil +} + +func (dva *DocumentVersionApprovers) DeleteByDocumentVersionID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentVersionID gid.GID, +) error { + q := ` +DELETE FROM + document_version_approvers +WHERE + %s + AND document_version_id = @document_version_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_version_id": documentVersionID} + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot delete document version approvers: %w", err) + } + + return nil +} + +func (dva *DocumentVersionApprovers) ApproverProfileIDs() []gid.GID { + ids := make([]gid.GID, len(*dva)) + for i, a := range *dva { + ids[i] = a.ApproverProfileID + } + return ids +} diff --git a/pkg/coredata/document_version_signature.go b/pkg/coredata/document_version_signature.go index dcda1c67b..5be1d070a 100644 --- a/pkg/coredata/document_version_signature.go +++ b/pkg/coredata/document_version_signature.go @@ -364,7 +364,7 @@ WITH sigs AS ( FROM document_version_signatures dvs INNER JOIN - iam_membership_profile_id p ON dvs.signed_by_profile_id = p.id + iam_membership_profiles p ON dvs.signed_by_profile_id = p.id WHERE dvs.document_version_id = @document_version_id ORDER BY diff --git a/pkg/coredata/membership_profile.go b/pkg/coredata/membership_profile.go index 96d8017e1..6386e226b 100644 --- a/pkg/coredata/membership_profile.go +++ b/pkg/coredata/membership_profile.go @@ -368,6 +368,212 @@ INNER JOIN identities i ON i.id = p.identity_id return nil } +func (p *MembershipProfiles) LoadByDocumentID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentID gid.GID, + cursor *page.Cursor[MembershipProfileOrderField], +) error { + q := ` +WITH profiles AS ( + SELECT + mp.id, + mp.identity_id, + mp.organization_id, + mp.membership_id, + mp.full_name, + mp.kind, + mp.additional_email_addresses, + mp.position, + mp.contract_start_date, + mp.contract_end_date, + mp.created_at, + mp.updated_at + FROM + iam_membership_profiles mp + WHERE + mp.%s + AND mp.id IN ( + SELECT approver_profile_id + FROM document_approvers + WHERE document_id = @document_id + ) + AND %s +) +SELECT + p.id, + p.identity_id, + p.organization_id, + p.membership_id, + i.email_address, + p.full_name, + p.kind, + p.additional_email_addresses, + p.position, + p.contract_start_date, + p.contract_end_date, + p.created_at, + p.updated_at +FROM profiles p +INNER JOIN identities i ON i.id = p.identity_id +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"document_id": documentID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query document approver profiles: %w", err) + } + + profiles, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[MembershipProfile]) + if err != nil { + return fmt.Errorf("cannot collect document approver profiles: %w", err) + } + + *p = profiles + + return nil +} + +func (p *MembershipProfiles) CountByDocumentID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentID gid.GID, +) (int, error) { + q := ` +SELECT + COUNT(*) +FROM + iam_membership_profiles mp +INNER JOIN document_approvers da ON mp.id = da.approver_profile_id +WHERE + mp.%s + AND da.document_id = @document_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_id": documentID} + maps.Copy(args, scope.SQLArguments()) + + var count int + err := conn.QueryRow(ctx, q, args).Scan(&count) + if err != nil { + return 0, fmt.Errorf("cannot query document approver profiles count: %w", err) + } + + return count, nil +} + +func (p *MembershipProfiles) LoadByDocumentVersionID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentVersionID gid.GID, + cursor *page.Cursor[MembershipProfileOrderField], +) error { + q := ` +WITH profiles AS ( + SELECT + mp.id, + mp.identity_id, + mp.organization_id, + mp.membership_id, + mp.full_name, + mp.kind, + mp.additional_email_addresses, + mp.position, + mp.contract_start_date, + mp.contract_end_date, + mp.created_at, + mp.updated_at + FROM + iam_membership_profiles mp + WHERE + mp.%s + AND mp.id IN ( + SELECT approver_profile_id + FROM document_version_approvers + WHERE document_version_id = @document_version_id + ) + AND %s +) +SELECT + p.id, + p.identity_id, + p.organization_id, + p.membership_id, + i.email_address, + p.full_name, + p.kind, + p.additional_email_addresses, + p.position, + p.contract_start_date, + p.contract_end_date, + p.created_at, + p.updated_at +FROM profiles p +INNER JOIN identities i ON i.id = p.identity_id +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"document_version_id": documentVersionID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query document version approver profiles: %w", err) + } + + profiles, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[MembershipProfile]) + if err != nil { + return fmt.Errorf("cannot collect document version approver profiles: %w", err) + } + + *p = profiles + + return nil +} + +func (p *MembershipProfiles) CountByDocumentVersionID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + documentVersionID gid.GID, +) (int, error) { + q := ` +SELECT + COUNT(*) +FROM + iam_membership_profiles mp +INNER JOIN document_version_approvers dva ON mp.id = dva.approver_profile_id +WHERE + mp.%s + AND dva.document_version_id = @document_version_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"document_version_id": documentVersionID} + maps.Copy(args, scope.SQLArguments()) + + var count int + err := conn.QueryRow(ctx, q, args).Scan(&count) + if err != nil { + return 0, fmt.Errorf("cannot query document version approver profiles count: %w", err) + } + + return count, nil +} + func (p *MembershipProfiles) LoadByMeetingID( ctx context.Context, conn pg.Conn, diff --git a/pkg/coredata/migrations/20260213T120000Z.sql b/pkg/coredata/migrations/20260213T120000Z.sql new file mode 100644 index 000000000..4837564d1 --- /dev/null +++ b/pkg/coredata/migrations/20260213T120000Z.sql @@ -0,0 +1,36 @@ +-- Create document_approvers join table +CREATE TABLE document_approvers ( + document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE, + approver_profile_id TEXT NOT NULL REFERENCES iam_membership_profiles(id) ON UPDATE CASCADE ON DELETE RESTRICT, + tenant_id TEXT NOT NULL, + organization_id TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY (document_id, approver_profile_id) +); + +-- Create document_version_approvers join table +CREATE TABLE document_version_approvers ( + document_version_id TEXT NOT NULL REFERENCES document_versions(id) ON DELETE CASCADE, + approver_profile_id TEXT NOT NULL REFERENCES iam_membership_profiles(id) ON UPDATE CASCADE ON DELETE RESTRICT, + tenant_id TEXT NOT NULL, + organization_id TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY (document_version_id, approver_profile_id) +); + +-- Migrate existing approver data from documents to document_approvers +INSERT INTO document_approvers (document_id, approver_profile_id, tenant_id, organization_id, created_at) +SELECT id, approver_profile_id, tenant_id, organization_id, created_at +FROM documents +WHERE approver_profile_id IS NOT NULL + AND deleted_at IS NULL; + +-- Migrate existing approver data from document_versions to document_version_approvers +INSERT INTO document_version_approvers (document_version_id, approver_profile_id, tenant_id, organization_id, created_at) +SELECT id, approver_profile_id, tenant_id, organization_id, created_at +FROM document_versions +WHERE approver_profile_id IS NOT NULL; + +-- Drop the old columns +ALTER TABLE documents DROP COLUMN approver_profile_id; +ALTER TABLE document_versions DROP COLUMN approver_profile_id; diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index ec32e3c47..9232f2a8f 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -50,7 +50,7 @@ type ( OrganizationID gid.GID Title string Content string - ApproverID gid.GID + ApproverIDs []gid.GID Classification coredata.DocumentClassification DocumentType coredata.DocumentType TrustCenterVisibility *coredata.TrustCenterVisibility @@ -59,7 +59,7 @@ type ( UpdateDocumentRequest struct { DocumentID gid.GID Title *string - ApproverID *gid.GID + ApproverIDs []gid.GID Classification *coredata.DocumentClassification DocumentType *coredata.DocumentType TrustCenterVisibility *coredata.TrustCenterVisibility @@ -102,7 +102,10 @@ func (cdr *CreateDocumentRequest) Validate() error { v.Check(cdr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType)) v.Check(cdr.Title, "title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength)) v.Check(cdr.Content, "content", validator.Required(), validator.NotEmpty(), validator.MaxLen(documentMaxLength)) - v.Check(cdr.ApproverID, "approver_id", validator.Required(), validator.GID(coredata.MembershipProfileEntityType)) + v.Check(cdr.ApproverIDs, "approver_ids", validator.Required(), validator.NotEmpty()) + for _, id := range cdr.ApproverIDs { + v.Check(id, "approver_ids", validator.Required(), validator.GID(coredata.MembershipProfileEntityType)) + } v.Check(cdr.Classification, "classification", validator.Required(), validator.OneOfSlice(coredata.DocumentClassifications())) v.Check(cdr.DocumentType, "document_type", validator.Required(), validator.OneOfSlice(coredata.DocumentTypes())) v.Check(cdr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities())) @@ -115,7 +118,9 @@ func (udr *UpdateDocumentRequest) Validate() error { v.Check(udr.DocumentID, "document_id", validator.Required(), validator.GID(coredata.DocumentEntityType)) v.Check(udr.Title, "title", validator.SafeTextNoNewLine(TitleMaxLength)) - v.Check(udr.ApproverID, "approver_id", validator.GID(coredata.MembershipProfileEntityType)) + for _, id := range udr.ApproverIDs { + v.Check(id, "approver_ids", validator.Required(), validator.GID(coredata.MembershipProfileEntityType)) + } v.Check(udr.Classification, "classification", validator.OneOfSlice(coredata.DocumentClassifications())) v.Check(udr.DocumentType, "document_type", validator.OneOfSlice(coredata.DocumentTypes())) v.Check(udr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities())) @@ -177,6 +182,108 @@ func (s *DocumentService) Get( return document, nil } +func (s *DocumentService) ListApprovers( + ctx context.Context, + documentID gid.GID, + cursor *page.Cursor[coredata.MembershipProfileOrderField], +) (*page.Page[*coredata.MembershipProfile, coredata.MembershipProfileOrderField], error) { + var profiles coredata.MembershipProfiles + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := profiles.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor); err != nil { + return fmt.Errorf("cannot load document approvers: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(profiles, cursor), nil +} + +func (s *DocumentService) CountApprovers( + ctx context.Context, + documentID gid.GID, +) (int, error) { + var count int + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) (err error) { + profiles := coredata.MembershipProfiles{} + count, err = profiles.CountByDocumentID(ctx, conn, s.svc.scope, documentID) + if err != nil { + return fmt.Errorf("cannot count document approvers: %w", err) + } + + return nil + }, + ) + + if err != nil { + return 0, err + } + + return count, nil +} + +func (s *DocumentService) ListVersionApprovers( + ctx context.Context, + documentVersionID gid.GID, + cursor *page.Cursor[coredata.MembershipProfileOrderField], +) (*page.Page[*coredata.MembershipProfile, coredata.MembershipProfileOrderField], error) { + var profiles coredata.MembershipProfiles + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := profiles.LoadByDocumentVersionID(ctx, conn, s.svc.scope, documentVersionID, cursor); err != nil { + return fmt.Errorf("cannot load document version approvers: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(profiles, cursor), nil +} + +func (s *DocumentService) CountVersionApprovers( + ctx context.Context, + documentVersionID gid.GID, +) (int, error) { + var count int + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) (err error) { + profiles := coredata.MembershipProfiles{} + count, err = profiles.CountByDocumentVersionID(ctx, conn, s.svc.scope, documentVersionID) + if err != nil { + return fmt.Errorf("cannot count document version approvers: %w", err) + } + + return nil + }, + ) + + if err != nil { + return 0, err + } + + return count, nil +} + func (s *DocumentService) GetWithFilter( ctx context.Context, documentID gid.GID, @@ -354,8 +461,7 @@ func (s *DocumentService) publishVersionInTx( return nil, nil, fmt.Errorf("cannot load published version: %w", err) } if publishedVersion.Content == documentVersion.Content && - publishedVersion.Title == documentVersion.Title && - publishedVersion.ApproverID == documentVersion.ApproverID { + publishedVersion.Title == documentVersion.Title { return nil, nil, &ErrDocumentVersionNoChanges{} } } @@ -395,7 +501,6 @@ func (s *DocumentService) Create( documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType) organization := &coredata.Organization{} - approver := &coredata.MembershipProfile{} document := &coredata.Document{ ID: documentID, @@ -415,7 +520,6 @@ func (s *DocumentService) Create( ID: documentVersionID, DocumentID: documentID, Title: req.Title, - ApproverID: req.ApproverID, VersionNumber: 1, Content: req.Content, Status: coredata.DocumentStatusDraft, @@ -431,23 +535,54 @@ func (s *DocumentService) Create( return fmt.Errorf("cannot load organization: %w", err) } - if err := approver.LoadByID(ctx, conn, s.svc.scope, req.ApproverID); err != nil { - return fmt.Errorf("cannot load approver profile: %w", err) + // Validate all approver profiles exist + approverProfiles := coredata.MembershipProfiles{} + if err := approverProfiles.LoadByIDs(ctx, conn, s.svc.scope, req.ApproverIDs); err != nil { + return fmt.Errorf("cannot load approver profiles: %w", err) + } + + if len(approverProfiles) != len(req.ApproverIDs) { + return fmt.Errorf("one or more approver profiles not found") } document.OrganizationID = organization.ID - document.ApproverID = approver.ID if err := document.Insert(ctx, conn, s.svc.scope); err != nil { return fmt.Errorf("cannot insert document: %w", err) } + // Insert document approvers + for _, approverID := range req.ApproverIDs { + da := coredata.DocumentApprover{ + DocumentID: documentID, + ApproverProfileID: approverID, + OrganizationID: organization.ID, + CreatedAt: now, + } + if err := da.Insert(ctx, conn, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert document approver: %w", err) + } + } + documentVersion.OrganizationID = organization.ID if err := documentVersion.Insert(ctx, conn, s.svc.scope); err != nil { return fmt.Errorf("cannot create document version: %w", err) } + // Insert document version approvers + for _, approverID := range req.ApproverIDs { + dva := coredata.DocumentVersionApprover{ + DocumentVersionID: documentVersionID, + ApproverProfileID: approverID, + OrganizationID: organization.ID, + CreatedAt: now, + } + if err := dva.Insert(ctx, conn, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert document version approver: %w", err) + } + } + return nil }, ) @@ -706,7 +841,6 @@ func (s *DocumentService) UpdateVersion( } documentVersion.Title = document.Title - documentVersion.ApproverID = document.ApproverID documentVersion.Classification = document.Classification documentVersion.Content = req.Content documentVersion.UpdatedAt = time.Now() @@ -715,6 +849,28 @@ func (s *DocumentService) UpdateVersion( return fmt.Errorf("cannot update document version: %w", err) } + docApprovers := &coredata.DocumentApprovers{} + if err := docApprovers.LoadByDocumentID(ctx, conn, s.svc.scope, document.ID); err != nil { + return fmt.Errorf("cannot load document approvers: %w", err) + } + + versionApprovers := &coredata.DocumentVersionApprovers{} + if err := versionApprovers.DeleteByDocumentVersionID(ctx, conn, s.svc.scope, documentVersion.ID); err != nil { + return fmt.Errorf("cannot delete document version approvers: %w", err) + } + + for _, da := range *docApprovers { + dva := coredata.DocumentVersionApprover{ + DocumentVersionID: documentVersion.ID, + ApproverProfileID: da.ApproverProfileID, + OrganizationID: da.OrganizationID, + CreatedAt: time.Now(), + } + if err := dva.Insert(ctx, conn, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert document version approver: %w", err) + } + } + return nil }, ) @@ -944,7 +1100,6 @@ func (s *DocumentService) CreateDraft( draftVersion.OrganizationID = document.OrganizationID draftVersion.DocumentID = documentID draftVersion.Title = document.Title - draftVersion.ApproverID = document.ApproverID draftVersion.VersionNumber = latestVersion.VersionNumber + 1 draftVersion.Classification = document.Classification draftVersion.Content = latestVersion.Content @@ -956,6 +1111,23 @@ func (s *DocumentService) CreateDraft( return fmt.Errorf("cannot create draft: %w", err) } + docApprovers := &coredata.DocumentApprovers{} + if err := docApprovers.LoadByDocumentID(ctx, conn, s.svc.scope, documentID); err != nil { + return fmt.Errorf("cannot load document approvers: %w", err) + } + + for _, da := range *docApprovers { + dva := coredata.DocumentVersionApprover{ + DocumentVersionID: draftVersionID, + ApproverProfileID: da.ApproverProfileID, + OrganizationID: da.OrganizationID, + CreatedAt: now, + } + if err := dva.Insert(ctx, conn, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert document version approver: %w", err) + } + } + return nil }, ) @@ -1384,7 +1556,6 @@ func (s *DocumentService) Update( } document := &coredata.Document{} - approver := &coredata.MembershipProfile{} now := time.Now() err := s.svc.pg.WithTx( @@ -1414,11 +1585,31 @@ func (s *DocumentService) Update( document.TrustCenterVisibility = *req.TrustCenterVisibility } - if req.ApproverID != nil { - if err := approver.LoadByID(ctx, tx, s.svc.scope, *req.ApproverID); err != nil { - return fmt.Errorf("cannot load approver profile %q: %w", *req.ApproverID, err) + if len(req.ApproverIDs) > 0 { + approverProfiles := coredata.MembershipProfiles{} + if err := approverProfiles.LoadByIDs(ctx, tx, s.svc.scope, req.ApproverIDs); err != nil { + return fmt.Errorf("cannot load approver profiles: %w", err) + } + if len(approverProfiles) != len(req.ApproverIDs) { + return fmt.Errorf("one or more approver profiles not found") + } + + docApprovers := &coredata.DocumentApprovers{} + if err := docApprovers.DeleteByDocumentID(ctx, tx, s.svc.scope, req.DocumentID); err != nil { + return fmt.Errorf("cannot delete document approvers: %w", err) + } + + for _, approverID := range req.ApproverIDs { + da := coredata.DocumentApprover{ + DocumentID: req.DocumentID, + ApproverProfileID: approverID, + OrganizationID: document.OrganizationID, + CreatedAt: now, + } + if err := da.Insert(ctx, tx, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert document approver: %w", err) + } } - document.ApproverID = approver.ID } document.UpdatedAt = now @@ -1427,18 +1618,35 @@ func (s *DocumentService) Update( return fmt.Errorf("cannot update document: %w", err) } - // Update the draft version if it exists to keep it in sync with the document draftVersion := &coredata.DocumentVersion{} err := draftVersion.LoadLatestVersion(ctx, tx, s.svc.scope, req.DocumentID) if err == nil && draftVersion.Status == coredata.DocumentStatusDraft { draftVersion.Title = document.Title - draftVersion.ApproverID = document.ApproverID draftVersion.Classification = document.Classification draftVersion.UpdatedAt = now if err := draftVersion.Update(ctx, tx, s.svc.scope); err != nil { return fmt.Errorf("cannot update draft version: %w", err) } + + if len(req.ApproverIDs) > 0 { + versionApprovers := &coredata.DocumentVersionApprovers{} + if err := versionApprovers.DeleteByDocumentVersionID(ctx, tx, s.svc.scope, draftVersion.ID); err != nil { + return fmt.Errorf("cannot delete draft version approvers: %w", err) + } + + for _, approverID := range req.ApproverIDs { + dva := coredata.DocumentVersionApprover{ + DocumentVersionID: draftVersion.ID, + ApproverProfileID: approverID, + OrganizationID: document.OrganizationID, + CreatedAt: now, + } + if err := dva.Insert(ctx, tx, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert draft version approver: %w", err) + } + } + } } return nil @@ -1638,7 +1846,6 @@ func exportDocumentPDF( ) ([]byte, error) { document := &coredata.Document{} version := &coredata.DocumentVersion{} - approver := &coredata.MembershipProfile{} organization := &coredata.Organization{} if err := version.LoadByID(ctx, conn, scope, documentVersionID); err != nil { @@ -1649,8 +1856,26 @@ func exportDocumentPDF( return nil, fmt.Errorf("cannot load document: %w", err) } - if err := approver.LoadByID(ctx, conn, scope, document.ApproverID); err != nil { - return nil, fmt.Errorf("cannot load document approver profile: %w", err) + versionApprovers := &coredata.DocumentVersionApprovers{} + if err := versionApprovers.LoadByDocumentVersionID(ctx, conn, scope, documentVersionID); err != nil { + return nil, fmt.Errorf("cannot load document version approvers: %w", err) + } + + approverProfiles := coredata.MembershipProfiles{} + if err := approverProfiles.LoadByIDs(ctx, conn, scope, versionApprovers.ApproverProfileIDs()); err != nil { + return nil, fmt.Errorf("cannot load document approver profiles: %w", err) + } + + profileByID := make(map[gid.GID]*coredata.MembershipProfile, len(approverProfiles)) + for _, p := range approverProfiles { + profileByID[p.ID] = p + } + + approverNames := make([]string, 0, len(*versionApprovers)) + for _, a := range *versionApprovers { + if p, ok := profileByID[a.ApproverProfileID]; ok { + approverNames = append(approverNames, p.FullName) + } } if err := organization.LoadByID(ctx, conn, scope, document.OrganizationID); err != nil { @@ -1702,7 +1927,7 @@ func exportDocumentPDF( Content: version.Content, Version: version.VersionNumber, Classification: classification, - Approver: approver.FullName, + Approver: strings.Join(approverNames, ", "), PublishedAt: version.PublishedAt, Signatures: signatureData, CompanyHorizontalLogoBase64: horizontalLogoBase64, diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 092b94feb..b50db87f5 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -2208,7 +2208,13 @@ type Document implements Node { classification: DocumentClassification! currentPublishedVersion: Int trustCenterVisibility: TrustCenterVisibility! - approver: Profile! @goField(forceResolver: true) + approvers( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true) versions( @@ -4018,7 +4024,7 @@ input CreateDocumentInput { organizationId: ID! title: String! content: String! - approverId: ID! + approverIds: [ID!]! documentType: DocumentType! classification: DocumentClassification! trustCenterVisibility: TrustCenterVisibility @@ -4028,7 +4034,7 @@ input UpdateDocumentInput { id: ID! title: String content: String - approverId: ID + approverIds: [ID!] documentType: DocumentType classification: DocumentClassification trustCenterVisibility: TrustCenterVisibility @@ -4862,7 +4868,13 @@ type DocumentVersion implements Node { changelog: String! title: String! classification: DocumentClassification! - approver: Profile! @goField(forceResolver: true) + approvers( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection! @goField(forceResolver: true) signatures( first: Int diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 251c4333f..9a84efd82 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -702,7 +702,7 @@ type ComplexityRoot struct { } Document struct { - Approver func(childComplexity int) int + Approvers func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) int Classification func(childComplexity int) int Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int @@ -730,7 +730,7 @@ type ComplexityRoot struct { } DocumentVersion struct { - Approver func(childComplexity int) int + Approvers func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) int Changelog func(childComplexity int) int Classification func(childComplexity int) int Content func(childComplexity int) int @@ -2058,7 +2058,7 @@ type DatumConnectionResolver interface { TotalCount(ctx context.Context, obj *types.DatumConnection) (int, error) } type DocumentResolver interface { - Approver(ctx context.Context, obj *types.Document) (*types.Profile, error) + Approvers(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) Organization(ctx context.Context, obj *types.Document) (*types.Organization, error) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) @@ -2071,7 +2071,7 @@ type DocumentConnectionResolver interface { type DocumentVersionResolver interface { Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) - Approver(ctx context.Context, obj *types.DocumentVersion) (*types.Profile, error) + Approvers(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) Signatures(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionSignatureOrder, filter *types.DocumentVersionSignatureFilter) (*types.DocumentVersionSignatureConnection, error) Signed(ctx context.Context, obj *types.DocumentVersion) (bool, error) @@ -4208,12 +4208,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.DeleteWebhookSubscriptionPayload.DeletedWebhookSubscriptionID(childComplexity), true - case "Document.approver": - if e.complexity.Document.Approver == nil { + case "Document.approvers": + if e.complexity.Document.Approvers == nil { break } - return e.complexity.Document.Approver(childComplexity), true + args, err := ec.field_Document_approvers_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Document.Approvers(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ProfileOrderBy)), true case "Document.classification": if e.complexity.Document.Classification == nil { break @@ -4340,12 +4345,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.DocumentEdge.Node(childComplexity), true - case "DocumentVersion.approver": - if e.complexity.DocumentVersion.Approver == nil { + case "DocumentVersion.approvers": + if e.complexity.DocumentVersion.Approvers == nil { break } - return e.complexity.DocumentVersion.Approver(childComplexity), true + args, err := ec.field_DocumentVersion_approvers_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.DocumentVersion.Approvers(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ProfileOrderBy)), true case "DocumentVersion.changelog": if e.complexity.DocumentVersion.Changelog == nil { break @@ -12795,7 +12805,13 @@ type Document implements Node { classification: DocumentClassification! currentPublishedVersion: Int trustCenterVisibility: TrustCenterVisibility! - approver: Profile! @goField(forceResolver: true) + approvers( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection! @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true) versions( @@ -14605,7 +14621,7 @@ input CreateDocumentInput { organizationId: ID! title: String! content: String! - approverId: ID! + approverIds: [ID!]! documentType: DocumentType! classification: DocumentClassification! trustCenterVisibility: TrustCenterVisibility @@ -14615,7 +14631,7 @@ input UpdateDocumentInput { id: ID! title: String content: String - approverId: ID + approverIds: [ID!] documentType: DocumentType classification: DocumentClassification trustCenterVisibility: TrustCenterVisibility @@ -15449,7 +15465,13 @@ type DocumentVersion implements Node { changelog: String! title: String! classification: DocumentClassification! - approver: Profile! @goField(forceResolver: true) + approvers( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ProfileOrder + ): ProfileConnection! @goField(forceResolver: true) signatures( first: Int @@ -16362,6 +16384,37 @@ func (ec *executionContext) field_DocumentVersionSignature_permission_args(ctx c return args, nil } +func (ec *executionContext) field_DocumentVersion_approvers_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "first", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "after", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := graphql.ProcessArgField(ctx, rawArgs, "last", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := graphql.ProcessArgField(ctx, rawArgs, "before", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["before"] = arg3 + arg4, err := graphql.ProcessArgField(ctx, rawArgs, "orderBy", ec.unmarshalOProfileOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfileOrderBy) + if err != nil { + return nil, err + } + args["orderBy"] = arg4 + return args, nil +} + func (ec *executionContext) field_DocumentVersion_permission_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -16409,6 +16462,37 @@ func (ec *executionContext) field_DocumentVersion_signatures_args(ctx context.Co return args, nil } +func (ec *executionContext) field_Document_approvers_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "first", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "after", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := graphql.ProcessArgField(ctx, rawArgs, "last", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := graphql.ProcessArgField(ctx, rawArgs, "before", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["before"] = arg3 + arg4, err := graphql.ProcessArgField(ctx, rawArgs, "orderBy", ec.unmarshalOProfileOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfileOrderBy) + if err != nil { + return nil, err + } + args["orderBy"] = arg4 + return args, nil +} + func (ec *executionContext) field_Document_controls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -29115,23 +29199,24 @@ func (ec *executionContext) fieldContext_Document_trustCenterVisibility(_ contex return fc, nil } -func (ec *executionContext) _Document_approver(ctx context.Context, field graphql.CollectedField, obj *types.Document) (ret graphql.Marshaler) { +func (ec *executionContext) _Document_approvers(ctx context.Context, field graphql.CollectedField, obj *types.Document) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, - ec.fieldContext_Document_approver, + ec.fieldContext_Document_approvers, func(ctx context.Context) (any, error) { - return ec.resolvers.Document().Approver(ctx, obj) + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Document().Approvers(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ProfileOrderBy)) }, nil, - ec.marshalNProfile2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfile, + ec.marshalNProfileConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfileConnection, true, true, ) } -func (ec *executionContext) fieldContext_Document_approver(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Document_approvers(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Document", Field: field, @@ -29139,32 +29224,27 @@ func (ec *executionContext) fieldContext_Document_approver(_ context.Context, fi IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "id": - return ec.fieldContext_Profile_id(ctx, field) - case "fullName": - return ec.fieldContext_Profile_fullName(ctx, field) - case "emailAddress": - return ec.fieldContext_Profile_emailAddress(ctx, field) - case "additionalEmailAddresses": - return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field) - case "kind": - return ec.fieldContext_Profile_kind(ctx, field) - case "position": - return ec.fieldContext_Profile_position(ctx, field) - case "contractStartDate": - return ec.fieldContext_Profile_contractStartDate(ctx, field) - case "contractEndDate": - return ec.fieldContext_Profile_contractEndDate(ctx, field) - case "createdAt": - return ec.fieldContext_Profile_createdAt(ctx, field) - case "updatedAt": - return ec.fieldContext_Profile_updatedAt(ctx, field) - case "permission": - return ec.fieldContext_Profile_permission(ctx, field) + case "totalCount": + return ec.fieldContext_ProfileConnection_totalCount(ctx, field) + case "edges": + return ec.fieldContext_ProfileConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_ProfileConnection_pageInfo(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type Profile", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ProfileConnection", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Document_approvers_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -29642,8 +29722,8 @@ func (ec *executionContext) fieldContext_DocumentEdge_node(_ context.Context, fi return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": return ec.fieldContext_Document_trustCenterVisibility(ctx, field) - case "approver": - return ec.fieldContext_Document_approver(ctx, field) + case "approvers": + return ec.fieldContext_Document_approvers(ctx, field) case "organization": return ec.fieldContext_Document_organization(ctx, field) case "versions": @@ -29730,8 +29810,8 @@ func (ec *executionContext) fieldContext_DocumentVersion_document(_ context.Cont return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": return ec.fieldContext_Document_trustCenterVisibility(ctx, field) - case "approver": - return ec.fieldContext_Document_approver(ctx, field) + case "approvers": + return ec.fieldContext_Document_approvers(ctx, field) case "organization": return ec.fieldContext_Document_organization(ctx, field) case "versions": @@ -29925,23 +30005,24 @@ func (ec *executionContext) fieldContext_DocumentVersion_classification(_ contex return fc, nil } -func (ec *executionContext) _DocumentVersion_approver(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) { +func (ec *executionContext) _DocumentVersion_approvers(ctx context.Context, field graphql.CollectedField, obj *types.DocumentVersion) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, ec.OperationContext, field, - ec.fieldContext_DocumentVersion_approver, + ec.fieldContext_DocumentVersion_approvers, func(ctx context.Context) (any, error) { - return ec.resolvers.DocumentVersion().Approver(ctx, obj) + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.DocumentVersion().Approvers(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ProfileOrderBy)) }, nil, - ec.marshalNProfile2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfile, + ec.marshalNProfileConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐProfileConnection, true, true, ) } -func (ec *executionContext) fieldContext_DocumentVersion_approver(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_DocumentVersion_approvers(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "DocumentVersion", Field: field, @@ -29949,32 +30030,27 @@ func (ec *executionContext) fieldContext_DocumentVersion_approver(_ context.Cont IsResolver: true, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { - case "id": - return ec.fieldContext_Profile_id(ctx, field) - case "fullName": - return ec.fieldContext_Profile_fullName(ctx, field) - case "emailAddress": - return ec.fieldContext_Profile_emailAddress(ctx, field) - case "additionalEmailAddresses": - return ec.fieldContext_Profile_additionalEmailAddresses(ctx, field) - case "kind": - return ec.fieldContext_Profile_kind(ctx, field) - case "position": - return ec.fieldContext_Profile_position(ctx, field) - case "contractStartDate": - return ec.fieldContext_Profile_contractStartDate(ctx, field) - case "contractEndDate": - return ec.fieldContext_Profile_contractEndDate(ctx, field) - case "createdAt": - return ec.fieldContext_Profile_createdAt(ctx, field) - case "updatedAt": - return ec.fieldContext_Profile_updatedAt(ctx, field) - case "permission": - return ec.fieldContext_Profile_permission(ctx, field) + case "totalCount": + return ec.fieldContext_ProfileConnection_totalCount(ctx, field) + case "edges": + return ec.fieldContext_ProfileConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_ProfileConnection_pageInfo(ctx, field) } - return nil, fmt.Errorf("no field named %q was found under type Profile", field.Name) + return nil, fmt.Errorf("no field named %q was found under type ProfileConnection", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_DocumentVersion_approvers_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -30356,8 +30432,8 @@ func (ec *executionContext) fieldContext_DocumentVersionEdge_node(_ context.Cont return ec.fieldContext_DocumentVersion_title(ctx, field) case "classification": return ec.fieldContext_DocumentVersion_classification(ctx, field) - case "approver": - return ec.fieldContext_DocumentVersion_approver(ctx, field) + case "approvers": + return ec.fieldContext_DocumentVersion_approvers(ctx, field) case "signatures": return ec.fieldContext_DocumentVersion_signatures(ctx, field) case "signed": @@ -30446,8 +30522,8 @@ func (ec *executionContext) fieldContext_DocumentVersionSignature_documentVersio return ec.fieldContext_DocumentVersion_title(ctx, field) case "classification": return ec.fieldContext_DocumentVersion_classification(ctx, field) - case "approver": - return ec.fieldContext_DocumentVersion_approver(ctx, field) + case "approvers": + return ec.fieldContext_DocumentVersion_approvers(ctx, field) case "signatures": return ec.fieldContext_DocumentVersion_signatures(ctx, field) case "signed": @@ -45461,8 +45537,8 @@ func (ec *executionContext) fieldContext_PublishDocumentVersionPayload_documentV return ec.fieldContext_DocumentVersion_title(ctx, field) case "classification": return ec.fieldContext_DocumentVersion_classification(ctx, field) - case "approver": - return ec.fieldContext_DocumentVersion_approver(ctx, field) + case "approvers": + return ec.fieldContext_DocumentVersion_approvers(ctx, field) case "signatures": return ec.fieldContext_DocumentVersion_signatures(ctx, field) case "signed": @@ -45520,8 +45596,8 @@ func (ec *executionContext) fieldContext_PublishDocumentVersionPayload_document( return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": return ec.fieldContext_Document_trustCenterVisibility(ctx, field) - case "approver": - return ec.fieldContext_Document_approver(ctx, field) + case "approvers": + return ec.fieldContext_Document_approvers(ctx, field) case "organization": return ec.fieldContext_Document_organization(ctx, field) case "versions": @@ -52250,8 +52326,8 @@ func (ec *executionContext) fieldContext_TrustCenterDocumentAccess_document(_ co return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": return ec.fieldContext_Document_trustCenterVisibility(ctx, field) - case "approver": - return ec.fieldContext_Document_approver(ctx, field) + case "approvers": + return ec.fieldContext_Document_approvers(ctx, field) case "organization": return ec.fieldContext_Document_organization(ctx, field) case "versions": @@ -54047,8 +54123,8 @@ func (ec *executionContext) fieldContext_UpdateDocumentPayload_document(_ contex return ec.fieldContext_Document_currentPublishedVersion(ctx, field) case "trustCenterVisibility": return ec.fieldContext_Document_trustCenterVisibility(ctx, field) - case "approver": - return ec.fieldContext_Document_approver(ctx, field) + case "approvers": + return ec.fieldContext_Document_approvers(ctx, field) case "organization": return ec.fieldContext_Document_organization(ctx, field) case "versions": @@ -54108,8 +54184,8 @@ func (ec *executionContext) fieldContext_UpdateDocumentVersionPayload_documentVe return ec.fieldContext_DocumentVersion_title(ctx, field) case "classification": return ec.fieldContext_DocumentVersion_classification(ctx, field) - case "approver": - return ec.fieldContext_DocumentVersion_approver(ctx, field) + case "approvers": + return ec.fieldContext_DocumentVersion_approvers(ctx, field) case "signatures": return ec.fieldContext_DocumentVersion_signatures(ctx, field) case "signed": @@ -63387,7 +63463,7 @@ func (ec *executionContext) unmarshalInputCreateDocumentInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"organizationId", "title", "content", "approverId", "documentType", "classification", "trustCenterVisibility"} + fieldsInOrder := [...]string{"organizationId", "title", "content", "approverIds", "documentType", "classification", "trustCenterVisibility"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -63415,13 +63491,13 @@ func (ec *executionContext) unmarshalInputCreateDocumentInput(ctx context.Contex return it, err } it.Content = data - case "approverId": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("approverId")) - data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + case "approverIds": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("approverIds")) + data, err := ec.unmarshalNID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v) if err != nil { return it, err } - it.ApproverID = data + it.ApproverIds = data case "documentType": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentType")) data, err := ec.unmarshalNDocumentType2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐDocumentType(ctx, v) @@ -68341,7 +68417,7 @@ func (ec *executionContext) unmarshalInputUpdateDocumentInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"id", "title", "content", "approverId", "documentType", "classification", "trustCenterVisibility"} + fieldsInOrder := [...]string{"id", "title", "content", "approverIds", "documentType", "classification", "trustCenterVisibility"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -68369,13 +68445,13 @@ func (ec *executionContext) unmarshalInputUpdateDocumentInput(ctx context.Contex return it, err } it.Content = data - case "approverId": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("approverId")) - data, err := ec.unmarshalOID2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + case "approverIds": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("approverIds")) + data, err := ec.unmarshalOID2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGIDᚄ(ctx, v) if err != nil { return it, err } - it.ApproverID = data + it.ApproverIds = data case "documentType": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("documentType")) data, err := ec.unmarshalODocumentType2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐDocumentType(ctx, v) @@ -77056,7 +77132,7 @@ func (ec *executionContext) _Document(ctx context.Context, sel ast.SelectionSet, if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "approver": + case "approvers": field := field innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { @@ -77065,7 +77141,7 @@ func (ec *executionContext) _Document(ctx context.Context, sel ast.SelectionSet, ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Document_approver(ctx, field, obj) + res = ec._Document_approvers(ctx, field, obj) if res == graphql.Null { atomic.AddUint32(&fs.Invalids, 1) } @@ -77475,7 +77551,7 @@ func (ec *executionContext) _DocumentVersion(ctx context.Context, sel ast.Select if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } - case "approver": + case "approvers": field := field innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { @@ -77484,7 +77560,7 @@ func (ec *executionContext) _DocumentVersion(ctx context.Context, sel ast.Select ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._DocumentVersion_approver(ctx, field, obj) + res = ec._DocumentVersion_approvers(ctx, field, obj) if res == graphql.Null { atomic.AddUint32(&fs.Invalids, 1) } diff --git a/pkg/server/api/console/v1/types/document.go b/pkg/server/api/console/v1/types/document.go index 6c4443e40..135d104fc 100644 --- a/pkg/server/api/console/v1/types/document.go +++ b/pkg/server/api/console/v1/types/document.go @@ -80,9 +80,6 @@ func NewDocument(document *coredata.Document) *Document { Organization: &Organization{ ID: document.OrganizationID, }, - Approver: &Profile{ - ID: document.ApproverID, - }, DocumentType: document.DocumentType, Classification: document.Classification, CurrentPublishedVersion: document.CurrentPublishedVersion, diff --git a/pkg/server/api/console/v1/types/document_version.go b/pkg/server/api/console/v1/types/document_version.go index 4fbf42b45..3c6ecc407 100644 --- a/pkg/server/api/console/v1/types/document_version.go +++ b/pkg/server/api/console/v1/types/document_version.go @@ -76,9 +76,6 @@ func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVers Document: &Document{ ID: documentVersion.DocumentID, }, - Approver: &Profile{ - ID: documentVersion.ApproverID, - }, Version: documentVersion.VersionNumber, Title: documentVersion.Title, Content: documentVersion.Content, diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index ab62602c7..324c67d95 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -369,7 +369,7 @@ type CreateDocumentInput struct { OrganizationID gid.GID `json:"organizationId"` Title string `json:"title"` Content string `json:"content"` - ApproverID gid.GID `json:"approverId"` + ApproverIds []gid.GID `json:"approverIds"` DocumentType coredata.DocumentType `json:"documentType"` Classification coredata.DocumentClassification `json:"classification"` TrustCenterVisibility *coredata.TrustCenterVisibility `json:"trustCenterVisibility,omitempty"` @@ -1129,7 +1129,7 @@ type Document struct { Classification coredata.DocumentClassification `json:"classification"` CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"` TrustCenterVisibility coredata.TrustCenterVisibility `json:"trustCenterVisibility"` - Approver *Profile `json:"approver"` + Approvers *ProfileConnection `json:"approvers"` Organization *Organization `json:"organization"` Versions *DocumentVersionConnection `json:"versions"` Controls *ControlConnection `json:"controls"` @@ -1159,7 +1159,7 @@ type DocumentVersion struct { Changelog string `json:"changelog"` Title string `json:"title"` Classification coredata.DocumentClassification `json:"classification"` - Approver *Profile `json:"approver"` + Approvers *ProfileConnection `json:"approvers"` Signatures *DocumentVersionSignatureConnection `json:"signatures"` Signed bool `json:"signed"` PublishedAt *time.Time `json:"publishedAt,omitempty"` @@ -2036,7 +2036,7 @@ type UpdateDocumentInput struct { ID gid.GID `json:"id"` Title *string `json:"title,omitempty"` Content *string `json:"content,omitempty"` - ApproverID *gid.GID `json:"approverId,omitempty"` + ApproverIds []gid.GID `json:"approverIds,omitempty"` DocumentType *coredata.DocumentType `json:"documentType,omitempty"` Classification *coredata.DocumentClassification `json:"classification,omitempty"` TrustCenterVisibility *coredata.TrustCenterVisibility `json:"trustCenterVisibility,omitempty"` diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index d6cd5840b..e683036e6 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -862,23 +862,39 @@ func (r *datumConnectionResolver) TotalCount(ctx context.Context, obj *types.Dat panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) } -// Approver is the resolver for the approver field. -func (r *documentResolver) Approver(ctx context.Context, obj *types.Document) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { +// Approvers is the resolver for the approvers field. +func (r *documentResolver) Approvers(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { return nil, err } - approver, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot get approver: %w", err)) + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil } - return types.NewProfile(approver), nil + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) + pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) + } + + c := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.Documents.ListApprovers(ctx, obj.ID, c) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document approvers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(p, r, obj.ID, nil), nil } // Organization is the resolver for the organization field. @@ -1031,23 +1047,39 @@ func (r *documentVersionResolver) Document(ctx context.Context, obj *types.Docum return types.NewDocument(document), nil } -// Approver is the resolver for the approver field. -func (r *documentVersionResolver) Approver(ctx context.Context, obj *types.DocumentVersion) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { +// Approvers is the resolver for the approvers field. +func (r *documentVersionResolver) Approvers(ctx context.Context, obj *types.DocumentVersion, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ProfileOrderBy) (*types.ProfileConnection, error) { + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileList); err != nil { return nil, err } - approver, err := r.iam.OrganizationService.GetProfile(ctx, obj.Approver.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot get approver: %w", err)) + if gqlutils.OnlyTotalCountSelected(ctx) { + return &types.ProfileConnection{ + Resolver: r, + ParentID: obj.ID, + }, nil } - return types.NewProfile(approver), nil + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy.Field = coredata.MembershipProfileOrderField(orderBy.Field) + pageOrderBy.Direction = page.OrderDirection(orderBy.Direction) + } + + c := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.Documents.ListVersionApprovers(ctx, obj.ID, c) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list document version approvers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfileConnection(p, r, obj.ID, nil), nil } // Signatures is the resolver for the signatures field. @@ -3528,7 +3560,7 @@ func (r *mutationResolver) CreateDocument(ctx context.Context, input types.Creat OrganizationID: input.OrganizationID, DocumentType: input.DocumentType, Title: input.Title, - ApproverID: input.ApproverID, + ApproverIDs: input.ApproverIds, Content: input.Content, Classification: input.Classification, TrustCenterVisibility: input.TrustCenterVisibility, @@ -3562,7 +3594,7 @@ func (r *mutationResolver) UpdateDocument(ctx context.Context, input types.Updat probo.UpdateDocumentRequest{ DocumentID: input.ID, Title: input.Title, - ApproverID: input.ApproverID, + ApproverIDs: input.ApproverIds, Classification: input.Classification, DocumentType: input.DocumentType, TrustCenterVisibility: input.TrustCenterVisibility, @@ -6576,6 +6608,20 @@ func (r *profileConnectionResolver) TotalCount(ctx context.Context, obj *types.P panic(fmt.Errorf("cannot count profiles: %w", err)) } return count, nil + case *documentResolver: + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + count, err := prb.Documents.CountApprovers(ctx, obj.ParentID) + if err != nil { + panic(fmt.Errorf("cannot count document approvers: %w", err)) + } + return count, nil + case *documentVersionResolver: + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + count, err := prb.Documents.CountVersionApprovers(ctx, obj.ParentID) + if err != nil { + panic(fmt.Errorf("cannot count document version approvers: %w", err)) + } + return count, nil } panic(fmt.Errorf("not implemented: TotalCount for parent type %T", obj.Resolver)) diff --git a/pkg/server/api/mcp/v1/helpers.go b/pkg/server/api/mcp/v1/helpers.go new file mode 100644 index 000000000..33d41e8a7 --- /dev/null +++ b/pkg/server/api/mcp/v1/helpers.go @@ -0,0 +1,36 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package mcp_v1 + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" +) + +func allApproversCursor() *page.Cursor[coredata.MembershipProfileOrderField] { + return page.NewCursor(100, nil, page.Head, page.OrderBy[coredata.MembershipProfileOrderField]{ + Field: coredata.MembershipProfileOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + }) +} + +func profileIDs(p *page.Page[*coredata.MembershipProfile, coredata.MembershipProfileOrderField]) []gid.GID { + ids := make([]gid.GID, len(p.Data)) + for i, profile := range p.Data { + ids[i] = profile.ID + } + return ids +} diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go index 5cfd97f1f..ff1ed0e6f 100644 --- a/pkg/server/api/mcp/v1/schema.resolvers.go +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -1553,12 +1553,21 @@ func (r *Resolver) ListDocumentsTool(ctx context.Context, req *mcp.CallToolReque documentFilter = coredata.NewDocumentFilter(query) } - page, err := prb.Documents.ListByOrganizationID(ctx, input.OrganizationID, cursor, documentFilter) + docPage, err := prb.Documents.ListByOrganizationID(ctx, input.OrganizationID, cursor, documentFilter) if err != nil { panic(fmt.Errorf("cannot list organization documents: %w", err)) } - return nil, types.NewListDocumentsOutput(page), nil + approverIDsMap := make(map[gid.GID][]gid.GID) + for _, d := range docPage.Data { + approverPage, err := prb.Documents.ListApprovers(ctx, d.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document approvers: %w", err)) + } + approverIDsMap[d.ID] = profileIDs(approverPage) + } + + return nil, types.NewListDocumentsOutput(docPage, approverIDsMap), nil } func (r *Resolver) GetDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentInput) (*mcp.CallToolResult, types.GetDocumentOutput, error) { @@ -1571,8 +1580,13 @@ func (r *Resolver) GetDocumentTool(ctx context.Context, req *mcp.CallToolRequest panic(fmt.Errorf("cannot get document: %w", err)) } + approverPage, err := prb.Documents.ListApprovers(ctx, input.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document approvers: %w", err)) + } + return nil, types.GetDocumentOutput{ - Document: types.NewDocument(document), + Document: types.NewDocument(document, profileIDs(approverPage)), }, nil } @@ -1592,7 +1606,7 @@ func (r *Resolver) AddDocumentTool(ctx context.Context, req *mcp.CallToolRequest OrganizationID: input.OrganizationID, Title: input.Title, Content: input.Content, - ApproverID: input.ApproverID, + ApproverIDs: input.ApproverIds, Classification: input.Classification, DocumentType: input.DocumentType, TrustCenterVisibility: trustCenterVisibility, @@ -1602,7 +1616,7 @@ func (r *Resolver) AddDocumentTool(ctx context.Context, req *mcp.CallToolRequest panic(fmt.Errorf("cannot create document: %w", err)) } - return nil, types.NewAddDocumentOutput(document, documentVersion), nil + return nil, types.NewAddDocumentOutput(document, documentVersion, input.ApproverIds, input.ApproverIds), nil } func (r *Resolver) UpdateDocumentTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateDocumentInput) (*mcp.CallToolResult, types.UpdateDocumentOutput, error) { @@ -1615,7 +1629,7 @@ func (r *Resolver) UpdateDocumentTool(ctx context.Context, req *mcp.CallToolRequ probo.UpdateDocumentRequest{ DocumentID: input.ID, Title: input.Title, - ApproverID: input.ApproverID, + ApproverIDs: input.ApproverIds, Classification: input.Classification, DocumentType: input.DocumentType, TrustCenterVisibility: input.TrustCenterVisibility, @@ -1625,8 +1639,13 @@ func (r *Resolver) UpdateDocumentTool(ctx context.Context, req *mcp.CallToolRequ panic(fmt.Errorf("cannot update document: %w", err)) } + approverPage, err := svc.Documents.ListApprovers(ctx, input.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document approvers: %w", err)) + } + return nil, types.UpdateDocumentOutput{ - Document: types.NewDocument(document), + Document: types.NewDocument(document, profileIDs(approverPage)), }, nil } @@ -1647,12 +1666,21 @@ func (r *Resolver) ListDocumentVersionsTool(ctx context.Context, req *mcp.CallTo cursor := types.NewCursor(input.Size, input.Cursor, pageOrderBy) svc := r.ProboService(ctx, input.DocumentID) - page, err := svc.Documents.ListVersions(ctx, input.DocumentID, cursor, coredata.NewDocumentVersionFilter()) + versionPage, err := svc.Documents.ListVersions(ctx, input.DocumentID, cursor, coredata.NewDocumentVersionFilter()) if err != nil { panic(fmt.Errorf("cannot list document versions: %w", err)) } - return nil, types.NewListDocumentVersionsOutput(page), nil + approverIDsMap := make(map[gid.GID][]gid.GID) + for _, v := range versionPage.Data { + approverPage, err := svc.Documents.ListVersionApprovers(ctx, v.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document version approvers: %w", err)) + } + approverIDsMap[v.ID] = profileIDs(approverPage) + } + + return nil, types.NewListDocumentVersionsOutput(versionPage, approverIDsMap), nil } func (r *Resolver) GetDocumentVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetDocumentVersionInput) (*mcp.CallToolResult, types.GetDocumentVersionOutput, error) { @@ -1665,8 +1693,13 @@ func (r *Resolver) GetDocumentVersionTool(ctx context.Context, req *mcp.CallTool panic(fmt.Errorf("cannot get document version: %w", err)) } + approverPage, err := svc.Documents.ListVersionApprovers(ctx, input.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document version approvers: %w", err)) + } + return nil, types.GetDocumentVersionOutput{ - DocumentVersion: types.NewDocumentVersion(version), + DocumentVersion: types.NewDocumentVersion(version, profileIDs(approverPage)), }, nil } @@ -1680,8 +1713,13 @@ func (r *Resolver) CreateDraftDocumentVersionTool(ctx context.Context, req *mcp. panic(fmt.Errorf("cannot create draft document version: %w", err)) } + approverPage, err := svc.Documents.ListVersionApprovers(ctx, draftVersion.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document version approvers: %w", err)) + } + return nil, types.CreateDraftDocumentVersionOutput{ - DocumentVersion: types.NewDocumentVersion(draftVersion), + DocumentVersion: types.NewDocumentVersion(draftVersion, profileIDs(approverPage)), }, nil } @@ -1701,8 +1739,13 @@ func (r *Resolver) UpdateDocumentVersionTool(ctx context.Context, req *mcp.CallT panic(fmt.Errorf("cannot update document version: %w", err)) } + versionApproverPage, err := svc.Documents.ListVersionApprovers(ctx, documentVersion.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document version approvers: %w", err)) + } + return nil, types.UpdateDocumentVersionOutput{ - DocumentVersion: types.NewDocumentVersion(documentVersion), + DocumentVersion: types.NewDocumentVersion(documentVersion, profileIDs(versionApproverPage)), }, nil } @@ -1718,9 +1761,19 @@ func (r *Resolver) PublishDocumentVersionTool(ctx context.Context, req *mcp.Call panic(fmt.Errorf("cannot publish document version: %w", err)) } + docApproverPage, err := svc.Documents.ListApprovers(ctx, document.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document approvers: %w", err)) + } + + versionApproverPage, err := svc.Documents.ListVersionApprovers(ctx, documentVersion.ID, allApproversCursor()) + if err != nil { + panic(fmt.Errorf("cannot list document version approvers: %w", err)) + } + return nil, types.PublishDocumentVersionOutput{ - Document: types.NewDocument(document), - DocumentVersion: types.NewDocumentVersion(documentVersion), + Document: types.NewDocument(document, profileIDs(docApproverPage)), + DocumentVersion: types.NewDocumentVersion(documentVersion, profileIDs(versionApproverPage)), }, nil } diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml index 994cbc6fe..befa7d42a 100644 --- a/pkg/server/api/mcp/v1/specification.yaml +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -3649,7 +3649,7 @@ components: required: - id - organization_id - - owner_id + - approver_ids - title - document_type - classification @@ -3663,9 +3663,11 @@ components: organization_id: $ref: "#/components/schemas/GID" description: Organization ID - owner_id: - $ref: "#/components/schemas/GID" - description: Owner ID + approver_ids: + type: array + items: + $ref: "#/components/schemas/GID" + description: Approver IDs title: type: string description: Document title @@ -3699,7 +3701,7 @@ components: - organization_id - document_id - title - - owner_id + - approver_ids - version_number - classification - content @@ -3720,9 +3722,11 @@ components: title: type: string description: Document version title - owner_id: - $ref: "#/components/schemas/GID" - description: Owner ID + approver_ids: + type: array + items: + $ref: "#/components/schemas/GID" + description: Approver IDs version_number: type: integer description: Version number @@ -3864,7 +3868,7 @@ components: - organization_id - title - content - - owner_id + - approver_ids - classification - document_type properties: @@ -3877,9 +3881,11 @@ components: content: type: string description: Document content - owner_id: - $ref: "#/components/schemas/GID" - description: Owner ID + approver_ids: + type: array + items: + $ref: "#/components/schemas/GID" + description: Approver IDs classification: $ref: "#/components/schemas/DocumentClassification" description: Document classification @@ -3912,9 +3918,11 @@ components: title: type: string description: Document title - owner_id: - $ref: "#/components/schemas/GID" - description: Owner ID + approver_ids: + type: array + items: + $ref: "#/components/schemas/GID" + description: Approver IDs classification: $ref: "#/components/schemas/DocumentClassification" description: Document classification diff --git a/pkg/server/api/mcp/v1/types/document.go b/pkg/server/api/mcp/v1/types/document.go index a305ffae9..1047f2f2f 100644 --- a/pkg/server/api/mcp/v1/types/document.go +++ b/pkg/server/api/mcp/v1/types/document.go @@ -16,14 +16,15 @@ package types import ( "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" ) -func NewDocument(d *coredata.Document) *Document { +func NewDocument(d *coredata.Document, approverIDs []gid.GID) *Document { return &Document{ ID: d.ID, OrganizationID: d.OrganizationID, - ApproverID: d.ApproverID, + ApproverIds: approverIDs, Title: d.Title, DocumentType: d.DocumentType, Classification: d.Classification, @@ -34,10 +35,10 @@ func NewDocument(d *coredata.Document) *Document { } } -func NewListDocumentsOutput(documentPage *page.Page[*coredata.Document, coredata.DocumentOrderField]) ListDocumentsOutput { +func NewListDocumentsOutput(documentPage *page.Page[*coredata.Document, coredata.DocumentOrderField], approverIDsMap map[gid.GID][]gid.GID) ListDocumentsOutput { documents := make([]*Document, 0, len(documentPage.Data)) for _, d := range documentPage.Data { - documents = append(documents, NewDocument(d)) + documents = append(documents, NewDocument(d, approverIDsMap[d.ID])) } var nextCursor *page.CursorKey @@ -52,20 +53,20 @@ func NewListDocumentsOutput(documentPage *page.Page[*coredata.Document, coredata } } -func NewAddDocumentOutput(doc *coredata.Document, docVersion *coredata.DocumentVersion) AddDocumentOutput { +func NewAddDocumentOutput(doc *coredata.Document, docVersion *coredata.DocumentVersion, docApproverIDs []gid.GID, versionApproverIDs []gid.GID) AddDocumentOutput { return AddDocumentOutput{ - Document: NewDocument(doc), - DocumentVersion: NewDocumentVersion(docVersion), + Document: NewDocument(doc, docApproverIDs), + DocumentVersion: NewDocumentVersion(docVersion, versionApproverIDs), } } -func NewDocumentVersion(dv *coredata.DocumentVersion) *DocumentVersion { +func NewDocumentVersion(dv *coredata.DocumentVersion, approverIDs []gid.GID) *DocumentVersion { return &DocumentVersion{ ID: dv.ID, OrganizationID: dv.OrganizationID, DocumentID: dv.DocumentID, Title: dv.Title, - ApproverID: dv.ApproverID, + ApproverIds: approverIDs, VersionNumber: dv.VersionNumber, Classification: dv.Classification, Content: dv.Content, @@ -77,10 +78,10 @@ func NewDocumentVersion(dv *coredata.DocumentVersion) *DocumentVersion { } } -func NewListDocumentVersionsOutput(versionPage *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField]) ListDocumentVersionsOutput { +func NewListDocumentVersionsOutput(versionPage *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField], approverIDsMap map[gid.GID][]gid.GID) ListDocumentVersionsOutput { versions := make([]*DocumentVersion, 0, len(versionPage.Data)) for _, v := range versionPage.Data { - versions = append(versions, NewDocumentVersion(v)) + versions = append(versions, NewDocumentVersion(v, approverIDsMap[v.ID])) } var nextCursor *page.CursorKey diff --git a/pkg/server/api/mcp/v1/types/types.go b/pkg/server/api/mcp/v1/types/types.go index e51f33abd..9b7e8e3b1 100644 --- a/pkg/server/api/mcp/v1/types/types.go +++ b/pkg/server/api/mcp/v1/types/types.go @@ -25,8 +25,8 @@ var ( AddControlToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"control":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Control description"},"exclusion_justification":{"description":"Exclusion justification"},"framework_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Control name"},"organization_id":{"type":"string","format":"string"},"section_title":{"type":"string","description":"Section title"},"status":{"type":"string","enum":["INCLUDED","EXCLUDED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","framework_id","section_title","name","status","created_at","updated_at"]}},"required":["control"]}`) AddDatumToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"data_classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"name":{"type":"string","description":"Datum name"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"vendor_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Vendor IDs"}},"required":["organization_id","name","data_classification","owner_id"]}`) AddDatumToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"datum":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"data_classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Datum name"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","data_classification","owner_id","created_at","updated_at"]}},"required":["datum"]}`) - AddDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]}},"required":["organization_id","title","content","approver_id","classification","document_type"]}`) - AddDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"]},"document_version":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document","document_version"]}`) + AddDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]}},"required":["organization_id","title","content","approver_ids","classification","document_type"]}`) + AddDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_ids","title","document_type","classification","trust_center_visibility","created_at","updated_at"]},"document_version":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document","document_version"]}`) AddFrameworkToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"description":{"type":"string","description":"Framework description"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"}},"required":["organization_id","name"]}`) AddFrameworkToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"framework":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","created_at","updated_at"]}},"required":["framework"]}`) AddMeasureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"category":{"type":"string","description":"Measure category"},"description":{"type":"string","description":"Measure description"},"name":{"type":"string","description":"Measure name"},"organization_id":{"type":"string","format":"string"}},"required":["organization_id","name","category"]}`) @@ -48,7 +48,7 @@ var ( CancelSignatureRequestToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_signature_id":{"type":"string","format":"string"}},"required":["document_version_signature_id"]}`) CancelSignatureRequestToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"deleted_document_version_signature_id":{"type":"string","format":"string"}},"required":["deleted_document_version_signature_id"]}`) CreateDraftDocumentVersionToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_id":{"type":"string","format":"string"}},"required":["document_id"]}`) - CreateDraftDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","owner_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) + CreateDraftDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) DeleteDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_id":{"type":"string","format":"string"}},"required":["document_id"]}`) DeleteDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"deleted_document_id":{"type":"string","format":"string"}},"required":["deleted_document_id"]}`) DeleteDraftDocumentVersionToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_id":{"type":"string","format":"string"}},"required":["document_version_id"]}`) @@ -70,11 +70,11 @@ var ( GetDatumToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) GetDatumToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"datum":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"data_classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Datum name"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","data_classification","owner_id","created_at","updated_at"]}},"required":["datum"]}`) GetDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) - GetDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"required":["document"]}`) + GetDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_ids","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"required":["document"]}`) GetDocumentVersionSignatureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) GetDocumentVersionSignatureToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_signature":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_version_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"requested_at":{"type":"string","description":"Requested timestamp","format":"date-time"},"signed_at":{"description":"Signed timestamp","format":"date-time"},"signed_by":{"type":"string","format":"string"},"state":{"type":"string","enum":["REQUESTED","SIGNED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","document_version_id","state","signed_by","requested_at","created_at","updated_at"]}},"required":["document_version_signature"]}`) GetDocumentVersionToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) - GetDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","owner_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) + GetDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) GetFrameworkToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) GetFrameworkToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"framework":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","created_at","updated_at"]}},"required":["framework"]}`) GetMeasureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"id":{"type":"string","format":"string"}},"required":["id"]}`) @@ -114,9 +114,9 @@ var ( ListDocumentVersionSignaturesToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"document_version_id":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"active_contract":{"type":"boolean","description":"Signatory contract status"},"states":{"type":"array","items":{"type":"string","enum":["REQUESTED","SIGNED"]},"description":"Signature states"}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","SIGNED_AT"]}},"required":["field","direction"]},"size":{"type":"integer","description":"Page size"}},"required":["document_version_id"]}`) ListDocumentVersionSignaturesToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_signatures":{"type":"array","items":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_version_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"requested_at":{"type":"string","description":"Requested timestamp","format":"date-time"},"signed_at":{"description":"Signed timestamp","format":"date-time"},"signed_by":{"type":"string","format":"string"},"state":{"type":"string","enum":["REQUESTED","SIGNED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","document_version_id","state","signed_by","requested_at","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["document_version_signatures"]}`) ListDocumentVersionsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"document_id":{"type":"string","format":"string"},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","VERSION"]}},"required":["field","direction"]},"size":{"type":"integer","description":"Page size"}},"required":["document_id"]}`) - ListDocumentVersionsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_versions":{"type":"array","items":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["document_versions"]}`) + ListDocumentVersionsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_versions":{"type":"array","items":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["document_versions"]}`) ListDocumentsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"trust_center_visibilities":{"type":"array","items":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"description":"Trust center visibilities"}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","TITLE","DOCUMENT_TYPE"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) - ListDocumentsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"documents":{"type":"array","items":{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["documents"]}`) + ListDocumentsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"documents":{"type":"array","items":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_ids","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["documents"]}`) ListFrameworksToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) ListFrameworksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"frameworks":{"type":"array","items":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["frameworks"]}`) ListMeasuresToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","NAME"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) @@ -142,7 +142,7 @@ var ( ListVendorsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","UPDATED_AT","NAME"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) ListVendorsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"next_cursor":{"type":"string","format":"string"},"vendors":{"type":"array","items":{"type":"object","properties":{"business_associate_agreement_url":{"description":"Business associate agreement URL"},"business_owner_id":{"description":"Business owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"category":{"type":"string","description":"Vendor category","enum":["ANALYTICS","CLOUD_MONITORING","CLOUD_PROVIDER","COLLABORATION","CUSTOMER_SUPPORT","DATA_STORAGE_AND_PROCESSING","DOCUMENT_MANAGEMENT","EMPLOYEE_MANAGEMENT","ENGINEERING","FINANCE","IDENTITY_PROVIDER","IT","MARKETING","OFFICE_OPERATIONS","OTHER","PASSWORD_MANAGEMENT","PRODUCT_AND_DESIGN","PROFESSIONAL_SERVICES","RECRUITING","SALES","SECURITY","VERSION_CONTROL"]},"certifications":{"type":"array","items":{"type":"string"},"description":"Certifications"},"countries":{"type":"array","items":{"type":"string"},"description":"Countries (ISO 3166-1 alpha-2 country codes)"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"data_processing_agreement_url":{"description":"Data processing agreement URL"},"description":{"description":"Vendor description"},"headquarter_address":{"description":"Headquarter address"},"id":{"type":"string","format":"string"},"legal_name":{"description":"Legal name"},"name":{"type":"string","description":"Vendor name"},"organization_id":{"type":"string","format":"string"},"privacy_policy_url":{"description":"Privacy policy URL"},"security_owner_id":{"description":"Security owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"security_page_url":{"description":"Security page URL"},"service_level_agreement_url":{"description":"Service level agreement URL"},"status_page_url":{"description":"Status page URL"},"subprocessors_list_url":{"description":"Subprocessors list URL"},"terms_of_service_url":{"description":"Terms of service URL"},"trust_page_url":{"description":"Trust page URL"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"website_url":{"description":"Website URL"}},"required":["id","name","organization_id","category","created_at","updated_at"]}}},"required":["vendors"]}`) PublishDocumentVersionToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"document_id":{"type":"string","format":"string"}},"required":["document_id"]}`) - PublishDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","owner_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"]},"document_version":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","owner_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document","document_version"]}`) + PublishDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_ids","title","document_type","classification","trust_center_visibility","created_at","updated_at"]},"document_version":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document","document_version"]}`) RequestDocumentVersionSignatureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_id":{"type":"string","format":"string"},"signatory_id":{"type":"string","format":"string"}},"required":["document_version_id","signatory_id"]}`) RequestDocumentVersionSignatureToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version_signature":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_version_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"requested_at":{"type":"string","description":"Requested timestamp","format":"date-time"},"signed_at":{"description":"Signed timestamp","format":"date-time"},"signed_by":{"type":"string","format":"string"},"state":{"type":"string","enum":["REQUESTED","SIGNED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","document_version_id","state","signed_by","requested_at","created_at","updated_at"]}},"required":["document_version_signature"]}`) TakeSnapshotToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"description":{"type":"string","description":"Snapshot description"},"name":{"type":"string","description":"Snapshot name"},"organization_id":{"type":"string","format":"string"},"type":{"type":"string","enum":["RISKS","VENDORS","ASSETS","DATA","NONCONFORMITIES","OBLIGATIONS","CONTINUAL_IMPROVEMENTS","PROCESSING_ACTIVITIES","STATES_OF_APPLICABILITY"]}},"required":["organization_id","name","type"]}`) @@ -167,10 +167,10 @@ var ( UpdateControlToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"control":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Control description"},"exclusion_justification":{"description":"Exclusion justification"},"framework_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Control name"},"organization_id":{"type":"string","format":"string"},"section_title":{"type":"string","description":"Section title"},"status":{"type":"string","enum":["INCLUDED","EXCLUDED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","framework_id","section_title","name","status","created_at","updated_at"]}},"required":["control"]}`) UpdateDatumToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"data_classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Datum name"},"owner_id":{"description":"Owner ID","anyOf":[{"type":"string","format":"string"},{"type":"null"}]},"vendor_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Vendor IDs"}},"required":["id"]}`) UpdateDatumToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"datum":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"data_classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Datum name"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"snapshot_id":{"description":"Snapshot ID","anyOf":[{"type":"string","format":"string"},{"type":"null","description":"No snapshot"}]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","data_classification","owner_id","created_at","updated_at"]}},"required":["datum"]}`) - UpdateDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]}},"required":["id"]}`) - UpdateDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"approver_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_id","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"required":["document"]}`) + UpdateDocumentToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]}},"required":["id"]}`) + UpdateDocumentToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"current_published_version":{"description":"Current published version number"},"document_type":{"type":"string","enum":["OTHER","ISMS","POLICY","PROCEDURE"]},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"title":{"type":"string","description":"Document title"},"trust_center_visibility":{"type":"string","enum":["NONE","PRIVATE","PUBLIC"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","approver_ids","title","document_type","classification","trust_center_visibility","created_at","updated_at"]}},"required":["document"]}`) UpdateDocumentVersionToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"content":{"type":"string","description":"Document content"},"document_version_id":{"type":"string","format":"string"}},"required":["document_version_id","content"]}`) - UpdateDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"owner_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","owner_id","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) + UpdateDocumentVersionToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"document_version":{"type":"object","properties":{"approver_ids":{"type":"array","items":{"type":"string","format":"string"},"description":"Approver IDs"},"changelog":{"type":"string","description":"Changelog"},"classification":{"type":"string","enum":["PUBLIC","INTERNAL","CONFIDENTIAL","SECRET"]},"content":{"type":"string","description":"Document content"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"document_id":{"type":"string","format":"string"},"id":{"type":"string","format":"string"},"organization_id":{"type":"string","format":"string"},"published_at":{"description":"Published timestamp","format":"date-time"},"status":{"type":"string","enum":["DRAFT","PUBLISHED"]},"title":{"type":"string","description":"Document version title"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"},"version_number":{"type":"integer","description":"Version number"}},"required":["id","organization_id","document_id","title","approver_ids","version_number","classification","content","changelog","status","created_at","updated_at"]}},"required":["document_version"]}`) UpdateFrameworkToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"}},"required":["id"]}`) UpdateFrameworkToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"framework":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","created_at","updated_at"]}},"required":["framework"]}`) UpdateMeasureToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"category":{"type":"string","description":"Measure category"},"description":{"description":"Measure description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Measure name"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]}},"required":["id"]}`) @@ -604,6 +604,8 @@ type AddDatumOutput struct { // AddDocumentInput represents the schema type AddDocumentInput struct { + // Approver IDs + ApproverIds []gid.GID `json:"approver_ids"` // Document classification Classification coredata.DocumentClassification `json:"classification"` // Document content @@ -612,8 +614,6 @@ type AddDocumentInput struct { DocumentType coredata.DocumentType `json:"document_type"` // Organization ID OrganizationID gid.GID `json:"organization_id"` - // Approver ID - ApproverID gid.GID `json:"approver_id"` // Document title Title string `json:"title"` // Trust center visibility @@ -1090,6 +1090,8 @@ type DeleteRiskOutput struct { // Document represents the schema type Document struct { + // Approver IDs + ApproverIds []gid.GID `json:"approver_ids"` // Document classification Classification coredata.DocumentClassification `json:"classification"` // Creation timestamp @@ -1102,8 +1104,6 @@ type Document struct { ID gid.GID `json:"id"` // Organization ID OrganizationID gid.GID `json:"organization_id"` - // Approver ID - ApproverID gid.GID `json:"approver_id"` // Document title Title string `json:"title"` // Trust center visibility @@ -1122,6 +1122,8 @@ type DocumentOrderBy struct { // DocumentVersion represents the schema type DocumentVersion struct { + // Approver IDs + ApproverIds []gid.GID `json:"approver_ids"` // Changelog Changelog string `json:"changelog"` // Document classification @@ -1136,8 +1138,6 @@ type DocumentVersion struct { ID gid.GID `json:"id"` // Organization ID OrganizationID gid.GID `json:"organization_id"` - // Approver ID - ApproverID gid.GID `json:"approver_id"` // Published timestamp PublishedAt *time.Time `json:"published_at,omitempty"` // Document status @@ -2342,14 +2342,14 @@ type UpdateDatumOutput struct { // UpdateDocumentInput represents the schema type UpdateDocumentInput struct { + // Approver IDs + ApproverIds []gid.GID `json:"approver_ids,omitempty"` // Document classification Classification *coredata.DocumentClassification `json:"classification,omitempty"` // Document type DocumentType *coredata.DocumentType `json:"document_type,omitempty"` // Document ID ID gid.GID `json:"id"` - // Approver ID - ApproverID *gid.GID `json:"approver_id,omitempty"` // Document title Title *string `json:"title,omitempty"` // Trust center visibility diff --git a/pkg/trust/document_service.go b/pkg/trust/document_service.go index 5cdbb01b0..534684a71 100644 --- a/pkg/trust/document_service.go +++ b/pkg/trust/document_service.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "io" + "strings" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" @@ -142,8 +143,8 @@ func (s *DocumentService) exportPDFData( ) ([]byte, error) { document := &coredata.Document{} version := &coredata.DocumentVersion{} - approver := &coredata.MembershipProfile{} organization := &coredata.Organization{} + var approverNames []string err := s.svc.pg.WithConn( ctx, @@ -160,8 +161,19 @@ func (s *DocumentService) exportPDFData( return fmt.Errorf("cannot load latest published document version: %w", err) } - if err := approver.LoadByID(ctx, conn, s.svc.scope, document.ApproverID); err != nil { - return fmt.Errorf("cannot load document approver profile: %w", err) + // Load approvers + docApprovers := &coredata.DocumentApprovers{} + if err := docApprovers.LoadByDocumentID(ctx, conn, s.svc.scope, documentID); err != nil { + return fmt.Errorf("cannot load document approvers: %w", err) + } + + profiles := coredata.MembershipProfiles{} + if err := profiles.LoadByIDs(ctx, conn, s.svc.scope, docApprovers.ApproverProfileIDs()); err != nil { + return fmt.Errorf("cannot load document approver profiles: %w", err) + } + + for _, p := range profiles { + approverNames = append(approverNames, p.FullName) } if err := organization.LoadByID(ctx, conn, s.svc.scope, document.OrganizationID); err != nil { @@ -203,7 +215,7 @@ func (s *DocumentService) exportPDFData( Content: version.Content, Version: version.VersionNumber, Classification: classification, - Approver: approver.FullName, + Approver: strings.Join(approverNames, ", "), PublishedAt: version.PublishedAt, CompanyHorizontalLogoBase64: horizontalLogoBase64, }