From 4d2cb793b618afcdebf793c8b3c6be1c373dd044 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Wed, 25 Feb 2026 14:59:00 +0100 Subject: [PATCH] Add compliance page mailing list base Signed-off-by: Sacha Al Himdani --- ...cePageMailingListDeleteMutation.graphql.ts | 132 ++ ...mpliancePageMailingListFragment.graphql.ts | 234 +++ ...pliancePageMailingListPageQuery.graphql.ts | 327 +++ ...tPage_updateMailingListMutation.graphql.ts | 114 ++ .../CompliancePageMailingListQuery.graphql.ts | 275 +++ ...ncePageSubscriberDialogMutation.graphql.ts | 201 ++ .../compliance-page/CompliancePageLayout.tsx | 6 +- .../CompliancePageMailingListPage.tsx | 147 ++ .../CompliancePageMailingListPageLoader.tsx | 37 + .../_components/CompliancePageMailingList.tsx | 160 ++ .../NewCompliancePageSubscriberDialog.tsx | 114 ++ .../organizations/compliance-page/routes.ts | 5 + .../src/components/OrganizationSidebar.tsx | 148 +- ..._subscribeToMailingListMutation.graphql.ts | 111 + ...ubscribeFromMailingListMutation.graphql.ts | 96 + apps/trust/src/layouts/MainLayout.tsx | 3 +- apps/trust/src/queries/TrustGraph.ts | 9 + .../TrustGraphCurrentQuery.graphql.ts | 188 +- pkg/coredata/entity_type_reg.go | 6 + pkg/coredata/mailing_list.go | 170 ++ pkg/coredata/mailing_list_subscriber.go | 316 +++ .../mailing_list_subscriber_order_field.go | 36 + .../mailing_list_subscriber_status.go | 58 + pkg/coredata/migrations/20260225T000000Z.sql | 50 + pkg/coredata/trust_center.go | 7 + pkg/iam/organization_service.go | 12 + pkg/mailman/errors.go | 23 + pkg/mailman/service.go | 218 ++ pkg/probo/actions.go | 8 + pkg/probo/trust_center_service.go | 33 + pkg/probod/probod.go | 4 + pkg/server/api/api.go | 5 + pkg/server/api/console/v1/graphql_handler.go | 4 +- pkg/server/api/console/v1/resolver.go | 9 +- pkg/server/api/console/v1/schema.graphql | 94 +- pkg/server/api/console/v1/schema/schema.go | 1805 ++++++++++++++++- .../api/console/v1/types/mailing_list.go | 26 + .../v1/types/mailing_list_subscriber.go | 71 + .../api/console/v1/types/trust_center.go | 24 + pkg/server/api/console/v1/types/types.go | 73 +- pkg/server/api/console/v1/v1_resolver.go | 126 ++ pkg/server/api/trust/v1/graphql_handler.go | 4 +- pkg/server/api/trust/v1/resolver.go | 15 +- pkg/server/api/trust/v1/schema.graphql | 38 + pkg/server/api/trust/v1/schema/schema.go | 762 +++++++ .../trust/v1/types/mailing_list_subscriber.go | 44 + pkg/server/api/trust/v1/types/types.go | 9 + pkg/server/api/trust/v1/v1_resolver.go | 82 + pkg/server/server.go | 3 + pkg/trust/trust_center_service.go | 33 + 50 files changed, 6365 insertions(+), 110 deletions(-) create mode 100644 apps/console/src/__generated__/core/CompliancePageMailingListDeleteMutation.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageMailingListFragment.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageMailingListPageQuery.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageMailingListQuery.graphql.ts create mode 100644 apps/console/src/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql.ts create mode 100644 apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPage.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/mailing-list/_components/CompliancePageMailingList.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/mailing-list/_components/NewCompliancePageSubscriberDialog.tsx create mode 100644 apps/trust/src/components/__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql.ts create mode 100644 apps/trust/src/components/__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql.ts create mode 100644 pkg/coredata/mailing_list.go create mode 100644 pkg/coredata/mailing_list_subscriber.go create mode 100644 pkg/coredata/mailing_list_subscriber_order_field.go create mode 100644 pkg/coredata/mailing_list_subscriber_status.go create mode 100644 pkg/coredata/migrations/20260225T000000Z.sql create mode 100644 pkg/mailman/errors.go create mode 100644 pkg/mailman/service.go create mode 100644 pkg/server/api/console/v1/types/mailing_list.go create mode 100644 pkg/server/api/console/v1/types/mailing_list_subscriber.go create mode 100644 pkg/server/api/trust/v1/types/mailing_list_subscriber.go diff --git a/apps/console/src/__generated__/core/CompliancePageMailingListDeleteMutation.graphql.ts b/apps/console/src/__generated__/core/CompliancePageMailingListDeleteMutation.graphql.ts new file mode 100644 index 000000000..1b5e1a7cd --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageMailingListDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<72b5471e12ed01f059b4192ee5827a1a>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteMailingListSubscriberInput = { + id: string; +}; +export type CompliancePageMailingListDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteMailingListSubscriberInput; +}; +export type CompliancePageMailingListDeleteMutation$data = { + readonly deleteMailingListSubscriber: { + readonly deletedMailingListSubscriberId: string; + }; +}; +export type CompliancePageMailingListDeleteMutation = { + response: CompliancePageMailingListDeleteMutation$data; + variables: CompliancePageMailingListDeleteMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedMailingListSubscriberId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageMailingListDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteMailingListSubscriberPayload", + "kind": "LinkedField", + "name": "deleteMailingListSubscriber", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "CompliancePageMailingListDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteMailingListSubscriberPayload", + "kind": "LinkedField", + "name": "deleteMailingListSubscriber", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedMailingListSubscriberId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "65de43d2f00cd2c553aa72568f81eac2", + "id": null, + "metadata": {}, + "name": "CompliancePageMailingListDeleteMutation", + "operationKind": "mutation", + "text": "mutation CompliancePageMailingListDeleteMutation(\n $input: DeleteMailingListSubscriberInput!\n) {\n deleteMailingListSubscriber(input: $input) {\n deletedMailingListSubscriberId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "bb627bfeb68e7b72c337c40133ab63a8"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageMailingListFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageMailingListFragment.graphql.ts new file mode 100644 index 000000000..e5b3f5e04 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageMailingListFragment.graphql.ts @@ -0,0 +1,234 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type MailingListSubscriberStatus = "CONFIRMED" | "PENDING"; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageMailingListFragment$data = { + readonly id: string; + readonly mailingList: { + readonly id: string; + readonly subscribers: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly createdAt: string; + readonly email: string; + readonly fullName: string; + readonly id: string; + readonly status: MailingListSubscriberStatus; + }; + }>; + readonly pageInfo: { + readonly endCursor: string | null | undefined; + readonly hasNextPage: boolean; + }; + }; + } | null | undefined; + readonly " $fragmentType": "CompliancePageMailingListFragment"; +}; +export type CompliancePageMailingListFragment$key = { + readonly " $data"?: CompliancePageMailingListFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">; +}; + +import CompliancePageMailingListQuery_graphql from './CompliancePageMailingListQuery.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "mailingList", + "subscribers" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": "first", + "cursor": "after", + "direction": "forward", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "first", + "cursor": "after" + }, + "backward": null, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": CompliancePageMailingListQuery_graphql, + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "CompliancePageMailingListFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingList", + "kind": "LinkedField", + "name": "mailingList", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": "subscribers", + "args": null, + "concreteType": "MailingListSubscriberConnection", + "kind": "LinkedField", + "name": "__CompliancePageMailingList_subscribers_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriberEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v1/*: any*/) + ], + "type": "TrustCenter", + "abstractKey": null +}; +})(); + +(node as any).hash = "b1828ed7ded38bb3e3e150b0875ea0a6"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageMailingListPageQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageMailingListPageQuery.graphql.ts new file mode 100644 index 000000000..898112fd0 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageMailingListPageQuery.graphql.ts @@ -0,0 +1,327 @@ +/** + * @generated SignedSource<<7e30e59b53799ccae49199d7b46f0320>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageMailingListPageQuery$variables = { + organizationId: string; +}; +export type CompliancePageMailingListPageQuery$data = { + readonly organization: { + readonly __typename: "Organization"; + readonly compliancePage: { + readonly id: string; + readonly mailingList: { + readonly id: string; + readonly replyTo: string | null | undefined; + } | null | undefined; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">; + }; + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other"; + }; +}; +export type CompliancePageMailingListPageQuery = { + response: CompliancePageMailingListPageQuery$data; + variables: CompliancePageMailingListPageQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "replyTo", + "storageKey": null +}, +v5 = [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageMailingListPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "MailingList", + "kind": "LinkedField", + "name": "mailingList", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageMailingListFragment" + } + ], + "storageKey": null + }, + "action": "THROW" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageMailingListPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "MailingList", + "kind": "LinkedField", + "name": "mailingList", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": (v5/*: any*/), + "concreteType": "MailingListSubscriberConnection", + "kind": "LinkedField", + "name": "subscribers", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriberEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": "subscribers(first:20)" + }, + { + "alias": null, + "args": (v5/*: any*/), + "filters": null, + "handle": "connection", + "key": "CompliancePageMailingList_subscribers", + "kind": "LinkedHandle", + "name": "subscribers" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "8eb57a5cf8f8b1294da6aee3228176f3", + "id": null, + "metadata": {}, + "name": "CompliancePageMailingListPageQuery", + "operationKind": "query", + "text": "query CompliancePageMailingListPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n mailingList {\n id\n replyTo\n }\n ...CompliancePageMailingListFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageMailingListFragment on TrustCenter {\n mailingList {\n id\n subscribers(first: 20) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n fullName\n email\n status\n createdAt\n __typename\n }\n cursor\n }\n }\n }\n id\n}\n" + } +}; +})(); + +(node as any).hash = "0df7b2a8b9f0d2f421f354bd56773e43"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql.ts b/apps/console/src/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql.ts new file mode 100644 index 000000000..c25952529 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql.ts @@ -0,0 +1,114 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type UpdateMailingListInput = { + id: string; + replyTo?: string | null | undefined; +}; +export type CompliancePageMailingListPage_updateMailingListMutation$variables = { + input: UpdateMailingListInput; +}; +export type CompliancePageMailingListPage_updateMailingListMutation$data = { + readonly updateMailingList: { + readonly mailingList: { + readonly id: string; + readonly replyTo: string | null | undefined; + }; + }; +}; +export type CompliancePageMailingListPage_updateMailingListMutation = { + response: CompliancePageMailingListPage_updateMailingListMutation$data; + variables: CompliancePageMailingListPage_updateMailingListMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "UpdateMailingListPayload", + "kind": "LinkedField", + "name": "updateMailingList", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingList", + "kind": "LinkedField", + "name": "mailingList", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "replyTo", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageMailingListPage_updateMailingListMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageMailingListPage_updateMailingListMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "30b67540b107297075df95ee044714ea", + "id": null, + "metadata": {}, + "name": "CompliancePageMailingListPage_updateMailingListMutation", + "operationKind": "mutation", + "text": "mutation CompliancePageMailingListPage_updateMailingListMutation(\n $input: UpdateMailingListInput!\n) {\n updateMailingList(input: $input) {\n mailingList {\n id\n replyTo\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "0a1e6524623210147ba5db2affec456c"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageMailingListQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageMailingListQuery.graphql.ts new file mode 100644 index 000000000..f3f557fe0 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageMailingListQuery.graphql.ts @@ -0,0 +1,275 @@ +/** + * @generated SignedSource<<88648e469580db301e2d4105dae6a072>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageMailingListQuery$variables = { + after?: string | null | undefined; + first?: number | null | undefined; + id: string; +}; +export type CompliancePageMailingListQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">; + }; +}; +export type CompliancePageMailingListQuery = { + response: CompliancePageMailingListQuery$data; + variables: CompliancePageMailingListQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "after" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "first" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageMailingListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": (v2/*: any*/), + "kind": "FragmentSpread", + "name": "CompliancePageMailingListFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageMailingListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingList", + "kind": "LinkedField", + "name": "mailingList", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "MailingListSubscriberConnection", + "kind": "LinkedField", + "name": "subscribers", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriberEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v2/*: any*/), + "filters": null, + "handle": "connection", + "key": "CompliancePageMailingList_subscribers", + "kind": "LinkedHandle", + "name": "subscribers" + } + ], + "storageKey": null + } + ], + "type": "TrustCenter", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "409500a631f678d3b173a2feae512f71", + "id": null, + "metadata": {}, + "name": "CompliancePageMailingListQuery", + "operationKind": "query", + "text": "query CompliancePageMailingListQuery(\n $after: CursorKey = null\n $first: Int = 20\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompliancePageMailingListFragment_2HEEH6\n id\n }\n}\n\nfragment CompliancePageMailingListFragment_2HEEH6 on TrustCenter {\n mailingList {\n id\n subscribers(first: $first, after: $after) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n fullName\n email\n status\n createdAt\n __typename\n }\n cursor\n }\n }\n }\n id\n}\n" + } +}; +})(); + +(node as any).hash = "b1828ed7ded38bb3e3e150b0875ea0a6"; + +export default node; diff --git a/apps/console/src/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql.ts b/apps/console/src/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql.ts new file mode 100644 index 000000000..8ca24f89e --- /dev/null +++ b/apps/console/src/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql.ts @@ -0,0 +1,201 @@ +/** + * @generated SignedSource<<61ab320d87e6fb43d245dc070871a160>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type MailingListSubscriberStatus = "CONFIRMED" | "PENDING"; +export type CreateMailingListSubscriberInput = { + email: string; + fullName: string; + mailingListId: string; +}; +export type NewCompliancePageSubscriberDialogMutation$variables = { + connections: ReadonlyArray; + input: CreateMailingListSubscriberInput; +}; +export type NewCompliancePageSubscriberDialogMutation$data = { + readonly createMailingListSubscriber: { + readonly mailingListSubscriberEdge: { + readonly cursor: string; + readonly node: { + readonly createdAt: string; + readonly email: string; + readonly fullName: string; + readonly id: string; + readonly status: MailingListSubscriberStatus; + }; + }; + }; +}; +export type NewCompliancePageSubscriberDialogMutation = { + response: NewCompliancePageSubscriberDialogMutation$data; + variables: NewCompliancePageSubscriberDialogMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriberEdge", + "kind": "LinkedField", + "name": "mailingListSubscriberEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "NewCompliancePageSubscriberDialogMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateMailingListSubscriberPayload", + "kind": "LinkedField", + "name": "createMailingListSubscriber", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "NewCompliancePageSubscriberDialogMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateMailingListSubscriberPayload", + "kind": "LinkedField", + "name": "createMailingListSubscriber", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "mailingListSubscriberEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "6029b34056e1e0477db8bec0080b4ad1", + "id": null, + "metadata": {}, + "name": "NewCompliancePageSubscriberDialogMutation", + "operationKind": "mutation", + "text": "mutation NewCompliancePageSubscriberDialogMutation(\n $input: CreateMailingListSubscriberInput!\n) {\n createMailingListSubscriber(input: $input) {\n mailingListSubscriberEdge {\n cursor\n node {\n id\n fullName\n email\n status\n createdAt\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "fae262a9df20589ff2923f631692a6ce"; + +export default node; diff --git a/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx b/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx index 7d045155a..1b3e71ab2 100644 --- a/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx +++ b/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx @@ -1,6 +1,6 @@ import { usePageTitle } from "@probo/hooks"; import { useTranslate } from "@probo/i18n"; -import { Badge, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui"; +import { Badge, IconBell2, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui"; import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; import { Outlet } from "react-router"; import { graphql } from "relay-runtime"; @@ -84,6 +84,10 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery {__("Access")} + + + {__("Mailing List")} + diff --git a/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPage.tsx b/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPage.tsx new file mode 100644 index 000000000..7fd30cc9d --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPage.tsx @@ -0,0 +1,147 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Card, Field, IconPlusLarge, Spinner, useDialogRef } from "@probo/ui"; +import { useState } from "react"; +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { ConnectionHandler, graphql } from "relay-runtime"; + +import type { CompliancePageMailingListPage_updateMailingListMutation } from "#/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql"; +import type { CompliancePageMailingListPageQuery } from "#/__generated__/core/CompliancePageMailingListPageQuery.graphql"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; + +import { CompliancePageMailingList } from "./_components/CompliancePageMailingList"; +import { NewCompliancePageSubscriberDialog } from "./_components/NewCompliancePageSubscriberDialog"; + +export const compliancePageMailingListPageQuery = graphql` + query CompliancePageMailingListPageQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + __typename + ... on Organization { + compliancePage: trustCenter @required(action: THROW) { + id + mailingList { + id + replyTo + } + ...CompliancePageMailingListFragment + } + } + } + } +`; + +const updateMailingListMutation = graphql` + mutation CompliancePageMailingListPage_updateMailingListMutation($input: UpdateMailingListInput!) { + updateMailingList(input: $input) { + mailingList { + id + replyTo + } + } + } +`; + +export function CompliancePageMailingListPage(props: { + queryRef: PreloadedQuery; +}) { + const { queryRef } = props; + const { __ } = useTranslate(); + const dialogRef = useDialogRef(); + + const { organization } = usePreloadedQuery( + compliancePageMailingListPageQuery, + queryRef, + ); + + if (organization.__typename !== "Organization") { + throw new Error("invalid type for node"); + } + + const mailingList = organization.compliancePage.mailingList; + const mailingListId = mailingList?.id; + + const connectionId = mailingListId + ? ConnectionHandler.getConnectionID(mailingListId, "CompliancePageMailingList_subscribers") + : null; + + const [replyTo, setReplyTo] = useState(mailingList?.replyTo ?? ""); + + const [updateMailingList, isUpdating] = useMutationWithToasts( + updateMailingListMutation, + { + successMessage: __("Mailing list updated successfully"), + errorMessage: __("Failed to update mailing list"), + }, + ); + + const handleSaveReplyTo = () => { + if (!mailingListId) return; + void updateMailingList({ + variables: { + input: { + id: mailingListId, + replyTo: replyTo.trim() || null, + }, + }, + }); + }; + + return ( +
+ {mailingListId && ( + +
+

{__("Settings")}

+

+ {__("Configure how your mailing list behaves")} +

+
+
+
+ setReplyTo(e.target.value)} + /> +
+ +
+
+ )} + +
+
+
+

{__("Subscribers")}

+

+ {__("People subscribed to receive security and compliance updates")} +

+
+ {mailingListId && ( + + )} +
+ + +
+ + {mailingListId && connectionId && ( + + )} +
+ ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader.tsx b/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader.tsx new file mode 100644 index 000000000..1037e4ca6 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader.tsx @@ -0,0 +1,37 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import type { CompliancePageMailingListPageQuery } from "#/__generated__/core/CompliancePageMailingListPageQuery.graphql"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; + +import { + CompliancePageMailingListPage, + compliancePageMailingListPageQuery, +} from "./CompliancePageMailingListPage"; + +function CompliancePageMailingListPageQueryLoader() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] = useQueryLoader( + compliancePageMailingListPageQuery, + ); + + useEffect(() => { + if (!queryRef) { + loadQuery({ organizationId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function CompliancePageMailingListPageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/CompliancePageMailingList.tsx b/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/CompliancePageMailingList.tsx new file mode 100644 index 000000000..186a78b53 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/CompliancePageMailingList.tsx @@ -0,0 +1,160 @@ +import { useTranslate } from "@probo/i18n"; +import { Badge, Button, IconChevronDown, IconTrashCan, Spinner, Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui"; +import { usePaginationFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageMailingListDeleteMutation } from "#/__generated__/core/CompliancePageMailingListDeleteMutation.graphql"; +import type { CompliancePageMailingListFragment$key } from "#/__generated__/core/CompliancePageMailingListFragment.graphql"; +import type { CompliancePageMailingListQuery } from "#/__generated__/core/CompliancePageMailingListQuery.graphql"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; + +const deleteMutation = graphql` + mutation CompliancePageMailingListDeleteMutation( + $input: DeleteMailingListSubscriberInput! + $connections: [ID!]! + ) { + deleteMailingListSubscriber(input: $input) { + deletedMailingListSubscriberId @deleteEdge(connections: $connections) + } + } +`; + +const fragment = graphql` + fragment CompliancePageMailingListFragment on TrustCenter + @argumentDefinitions( + first: { type: Int, defaultValue: 20 } + after: { type: CursorKey, defaultValue: null } + ) + @refetchable(queryName: "CompliancePageMailingListQuery") { + mailingList { + id + subscribers( + first: $first + after: $after + ) @connection(key: "CompliancePageMailingList_subscribers") { + __id + pageInfo { + hasNextPage + endCursor + } + edges { + node { + id + fullName + email + status + createdAt + } + } + } + } + } +`; + +export function CompliancePageMailingList(props: { + fragmentRef: CompliancePageMailingListFragment$key; +}) { + const { fragmentRef } = props; + const { __ } = useTranslate(); + + const { + data, + hasNext, + loadNext, + isLoadingNext, + } = usePaginationFragment( + fragment, + fragmentRef, + ); + + const subscribers = data.mailingList?.subscribers; + + const [deleteSubscriber, isDeleting] = useMutationWithToasts( + deleteMutation, + { + successMessage: __("Subscriber removed successfully"), + errorMessage: __("Failed to delete subscriber"), + }, + ); + + const handleDelete = (id: string) => { + if (!subscribers) return; + void deleteSubscriber({ + variables: { + input: { id }, + connections: [subscribers.__id], + }, + }); + }; + + return ( + <> + {!subscribers || subscribers.edges.length === 0 + ? ( + + + + + + +
+ {__("No mailing list subscribers yet")} +
+ ) + : ( + <> + + + + + + + + + + + {subscribers.edges.map(({ node: subscriber }) => ( + + + + + + + + ))} + +
{__("Name")}{__("Email")}{__("Status")}{__("Subscribed on")} +
{subscriber.fullName}{subscriber.email} + + {subscriber.status === "CONFIRMED" ? __("Confirmed") : __("Pending")} + + + {new Date(subscriber.createdAt).toLocaleDateString()} + +
+ {hasNext && ( + + )} + + )} + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/NewCompliancePageSubscriberDialog.tsx b/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/NewCompliancePageSubscriberDialog.tsx new file mode 100644 index 000000000..511bcbcf0 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/mailing-list/_components/NewCompliancePageSubscriberDialog.tsx @@ -0,0 +1,114 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, Field, Spinner } from "@probo/ui"; +import { type DataID, graphql } from "relay-runtime"; +import { z } from "zod"; + +import type { NewCompliancePageSubscriberDialogMutation } from "#/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql"; +import { useFormWithSchema } from "#/hooks/useFormWithSchema"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; + +const createSubscriberMutation = graphql` + mutation NewCompliancePageSubscriberDialogMutation( + $input: CreateMailingListSubscriberInput! + $connections: [ID!]! + ) { + createMailingListSubscriber(input: $input) { + mailingListSubscriberEdge @prependEdge(connections: $connections) { + cursor + node { + id + fullName + email + status + createdAt + } + } + } + } +`; + +export function NewCompliancePageSubscriberDialog(props: { + mailingListId: string; + connectionId: DataID; + ref: DialogRef; +}) { + const { mailingListId, connectionId, ref } = props; + const { __ } = useTranslate(); + + const schema = z.object({ + fullName: z.string().min(1, __("Full name is required")).trim(), + email: z + .string() + .min(1, __("Email is required")) + .trim() + .email(__("Please enter a valid email address")), + }); + + const form = useFormWithSchema(schema, { + defaultValues: { fullName: "", email: "" }, + }); + + const [createSubscriber, isCreating] = useMutationWithToasts( + createSubscriberMutation, + { + successMessage: __("Subscriber added successfully"), + errorMessage: __("Failed to add subscriber"), + }, + ); + + const handleSubmit = async (data: z.infer) => { + await createSubscriber({ + variables: { + input: { + mailingListId, + fullName: data.fullName.trim(), + email: data.email.trim(), + }, + connections: connectionId ? [connectionId] : [], + }, + onCompleted: (_, errors) => { + if (errors?.length) return; + setTimeout(() => { + form.reset(); + ref.current?.close(); + }, 50); + setTimeout(() => { + form.reset(); + }, 300); + }, + }); + }; + + return ( + +
void form.handleSubmit(handleSubmit)(e)}> + +

+ {__("Add a person to receive security and compliance updates")} +

+ + +
+ + + +
+
+ ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/routes.ts b/apps/console/src/pages/organizations/compliance-page/routes.ts index b3d296fb6..697a3274b 100644 --- a/apps/console/src/pages/organizations/compliance-page/routes.ts +++ b/apps/console/src/pages/organizations/compliance-page/routes.ts @@ -57,6 +57,11 @@ export const compliancePageRoutes = [ Fallback: LinkCardSkeleton, Component: lazy(() => import("#/pages/organizations/compliance-page/access/CompliancePageAccessPageLoader")), }, + { + path: "mailing-list", + Fallback: LinkCardSkeleton, + Component: lazy(() => import("#/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader")), + }, ], }, ]; diff --git a/apps/trust/src/components/OrganizationSidebar.tsx b/apps/trust/src/components/OrganizationSidebar.tsx index 36f1491f1..3688b8304 100644 --- a/apps/trust/src/components/OrganizationSidebar.tsx +++ b/apps/trust/src/components/OrganizationSidebar.tsx @@ -5,6 +5,7 @@ import { UnAuthenticatedError } from "@probo/relay"; import { Button, Card, + IconBell2, IconBlock, IconLock, IconMedal, @@ -20,6 +21,8 @@ import type { TrustGraphCurrentQuery$data } from "#/queries/__generated__/TrustG import { getPathPrefix } from "#/utils/pathPrefix"; import type { OrganizationSidebar_requestAllAccessesMutation } from "./__generated__/OrganizationSidebar_requestAllAccessesMutation.graphql"; +import type { OrganizationSidebar_subscribeToMailingListMutation } from "./__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql"; +import type { OrganizationSidebar_unsubscribeFromMailingListMutation } from "./__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql"; import { FrameworkBadge } from "./FrameworkBadge"; const requestAllAccessesMutation = graphql` @@ -32,11 +35,35 @@ const requestAllAccessesMutation = graphql` } `; +const subscribeToMailingListMutation = graphql` + mutation OrganizationSidebar_subscribeToMailingListMutation { + subscribeToMailingList { + subscription { + id + email + createdAt + updatedAt + } + } + } +`; + +const unsubscribeFromMailingListMutation = graphql` + mutation OrganizationSidebar_unsubscribeFromMailingListMutation { + unsubscribeFromMailingList { + deletedMailingListSubscriberId @deleteRecord + } + } +`; + export function OrganizationSidebar({ trustCenter, + isAuthenticated, }: { trustCenter: TrustGraphCurrentQuery$data["currentTrustCenter"]; + isAuthenticated: boolean; }) { + const trustCenterId = trustCenter?.id; const { __ } = useTranslate(); const { toast } = useToast(); const theme = useSystemTheme(); @@ -51,6 +78,16 @@ export function OrganizationSidebar({ requestAllAccessesMutation, ); + const [subscribeToMailingList, isSubscribing] + = useMutation( + subscribeToMailingListMutation, + ); + + const [unsubscribeFromMailingList, isUnsubscribing] + = useMutation( + unsubscribeFromMailingListMutation, + ); + const handleRequestAllAccesses = () => { requestAllAccesses({ variables: {}, @@ -93,6 +130,71 @@ export function OrganizationSidebar({ }); }; + const handleSubscribe = () => { + subscribeToMailingList({ + variables: {}, + updater: (store, data) => { + const subscription = data?.subscribeToMailingList?.subscription; + if (!subscription?.id || !trustCenterId) return; + const trustCenterRecord = store.get(trustCenterId); + if (!trustCenterRecord) return; + const subscriptionRecord = store.get(subscription.id); + if (!subscriptionRecord) return; + trustCenterRecord.setLinkedRecord(subscriptionRecord, "viewerSubscription"); + }, + onCompleted: (_, errors) => { + if (errors?.length) { + toast({ + title: __("Error"), + description: formatError(__("Cannot subscribe"), errors), + variant: "error", + }); + return; + } + toast({ + title: __("Subscribed"), + description: __("You will be notified of security updates."), + variant: "success", + }); + }, + onError: (error) => { + toast({ + title: __("Error"), + description: error.message ?? __("Cannot subscribe"), + variant: "error", + }); + }, + }); + }; + + const handleUnsubscribe = () => { + unsubscribeFromMailingList({ + variables: {}, + onCompleted: (_, errors) => { + if (errors?.length) { + toast({ + title: __("Error"), + description: formatError(__("Cannot unsubscribe"), errors), + variant: "error", + }); + return; + } + toast({ + title: __("Unsubscribed"), + description: __("You will no longer receive security updates."), + variant: "success", + }); + }, + onError: (error) => { + toast({ + title: __("Error"), + description: error.message ?? __("Cannot unsubscribe"), + variant: "error", + }); + }, + }); + }; + if (!trustCenter) { return null; } @@ -188,15 +290,43 @@ export function OrganizationSidebar({ )} - + {/* Actions */} +
+ + {isAuthenticated && ( + trustCenter.viewerSubscription + ? ( + + ) + : ( + + ) + )} +
); diff --git a/apps/trust/src/components/__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql.ts b/apps/trust/src/components/__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql.ts new file mode 100644 index 000000000..8a0b3ba96 --- /dev/null +++ b/apps/trust/src/components/__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql.ts @@ -0,0 +1,111 @@ +/** + * @generated SignedSource<<80e6e1e3558d126472e7affb5b7b6cbd>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type OrganizationSidebar_subscribeToMailingListMutation$variables = Record; +export type OrganizationSidebar_subscribeToMailingListMutation$data = { + readonly subscribeToMailingList: { + readonly subscription: { + readonly createdAt: any; + readonly email: any; + readonly id: string; + readonly updatedAt: any; + }; + }; +}; +export type OrganizationSidebar_subscribeToMailingListMutation = { + response: OrganizationSidebar_subscribeToMailingListMutation$data; + variables: OrganizationSidebar_subscribeToMailingListMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "SubscribeToMailingListPayload", + "kind": "LinkedField", + "name": "subscribeToMailingList", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "subscription", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "OrganizationSidebar_subscribeToMailingListMutation", + "selections": (v0/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "OrganizationSidebar_subscribeToMailingListMutation", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "07d99ad1645ea1e8752678fd9b5e6748", + "id": null, + "metadata": {}, + "name": "OrganizationSidebar_subscribeToMailingListMutation", + "operationKind": "mutation", + "text": "mutation OrganizationSidebar_subscribeToMailingListMutation {\n subscribeToMailingList {\n subscription {\n id\n email\n createdAt\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "80d3e85f4a55a7de7b066959f4a457c8"; + +export default node; diff --git a/apps/trust/src/components/__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql.ts b/apps/trust/src/components/__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql.ts new file mode 100644 index 000000000..3e84561d4 --- /dev/null +++ b/apps/trust/src/components/__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql.ts @@ -0,0 +1,96 @@ +/** + * @generated SignedSource<<8c1211258e4480dcaa6fdf14364cb49b>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type OrganizationSidebar_unsubscribeFromMailingListMutation$variables = Record; +export type OrganizationSidebar_unsubscribeFromMailingListMutation$data = { + readonly unsubscribeFromMailingList: { + readonly deletedMailingListSubscriberId: string | null | undefined; + }; +}; +export type OrganizationSidebar_unsubscribeFromMailingListMutation = { + response: OrganizationSidebar_unsubscribeFromMailingListMutation$data; + variables: OrganizationSidebar_unsubscribeFromMailingListMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedMailingListSubscriberId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "OrganizationSidebar_unsubscribeFromMailingListMutation", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "UnsubscribeFromMailingListPayload", + "kind": "LinkedField", + "name": "unsubscribeFromMailingList", + "plural": false, + "selections": [ + (v0/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "OrganizationSidebar_unsubscribeFromMailingListMutation", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "UnsubscribeFromMailingListPayload", + "kind": "LinkedField", + "name": "unsubscribeFromMailingList", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteRecord", + "key": "", + "kind": "ScalarHandle", + "name": "deletedMailingListSubscriberId" + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "1c6706f94348a5426c1e57d5fe99ab82", + "id": null, + "metadata": {}, + "name": "OrganizationSidebar_unsubscribeFromMailingListMutation", + "operationKind": "mutation", + "text": "mutation OrganizationSidebar_unsubscribeFromMailingListMutation {\n unsubscribeFromMailingList {\n deletedMailingListSubscriberId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "7f3949f63166fc3bd23838a44fb94993"; + +export default node; diff --git a/apps/trust/src/layouts/MainLayout.tsx b/apps/trust/src/layouts/MainLayout.tsx index d60820345..a743150a8 100644 --- a/apps/trust/src/layouts/MainLayout.tsx +++ b/apps/trust/src/layouts/MainLayout.tsx @@ -18,6 +18,7 @@ export function MainLayout(props: Props) { const { __ } = useTranslate(); const data = usePreloadedQuery(currentTrustGraphQuery, props.queryRef); const trustCenter = data.currentTrustCenter; + const isAuthenticated = data.viewer != null; const theme = useSystemTheme(); @@ -31,7 +32,7 @@ export function MainLayout(props: Props) { return (
- +
{__("Overview")} diff --git a/apps/trust/src/queries/TrustGraph.ts b/apps/trust/src/queries/TrustGraph.ts index 376f1acc1..48655df9a 100644 --- a/apps/trust/src/queries/TrustGraph.ts +++ b/apps/trust/src/queries/TrustGraph.ts @@ -3,9 +3,18 @@ import { graphql } from "relay-runtime"; // Queries for custom domain (subdomain) approach export const currentTrustGraphQuery = graphql` query TrustGraphCurrentQuery { + viewer { + id + } currentTrustCenter @required(action: THROW) { id slug + viewerSubscription { + id + email + createdAt + updatedAt + } logoFileUrl darkLogoFileUrl nonDisclosureAgreement { diff --git a/apps/trust/src/queries/__generated__/TrustGraphCurrentQuery.graphql.ts b/apps/trust/src/queries/__generated__/TrustGraphCurrentQuery.graphql.ts index 1f584cb58..ed767828c 100644 --- a/apps/trust/src/queries/__generated__/TrustGraphCurrentQuery.graphql.ts +++ b/apps/trust/src/queries/__generated__/TrustGraphCurrentQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<74757bf9a32552f6353eb888c9f663cd>> + * @generated SignedSource<<5b0657c3423f38cbd689de2dae810831>> * @lightSyntaxTransform * @nogrep */ @@ -62,8 +62,17 @@ export type TrustGraphCurrentQuery$data = { readonly vendorInfo: { readonly totalCount: number; }; + readonly viewerSubscription: { + readonly createdAt: any; + readonly email: any; + readonly id: string; + readonly updatedAt: any; + } | null | undefined; readonly " $fragmentSpreads": FragmentRefs<"OverviewPageFragment">; }; + readonly viewer: { + readonly id: string; + } | null | undefined; }; export type TrustGraphCurrentQuery = { response: TrustGraphCurrentQuery$data; @@ -81,81 +90,120 @@ var v0 = { v1 = { "alias": null, "args": null, - "kind": "ScalarField", - "name": "slug", + "concreteType": "Identity", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + (v0/*: any*/) + ], "storageKey": null }, v2 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "logoFileUrl", + "name": "slug", "storageKey": null }, v3 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "darkLogoFileUrl", + "name": "email", "storageKey": null }, v4 = { "alias": null, "args": null, - "kind": "ScalarField", - "name": "fileName", + "concreteType": "MailingListSubscriber", + "kind": "LinkedField", + "name": "viewerSubscription", + "plural": false, + "selections": [ + (v0/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], "storageKey": null }, v5 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "fileUrl", + "name": "logoFileUrl", "storageKey": null }, v6 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "status", + "name": "darkLogoFileUrl", "storageKey": null }, v7 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "name", + "name": "fileName", "storageKey": null }, v8 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "description", + "name": "fileUrl", "storageKey": null }, v9 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "websiteUrl", + "name": "status", "storageKey": null }, v10 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "email", + "name": "name", "storageKey": null }, v11 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "headquarterAddress", + "name": "description", "storageKey": null }, v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "headquarterAddress", + "storageKey": null +}, +v14 = { "alias": null, "args": [ { @@ -186,7 +234,7 @@ v12 = { "plural": false, "selections": [ (v0/*: any*/), - (v7/*: any*/), + (v10/*: any*/), { "alias": null, "args": null, @@ -203,7 +251,7 @@ v12 = { ], "storageKey": "externalUrls(first:20)" }, -v13 = { +v15 = { "alias": "vendorInfo", "args": [ { @@ -227,28 +275,28 @@ v13 = { ], "storageKey": "vendors(first:0)" }, -v14 = [ +v16 = [ { "kind": "Literal", "name": "first", "value": 50 } ], -v15 = [ +v17 = [ { "kind": "Literal", "name": "first", "value": 5 } ], -v16 = { +v18 = { "alias": null, "args": null, "kind": "ScalarField", "name": "isUserAuthorized", "storageKey": null }, -v17 = { +v19 = { "alias": null, "args": null, "concreteType": "DocumentAccess", @@ -257,11 +305,11 @@ v17 = { "plural": false, "selections": [ (v0/*: any*/), - (v6/*: any*/) + (v9/*: any*/) ], "storageKey": null }, -v18 = { +v20 = { "alias": null, "args": null, "concreteType": "Framework", @@ -270,7 +318,7 @@ v18 = { "plural": false, "selections": [ (v0/*: any*/), - (v7/*: any*/), + (v10/*: any*/), { "alias": null, "args": null, @@ -295,6 +343,7 @@ return { "metadata": null, "name": "TrustGraphCurrentQuery", "selections": [ + (v1/*: any*/), { "kind": "RequiredField", "field": { @@ -306,9 +355,10 @@ return { "plural": false, "selections": [ (v0/*: any*/), - (v1/*: any*/), (v2/*: any*/), - (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), { "alias": null, "args": null, @@ -317,8 +367,8 @@ return { "name": "nonDisclosureAgreement", "plural": false, "selections": [ - (v4/*: any*/), - (v5/*: any*/), + (v7/*: any*/), + (v8/*: any*/), { "alias": null, "args": null, @@ -327,7 +377,7 @@ return { "name": "viewerSignature", "plural": false, "selections": [ - (v6/*: any*/) + (v9/*: any*/) ], "storageKey": null } @@ -342,24 +392,24 @@ return { "name": "organization", "plural": false, "selections": [ - (v7/*: any*/), - (v8/*: any*/), - (v9/*: any*/), (v10/*: any*/), - (v11/*: any*/) + (v11/*: any*/), + (v12/*: any*/), + (v3/*: any*/), + (v13/*: any*/) ], "storageKey": null }, - (v12/*: any*/), + (v14/*: any*/), { "args": null, "kind": "FragmentSpread", "name": "OverviewPageFragment" }, - (v13/*: any*/), + (v15/*: any*/), { "alias": null, - "args": (v14/*: any*/), + "args": (v16/*: any*/), "concreteType": "AuditConnection", "kind": "LinkedField", "name": "audits", @@ -398,7 +448,7 @@ return { }, { "alias": null, - "args": (v14/*: any*/), + "args": (v16/*: any*/), "concreteType": "ComplianceFrameworkConnection", "kind": "LinkedField", "name": "complianceFrameworks", @@ -461,6 +511,7 @@ return { "kind": "Operation", "name": "TrustGraphCurrentQuery", "selections": [ + (v1/*: any*/), { "alias": null, "args": null, @@ -470,9 +521,10 @@ return { "plural": false, "selections": [ (v0/*: any*/), - (v1/*: any*/), (v2/*: any*/), - (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v6/*: any*/), { "alias": null, "args": null, @@ -481,8 +533,8 @@ return { "name": "nonDisclosureAgreement", "plural": false, "selections": [ - (v4/*: any*/), - (v5/*: any*/), + (v7/*: any*/), + (v8/*: any*/), { "alias": null, "args": null, @@ -491,7 +543,7 @@ return { "name": "viewerSignature", "plural": false, "selections": [ - (v6/*: any*/), + (v9/*: any*/), (v0/*: any*/) ], "storageKey": null @@ -507,16 +559,16 @@ return { "name": "organization", "plural": false, "selections": [ - (v7/*: any*/), - (v8/*: any*/), - (v9/*: any*/), (v10/*: any*/), (v11/*: any*/), + (v12/*: any*/), + (v3/*: any*/), + (v13/*: any*/), (v0/*: any*/) ], "storageKey": null }, - (v12/*: any*/), + (v14/*: any*/), { "alias": null, "args": [ @@ -548,7 +600,7 @@ return { "plural": false, "selections": [ (v0/*: any*/), - (v7/*: any*/), + (v10/*: any*/), { "alias": null, "args": null, @@ -556,7 +608,7 @@ return { "name": "logoUrl", "storageKey": null }, - (v9/*: any*/) + (v12/*: any*/) ], "storageKey": null } @@ -604,9 +656,9 @@ return { "name": "countries", "storageKey": null }, - (v7/*: any*/), - (v8/*: any*/), - (v9/*: any*/) + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/) ], "storageKey": null } @@ -618,7 +670,7 @@ return { }, { "alias": null, - "args": (v15/*: any*/), + "args": (v17/*: any*/), "concreteType": "DocumentConnection", "kind": "LinkedField", "name": "documents", @@ -648,8 +700,8 @@ return { "name": "title", "storageKey": null }, - (v16/*: any*/), - (v17/*: any*/), + (v18/*: any*/), + (v19/*: any*/), { "alias": null, "args": null, @@ -668,7 +720,7 @@ return { }, { "alias": null, - "args": (v15/*: any*/), + "args": (v17/*: any*/), "concreteType": "TrustCenterFileConnection", "kind": "LinkedField", "name": "trustCenterFiles", @@ -698,9 +750,9 @@ return { "name": "category", "storageKey": null }, - (v7/*: any*/), - (v16/*: any*/), - (v17/*: any*/) + (v10/*: any*/), + (v18/*: any*/), + (v19/*: any*/) ], "storageKey": null } @@ -710,10 +762,10 @@ return { ], "storageKey": "trustCenterFiles(first:5)" }, - (v13/*: any*/), + (v15/*: any*/), { "alias": null, - "args": (v14/*: any*/), + "args": (v16/*: any*/), "concreteType": "AuditConnection", "kind": "LinkedField", "name": "audits", @@ -736,7 +788,7 @@ return { "plural": false, "selections": [ (v0/*: any*/), - (v7/*: any*/), + (v10/*: any*/), { "alias": null, "args": null, @@ -753,12 +805,12 @@ return { "name": "filename", "storageKey": null }, - (v16/*: any*/), - (v17/*: any*/) + (v18/*: any*/), + (v19/*: any*/) ], "storageKey": null }, - (v18/*: any*/) + (v20/*: any*/) ], "storageKey": null } @@ -770,7 +822,7 @@ return { }, { "alias": null, - "args": (v14/*: any*/), + "args": (v16/*: any*/), "concreteType": "ComplianceFrameworkConnection", "kind": "LinkedField", "name": "complianceFrameworks", @@ -793,7 +845,7 @@ return { "plural": false, "selections": [ (v0/*: any*/), - (v18/*: any*/) + (v20/*: any*/) ], "storageKey": null } @@ -809,16 +861,16 @@ return { ] }, "params": { - "cacheID": "96e75937f385e17b760440e001cefc47", + "cacheID": "80334c766999a3d299eaf40fd32235b7", "id": null, "metadata": {}, "name": "TrustGraphCurrentQuery", "operationKind": "query", - "text": "query TrustGraphCurrentQuery {\n currentTrustCenter {\n id\n slug\n logoFileUrl\n darkLogoFileUrl\n nonDisclosureAgreement {\n fileName\n fileUrl\n viewerSignature {\n status\n id\n }\n }\n organization {\n name\n description\n websiteUrl\n email\n headquarterAddress\n id\n }\n externalUrls(first: 20) {\n edges {\n node {\n id\n name\n url\n }\n }\n }\n ...OverviewPageFragment\n vendorInfo: vendors(first: 0) {\n totalCount\n }\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n complianceFrameworks(first: 50) {\n edges {\n node {\n id\n framework {\n ...FrameworkBadgeFragment\n id\n }\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n name\n report {\n id\n filename\n isUserAuthorized\n access {\n id\n status\n }\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment FrameworkBadgeFragment on Framework {\n id\n name\n lightLogoURL\n darkLogoURL\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment VendorRowFragment on Vendor {\n name\n description\n websiteUrl\n countries\n}\n" + "text": "query TrustGraphCurrentQuery {\n viewer {\n id\n }\n currentTrustCenter {\n id\n slug\n viewerSubscription {\n id\n email\n createdAt\n updatedAt\n }\n logoFileUrl\n darkLogoFileUrl\n nonDisclosureAgreement {\n fileName\n fileUrl\n viewerSignature {\n status\n id\n }\n }\n organization {\n name\n description\n websiteUrl\n email\n headquarterAddress\n id\n }\n externalUrls(first: 20) {\n edges {\n node {\n id\n name\n url\n }\n }\n }\n ...OverviewPageFragment\n vendorInfo: vendors(first: 0) {\n totalCount\n }\n audits(first: 50) {\n edges {\n node {\n id\n ...AuditRowFragment\n }\n }\n }\n complianceFrameworks(first: 50) {\n edges {\n node {\n id\n framework {\n ...FrameworkBadgeFragment\n id\n }\n }\n }\n }\n }\n}\n\nfragment AuditRowFragment on Audit {\n name\n report {\n id\n filename\n isUserAuthorized\n access {\n id\n status\n }\n }\n framework {\n id\n name\n lightLogoURL\n darkLogoURL\n }\n}\n\nfragment DocumentRowFragment on Document {\n id\n title\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment FrameworkBadgeFragment on Framework {\n id\n name\n lightLogoURL\n darkLogoURL\n}\n\nfragment OverviewPageFragment on TrustCenter {\n references(first: 14) {\n edges {\n node {\n id\n name\n logoUrl\n websiteUrl\n }\n }\n }\n vendors(first: 3) {\n edges {\n node {\n id\n countries\n ...VendorRowFragment\n }\n }\n }\n documents(first: 5) {\n edges {\n node {\n id\n ...DocumentRowFragment\n documentType\n }\n }\n }\n trustCenterFiles(first: 5) {\n edges {\n node {\n id\n category\n ...TrustCenterFileRowFragment\n }\n }\n }\n}\n\nfragment TrustCenterFileRowFragment on TrustCenterFile {\n id\n name\n isUserAuthorized\n access {\n id\n status\n }\n}\n\nfragment VendorRowFragment on Vendor {\n name\n description\n websiteUrl\n countries\n}\n" } }; })(); -(node as any).hash = "ca6f04fecf9bce92407f33e8c0e4bd03"; +(node as any).hash = "d38cdfff47370448fff4d9c6c22ad1bd"; export default node; diff --git a/pkg/coredata/entity_type_reg.go b/pkg/coredata/entity_type_reg.go index 4a5819b5c..33b23abd6 100644 --- a/pkg/coredata/entity_type_reg.go +++ b/pkg/coredata/entity_type_reg.go @@ -87,6 +87,8 @@ const ( EmailAttachmentEntityType uint16 = 61 ComplianceFrameworkEntityType uint16 = 62 ComplianceExternalURLEntityType uint16 = 63 + MailingListEntityType uint16 = 64 + MailingListSubscriberEntityType uint16 = 65 ) func NewEntityFromID(id gid.GID) (any, bool) { @@ -215,6 +217,10 @@ func NewEntityFromID(id gid.GID) (any, bool) { return &ComplianceFramework{ID: id}, true case ComplianceExternalURLEntityType: return &ComplianceExternalURL{ID: id}, true + case MailingListEntityType: + return &MailingList{ID: id}, true + case MailingListSubscriberEntityType: + return &MailingListSubscriber{ID: id}, true default: return nil, false } diff --git a/pkg/coredata/mailing_list.go b/pkg/coredata/mailing_list.go new file mode 100644 index 000000000..36fe538c2 --- /dev/null +++ b/pkg/coredata/mailing_list.go @@ -0,0 +1,170 @@ +// 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" + "errors" + "fmt" + "maps" + "time" + + "github.com/jackc/pgx/v5" + "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" +) + +type MailingList struct { + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + ReplyTo *mail.Addr `db:"reply_to"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (ml *MailingList) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) { + q := `SELECT organization_id FROM mailing_lists WHERE id = $1 LIMIT 1;` + + var organizationID gid.GID + if err := conn.QueryRow(ctx, q, ml.ID).Scan(&organizationID); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, ErrResourceNotFound + } + return nil, fmt.Errorf("cannot query mailing list authorization attributes: %w", err) + } + + return map[string]string{"organization_id": organizationID.String()}, nil +} + +func (ml *MailingList) LoadByID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + id gid.GID, +) error { + q := ` +SELECT + id, + organization_id, + reply_to, + created_at, + updated_at +FROM + mailing_lists +WHERE + %s + AND id = @mailing_list_id +LIMIT 1; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"mailing_list_id": id} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query mailing list: %w", err) + } + + result, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[MailingList]) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return ErrResourceNotFound + } + return fmt.Errorf("cannot collect mailing list: %w", err) + } + + *ml = result + + return nil +} + +func (ml *MailingList) Update( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +UPDATE mailing_lists +SET + reply_to = @reply_to, + updated_at = @updated_at +WHERE + %s + AND id = @mailing_list_id; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "mailing_list_id": ml.ID, + "reply_to": ml.ReplyTo, + "updated_at": ml.UpdatedAt, + } + maps.Copy(args, scope.SQLArguments()) + + tag, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot update mailing list: %w", err) + } + + if tag.RowsAffected() == 0 { + return ErrResourceNotFound + } + + return nil +} + +func (ml *MailingList) Insert( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +INSERT INTO mailing_lists ( + id, + tenant_id, + organization_id, + reply_to, + created_at, + updated_at +) +VALUES ( + @mailing_list_id, + @tenant_id, + @organization_id, + @reply_to, + @created_at, + @updated_at +); +` + + args := pgx.StrictNamedArgs{ + "mailing_list_id": ml.ID, + "tenant_id": scope.GetTenantID(), + "organization_id": ml.OrganizationID, + "reply_to": ml.ReplyTo, + "created_at": ml.CreatedAt, + "updated_at": ml.UpdatedAt, + } + + if _, err := conn.Exec(ctx, q, args); err != nil { + return fmt.Errorf("cannot insert mailing list: %w", err) + } + + return nil +} diff --git a/pkg/coredata/mailing_list_subscriber.go b/pkg/coredata/mailing_list_subscriber.go new file mode 100644 index 000000000..a8baf3c18 --- /dev/null +++ b/pkg/coredata/mailing_list_subscriber.go @@ -0,0 +1,316 @@ +// 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" + "errors" + "fmt" + "maps" + "time" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" + "go.probo.inc/probo/pkg/page" +) + +type ( + MailingListSubscriber struct { + ID gid.GID `db:"id"` + OrganizationID gid.GID `db:"organization_id"` + MailingListID gid.GID `db:"mailing_list_id"` + FullName string `db:"full_name"` + Email mail.Addr `db:"email"` + Status MailingListSubscriberStatus `db:"status"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + } + + MailingListSubscribers []*MailingListSubscriber +) + +func (cns *MailingListSubscriber) AuthorizationAttributes(ctx context.Context, conn pg.Conn) (map[string]string, error) { + q := `SELECT organization_id FROM mailing_list_subscribers WHERE id = $1 LIMIT 1;` + + var organizationID gid.GID + if err := conn.QueryRow(ctx, q, cns.ID).Scan(&organizationID); err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, ErrResourceNotFound + } + return nil, fmt.Errorf("cannot query mailing list subscriber authorization attributes: %w", err) + } + + return map[string]string{"organization_id": organizationID.String()}, nil +} + +func (cns *MailingListSubscriber) CursorKey(orderBy MailingListSubscriberOrderField) page.CursorKey { + switch orderBy { + case MailingListSubscriberOrderFieldCreatedAt: + return page.NewCursorKey(cns.ID, cns.CreatedAt) + } + + panic(fmt.Sprintf("unsupported order by: %s", orderBy)) +} + +func (cns *MailingListSubscriber) LoadByMailingListIDAndEmail( + ctx context.Context, + conn pg.Conn, + scope Scoper, + mailingListID gid.GID, + email mail.Addr, +) error { + q := ` +SELECT + id, + organization_id, + mailing_list_id, + full_name, + email, + status, + created_at, + updated_at +FROM + mailing_list_subscribers +WHERE + %s + AND mailing_list_id = @mailing_list_id + AND email = @email +LIMIT 1; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "mailing_list_id": mailingListID, + "email": email, + } + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query mailing list subscriber: %w", err) + } + + subscriber, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[MailingListSubscriber]) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return ErrResourceNotFound + } + + return fmt.Errorf("cannot collect mailing list subscriber: %w", err) + } + + *cns = subscriber + + return nil +} + +func (cns *MailingListSubscriber) Insert( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +INSERT INTO mailing_list_subscribers ( + id, + tenant_id, + organization_id, + mailing_list_id, + full_name, + email, + status, + created_at, + updated_at +) +VALUES ( + @mailing_list_subscriber_id, + @tenant_id, + @organization_id, + @mailing_list_id, + @full_name, + @email, + @status, + @created_at, + @updated_at +); +` + + args := pgx.StrictNamedArgs{ + "mailing_list_subscriber_id": cns.ID, + "tenant_id": scope.GetTenantID(), + "organization_id": cns.OrganizationID, + "mailing_list_id": cns.MailingListID, + "full_name": cns.FullName, + "email": cns.Email, + "status": cns.Status, + "created_at": cns.CreatedAt, + "updated_at": cns.UpdatedAt, + } + + if _, err := conn.Exec(ctx, q, args); err != nil { + if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" { + return ErrResourceAlreadyExists + } + + return fmt.Errorf("cannot insert mailing list subscriber: %w", err) + } + + return nil +} + +func (cns *MailingListSubscriber) Update( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +UPDATE mailing_list_subscribers +SET + status = @status, + updated_at = @updated_at +WHERE + %s + AND id = @mailing_list_subscriber_id; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "mailing_list_subscriber_id": cns.ID, + "status": cns.Status, + "updated_at": cns.UpdatedAt, + } + maps.Copy(args, scope.SQLArguments()) + + tag, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot update mailing list subscriber: %w", err) + } + + if tag.RowsAffected() == 0 { + return ErrResourceNotFound + } + + return nil +} + +func (cns *MailingListSubscriber) Delete( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +DELETE FROM + mailing_list_subscribers +WHERE + %s + AND id = @mailing_list_subscriber_id; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"mailing_list_subscriber_id": cns.ID} + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot delete mailing list subscriber: %w", err) + } + + return nil +} + +func (cnss *MailingListSubscribers) CountByMailingListID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + mailingListID gid.GID, +) (int, error) { + q := ` +SELECT + COUNT(id) +FROM + mailing_list_subscribers +WHERE + %s + AND mailing_list_id = @mailing_list_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"mailing_list_id": mailingListID} + maps.Copy(args, scope.SQLArguments()) + + row := conn.QueryRow(ctx, q, args) + + var count int + err := row.Scan(&count) + if err != nil { + return 0, fmt.Errorf("cannot count mailing list subscribers: %w", err) + } + + return count, nil +} + +func (cnss *MailingListSubscribers) LoadByMailingListID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + mailingListID gid.GID, + cursor *page.Cursor[MailingListSubscriberOrderField], +) error { + q := ` +SELECT + id, + organization_id, + mailing_list_id, + full_name, + email, + status, + created_at, + updated_at +FROM + mailing_list_subscribers +WHERE + %s + AND mailing_list_id = @mailing_list_id + AND %s +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "mailing_list_id": mailingListID, + } + 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 mailing list subscribers: %w", err) + } + + subscribers, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[MailingListSubscriber]) + if err != nil { + return fmt.Errorf("cannot collect mailing list subscribers: %w", err) + } + + *cnss = subscribers + + return nil +} diff --git a/pkg/coredata/mailing_list_subscriber_order_field.go b/pkg/coredata/mailing_list_subscriber_order_field.go new file mode 100644 index 000000000..d96cfb62e --- /dev/null +++ b/pkg/coredata/mailing_list_subscriber_order_field.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 coredata + +import "fmt" + +type MailingListSubscriberOrderField string + +const ( + MailingListSubscriberOrderFieldCreatedAt MailingListSubscriberOrderField = "CREATED_AT" +) + +func (f MailingListSubscriberOrderField) String() string { + return string(f) +} + +func (f MailingListSubscriberOrderField) Column() string { + switch f { + case MailingListSubscriberOrderFieldCreatedAt: + return "created_at" + } + + panic(fmt.Sprintf("unsupported order by: %s", f)) +} diff --git a/pkg/coredata/mailing_list_subscriber_status.go b/pkg/coredata/mailing_list_subscriber_status.go new file mode 100644 index 000000000..4c33c69c6 --- /dev/null +++ b/pkg/coredata/mailing_list_subscriber_status.go @@ -0,0 +1,58 @@ +// 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 ( + "database/sql/driver" + "fmt" +) + +type MailingListSubscriberStatus string + +const ( + MailingListSubscriberStatusPending MailingListSubscriberStatus = "PENDING" + MailingListSubscriberStatusConfirmed MailingListSubscriberStatus = "CONFIRMED" +) + +func (s MailingListSubscriberStatus) String() string { + return string(s) +} + +func (s *MailingListSubscriberStatus) Scan(value any) error { + var str string + switch v := value.(type) { + case string: + str = v + case []byte: + str = string(v) + default: + return fmt.Errorf("unsupported type for MailingListSubscriberStatus: %T", value) + } + + switch str { + case "PENDING": + *s = MailingListSubscriberStatusPending + case "CONFIRMED": + *s = MailingListSubscriberStatusConfirmed + default: + return fmt.Errorf("invalid MailingListSubscriberStatus value: %q", str) + } + + return nil +} + +func (s MailingListSubscriberStatus) Value() (driver.Value, error) { + return s.String(), nil +} diff --git a/pkg/coredata/migrations/20260225T000000Z.sql b/pkg/coredata/migrations/20260225T000000Z.sql new file mode 100644 index 000000000..a4348e8eb --- /dev/null +++ b/pkg/coredata/migrations/20260225T000000Z.sql @@ -0,0 +1,50 @@ +CREATE TABLE mailing_lists ( + id TEXT PRIMARY KEY, + tenant_id TEXT NOT NULL, + organization_id TEXT NOT NULL REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE, + reply_to CITEXT, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + UNIQUE (organization_id) +); + +CREATE TYPE mailing_list_subscriber_status AS ENUM ( + 'PENDING', + 'CONFIRMED' +); + +CREATE TABLE mailing_list_subscribers ( + id TEXT PRIMARY KEY, + tenant_id TEXT NOT NULL, + organization_id TEXT NOT NULL REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE, + mailing_list_id TEXT NOT NULL REFERENCES mailing_lists(id) ON UPDATE CASCADE ON DELETE CASCADE, + full_name TEXT NOT NULL, + email CITEXT NOT NULL, + status mailing_list_subscriber_status NOT NULL DEFAULT 'PENDING', + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL, + UNIQUE (mailing_list_id, email) +); + +ALTER TABLE trust_centers + ADD COLUMN mailing_list_id TEXT REFERENCES mailing_lists(id) ON UPDATE CASCADE ON DELETE RESTRICT; + +INSERT INTO mailing_lists ( + id, + tenant_id, + organization_id, + created_at, + updated_at +) +SELECT + generate_gid(decode_base64_unpadded(tc.tenant_id), 64), + tc.tenant_id, + tc.organization_id, + NOW(), + NOW() +FROM trust_centers tc; + +UPDATE trust_centers tc +SET mailing_list_id = ml.id +FROM mailing_lists ml +WHERE ml.organization_id = tc.organization_id; diff --git a/pkg/coredata/trust_center.go b/pkg/coredata/trust_center.go index 636da6d45..329da638f 100644 --- a/pkg/coredata/trust_center.go +++ b/pkg/coredata/trust_center.go @@ -35,6 +35,7 @@ type ( TenantID gid.TenantID `db:"tenant_id"` Active bool `db:"active"` Slug string `db:"slug"` + MailingListID *gid.GID `db:"mailing_list_id"` LogoFileID *gid.GID `db:"logo_file_id"` DarkLogoFileID *gid.GID `db:"dark_logo_file_id"` NonDisclosureAgreementFileID *gid.GID `db:"non_disclosure_agreement_file_id"` @@ -79,6 +80,7 @@ SELECT id, organization_id, tenant_id, + mailing_list_id, logo_file_id, dark_logo_file_id, active, @@ -129,6 +131,7 @@ SELECT id, organization_id, tenant_id, + mailing_list_id, logo_file_id, dark_logo_file_id, active, @@ -179,6 +182,7 @@ SELECT id, organization_id, tenant_id, + mailing_list_id, logo_file_id, dark_logo_file_id, active, @@ -224,6 +228,7 @@ INSERT INTO trust_centers ( id, organization_id, tenant_id, + mailing_list_id, logo_file_id, dark_logo_file_id, active, @@ -235,6 +240,7 @@ INSERT INTO trust_centers ( @id, @organization_id, @tenant_id, + @mailing_list_id, @logo_file_id, @dark_logo_file_id, @active, @@ -249,6 +255,7 @@ INSERT INTO trust_centers ( "id": tc.ID, "organization_id": tc.OrganizationID, "tenant_id": tc.TenantID, + "mailing_list_id": tc.MailingListID, "logo_file_id": tc.LogoFileID, "dark_logo_file_id": tc.DarkLogoFileID, "active": tc.Active, diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index 6159cb53e..3322e51f0 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -480,12 +480,20 @@ func (s *OrganizationService) CreateOrganization( UpdatedAt: now, } + mailingList = &coredata.MailingList{ + ID: gid.New(tenantID, coredata.MailingListEntityType), + OrganizationID: organization.ID, + CreatedAt: now, + UpdatedAt: now, + } + trustCenter = &coredata.TrustCenter{ ID: gid.New(tenantID, coredata.TrustCenterEntityType), OrganizationID: organization.ID, TenantID: organization.TenantID, Active: false, Slug: slug.Make(organization.Name), + MailingListID: &mailingList.ID, CreatedAt: now, UpdatedAt: now, } @@ -616,6 +624,10 @@ func (s *OrganizationService) CreateOrganization( return fmt.Errorf("cannot insert organization context: %w", err) } + if err := mailingList.Insert(ctx, tx, scope); err != nil { + return fmt.Errorf("cannot insert mailing list: %w", err) + } + if err := trustCenter.Insert(ctx, tx, scope); err != nil { return fmt.Errorf("cannot insert trust center: %w", err) } diff --git a/pkg/mailman/errors.go b/pkg/mailman/errors.go new file mode 100644 index 000000000..bb32e50d0 --- /dev/null +++ b/pkg/mailman/errors.go @@ -0,0 +1,23 @@ +// 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 mailman + +import "errors" + +var ( + ErrMailingListNotFound = errors.New("mailing list not found") + ErrSubscriberNotFound = errors.New("mailing list subscriber not found") + ErrSubscriberAlreadyExist = errors.New("mailing list subscriber already exists") +) diff --git a/pkg/mailman/service.go b/pkg/mailman/service.go new file mode 100644 index 000000000..50aa0a5b1 --- /dev/null +++ b/pkg/mailman/service.go @@ -0,0 +1,218 @@ +// 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 mailman + +import ( + "context" + "errors" + "fmt" + "time" + + "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" + "go.probo.inc/probo/pkg/page" +) + +type Service struct { + pg *pg.Client +} + +func NewService(pgClient *pg.Client) *Service { + return &Service{pg: pgClient} +} + +func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService { + return &TenantService{pg: s.pg, scope: coredata.NewScope(tenantID)} +} + +type TenantService struct { + pg *pg.Client + scope coredata.Scoper +} + +func (s *TenantService) UpdateMailingList( + ctx context.Context, + id gid.GID, + replyTo *mail.Addr, +) (*coredata.MailingList, error) { + var ml coredata.MailingList + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := ml.LoadByID(ctx, conn, s.scope, id); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return ErrMailingListNotFound + } + return fmt.Errorf("cannot load mailing list: %w", err) + } + + ml.ReplyTo = replyTo + ml.UpdatedAt = time.Now() + + if err := ml.Update(ctx, conn, s.scope); err != nil { + return fmt.Errorf("cannot update mailing list: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return &ml, nil +} + +func (s *TenantService) GetSubscriber( + ctx context.Context, + mailingListID gid.GID, + email mail.Addr, +) (*coredata.MailingListSubscriber, error) { + var subscriber coredata.MailingListSubscriber + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := subscriber.LoadByMailingListIDAndEmail(ctx, conn, s.scope, mailingListID, email); err != nil { + return fmt.Errorf("cannot load mailing list subscriber: %w", err) + } + + return nil + }, + ) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, nil + } + return nil, err + } + + return &subscriber, nil +} + +func (s *TenantService) CreateSubscriber( + ctx context.Context, + mailingListID gid.GID, + email mail.Addr, + fullName string, +) (*coredata.MailingListSubscriber, error) { + now := time.Now() + + subscriber := &coredata.MailingListSubscriber{ + ID: gid.New(s.scope.GetTenantID(), coredata.MailingListSubscriberEntityType), + MailingListID: mailingListID, + FullName: fullName, + Email: email, + Status: coredata.MailingListSubscriberStatusPending, + CreatedAt: now, + UpdatedAt: now, + } + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + var ml coredata.MailingList + if err := ml.LoadByID(ctx, conn, s.scope, mailingListID); err != nil { + return fmt.Errorf("cannot load mailing list: %w", err) + } + subscriber.OrganizationID = ml.OrganizationID + + if err := subscriber.Insert(ctx, conn, s.scope); err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return ErrSubscriberAlreadyExist + } + return fmt.Errorf("cannot insert mailing list subscriber: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return subscriber, nil +} + +func (s *TenantService) DeleteSubscriber( + ctx context.Context, + id gid.GID, +) error { + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + subscriber := coredata.MailingListSubscriber{ID: id} + if err := subscriber.Delete(ctx, conn, s.scope); err != nil { + return fmt.Errorf("cannot delete mailing list subscriber: %w", err) + } + return nil + }, + ) + if err != nil { + return err + } + + return nil +} + +func (s *TenantService) CountSubscribers( + ctx context.Context, + mailingListID gid.GID, +) (int, error) { + var count int + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) (err error) { + subscribers := coredata.MailingListSubscribers{} + count, err = subscribers.CountByMailingListID(ctx, conn, s.scope, mailingListID) + if err != nil { + return fmt.Errorf("cannot count mailing list subscribers: %w", err) + } + return nil + }, + ) + if err != nil { + return 0, err + } + + return count, nil +} + +func (s *TenantService) ListSubscribers( + ctx context.Context, + mailingListID gid.GID, + cursor *page.Cursor[coredata.MailingListSubscriberOrderField], +) (*page.Page[*coredata.MailingListSubscriber, coredata.MailingListSubscriberOrderField], error) { + var subscribers coredata.MailingListSubscribers + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := subscribers.LoadByMailingListID(ctx, conn, s.scope, mailingListID, cursor); err != nil { + return fmt.Errorf("cannot load mailing list subscribers: %w", err) + } + return nil + }, + ) + if err != nil { + return nil, err + } + + return page.NewPage(subscribers, cursor), nil +} diff --git a/pkg/probo/actions.go b/pkg/probo/actions.go index 3d6c2a198..5996b21fb 100644 --- a/pkg/probo/actions.go +++ b/pkg/probo/actions.go @@ -40,6 +40,14 @@ const ( ActionTrustCenterAccessUpdate = "core:trust-center-access:update" ActionTrustCenterAccessDelete = "core:trust-center-access:delete" + // MailingList actions + ActionMailingListUpdate = "core:mailing-list:update" + + // MailingListSubscriber actions + ActionMailingListSubscriberList = "core:mailing-list-subscriber:list" + ActionMailingListSubscriberCreate = "core:mailing-list-subscriber:create" + ActionMailingListSubscriberDelete = "core:mailing-list-subscriber:delete" + // TrustCenterReference actions ActionTrustCenterReferenceList = "core:trust-center-reference:list" ActionTrustCenterReferenceGetLogoUrl = "core:trust-center-reference:get-logo-url" diff --git a/pkg/probo/trust_center_service.go b/pkg/probo/trust_center_service.go index 48dbff5d8..3d57597c9 100644 --- a/pkg/probo/trust_center_service.go +++ b/pkg/probo/trust_center_service.go @@ -673,3 +673,36 @@ func (s *TrustCenterService) EmailPresenterConfig(ctx context.Context, complianc return emailPresenterCfg, nil } + +func (s *TrustCenterService) GetMailingList( + ctx context.Context, + trustCenterID gid.GID, +) (*coredata.MailingList, error) { + var mailingList *coredata.MailingList + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + trustCenter := &coredata.TrustCenter{} + if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil { + return fmt.Errorf("cannot load trust center: %w", err) + } + + if trustCenter.MailingListID == nil { + return nil + } + + mailingList = &coredata.MailingList{} + if err := mailingList.LoadByID(ctx, conn, s.svc.scope, *trustCenter.MailingListID); err != nil { + return fmt.Errorf("cannot load mailing list: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return mailingList, nil +} diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index b09414f6d..0d452c451 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -56,6 +56,7 @@ import ( "go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/mailer" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/server" @@ -471,6 +472,8 @@ func (impl *Implm) Run( slackService, ) + mailmanService := mailman.NewService(pgClient) + serverHandler, err := server.NewServer( server.Config{ AllowedOrigins: impl.cfg.Api.Cors.AllowedOrigins, @@ -479,6 +482,7 @@ func (impl *Implm) Run( IAM: iamService, Trust: trustService, ESign: esignService, + Mailman: mailmanService, Slack: slackService, ConnectorRegistry: defaultConnectorRegistry, BaseURL: baseURL, diff --git a/pkg/server/api/api.go b/pkg/server/api/api.go index b5bfe2a7f..89939c97e 100644 --- a/pkg/server/api/api.go +++ b/pkg/server/api/api.go @@ -27,6 +27,7 @@ import ( "go.probo.inc/probo/pkg/connector" "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/securecookie" connect_v1 "go.probo.inc/probo/pkg/server/api/connect/v1" @@ -47,6 +48,7 @@ type ( Trust *trust.Service ESign *esign.Service Slack *slack.Service + Mailman *mailman.Service Cookie securecookie.Config TokenSecret string ConnectorRegistry *connector.ConnectorRegistry @@ -120,7 +122,9 @@ func NewServer(cfg Config) (*Server, error) { cfg.IAM, cfg.Trust, cfg.ESign, + cfg.Mailman, cfg.Cookie, + cfg.TokenSecret, cfg.BaseURL, ), consoleHandler: console_v1.NewMux( @@ -128,6 +132,7 @@ func NewServer(cfg Config) (*Server, error) { cfg.Probo, cfg.IAM, cfg.ESign, + cfg.Mailman, cfg.Cookie, cfg.TokenSecret, cfg.ConnectorRegistry, diff --git a/pkg/server/api/console/v1/graphql_handler.go b/pkg/server/api/console/v1/graphql_handler.go index 0bb0f8682..3c349d25e 100644 --- a/pkg/server/api/console/v1/graphql_handler.go +++ b/pkg/server/api/console/v1/graphql_handler.go @@ -20,19 +20,21 @@ import ( "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/server/api/authz" "go.probo.inc/probo/pkg/server/api/console/v1/schema" "go.probo.inc/probo/pkg/server/gqlutils" ) -func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, esignSvc *esign.Service, customDomainCname string, logger *log.Logger) http.Handler { +func NewGraphQLHandler(iamSvc *iam.Service, proboSvc *probo.Service, esignSvc *esign.Service, mailmanSvc *mailman.Service, customDomainCname string, logger *log.Logger) http.Handler { config := schema.Config{ Resolvers: &Resolver{ authorize: authz.NewAuthorizeFunc(iamSvc, logger), probo: proboSvc, iam: iamSvc, esign: esignSvc, + mailman: mailmanSvc, customDomainCname: customDomainCname, logger: logger, }, diff --git a/pkg/server/api/console/v1/resolver.go b/pkg/server/api/console/v1/resolver.go index 48acb9a55..d97debb2d 100644 --- a/pkg/server/api/console/v1/resolver.go +++ b/pkg/server/api/console/v1/resolver.go @@ -31,6 +31,7 @@ import ( "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/saferedirect" "go.probo.inc/probo/pkg/securecookie" @@ -45,6 +46,7 @@ type ( probo *probo.Service iam *iam.Service esign *esign.Service + mailman *mailman.Service logger *log.Logger customDomainCname string } @@ -55,6 +57,7 @@ func NewMux( proboSvc *probo.Service, iamSvc *iam.Service, esignSvc *esign.Service, + mailmanSvc *mailman.Service, cookieConfig securecookie.Config, tokenSecret string, connectorRegistry *connector.ConnectorRegistry, @@ -65,7 +68,7 @@ func NewMux( safeRedirect := &saferedirect.SafeRedirect{AllowedHost: baseURL.Host()} - graphqlHandler := NewGraphQLHandler(iamSvc, proboSvc, esignSvc, customDomainCname, logger) + graphqlHandler := NewGraphQLHandler(iamSvc, proboSvc, esignSvc, mailmanSvc, customDomainCname, logger) r.Group(func(r chi.Router) { r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) @@ -199,6 +202,10 @@ func (r *Resolver) ProboService(ctx context.Context, tenantID gid.TenantID) *pro return r.probo.WithTenant(tenantID) } +func (r *Resolver) MailmanService(ctx context.Context, tenantID gid.TenantID) *mailman.TenantService { + return r.mailman.WithTenant(tenantID) +} + func (r *Resolver) Permission(ctx context.Context, obj types.Node, action string) (bool, error) { return r.authorize(ctx, obj.GetID(), action) == nil, nil } diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 8221ca199..7c2c68e79 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -1634,7 +1634,10 @@ input VendorFilter { } # Core Types -type TrustCenter implements Node { +type TrustCenter implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenter" + ) { id: ID! active: Boolean! logoFileUrl: String @goField(forceResolver: true) @@ -1677,9 +1680,60 @@ type TrustCenter implements Node { orderBy: ComplianceExternalURLOrder ): ComplianceExternalURLConnection! @goField(forceResolver: true) + mailingList: MailingList @goField(forceResolver: true) + permission(action: String!): Boolean! @goField(forceResolver: true) } +type MailingList implements Node { + id: ID! + replyTo: EmailAddr + + subscribers( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): MailingListSubscriberConnection! @goField(forceResolver: true) +} + +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +type MailingListSubscriber implements Node { + id: ID! + fullName: String! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! +} + +type MailingListSubscriberConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListSubscriberConnection" + ) { + edges: [MailingListSubscriberEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type MailingListSubscriberEdge { + cursor: CursorKey! + node: MailingListSubscriber! +} + type Organization implements Node { id: ID! name: String! @@ -3353,6 +3407,17 @@ type Mutation { deleteTrustCenterAccess( input: DeleteTrustCenterAccessInput! ): DeleteTrustCenterAccessPayload! + # Mailing List mutations + updateMailingList( + input: UpdateMailingListInput! + ): UpdateMailingListPayload! + # Mailing List Subscriber mutations + createMailingListSubscriber( + input: CreateMailingListSubscriberInput! + ): CreateMailingListSubscriberPayload! + deleteMailingListSubscriber( + input: DeleteMailingListSubscriberInput! + ): DeleteMailingListSubscriberPayload! # Trust Center Reference mutations createTrustCenterReference( input: CreateTrustCenterReferenceInput! @@ -3755,6 +3820,25 @@ input DeleteTrustCenterAccessInput { id: ID! } +input UpdateMailingListInput { + id: ID! + replyTo: EmailAddr +} + +type UpdateMailingListPayload { + mailingList: MailingList! +} + +input CreateMailingListSubscriberInput { + mailingListId: ID! + fullName: String! + email: EmailAddr! +} + +input DeleteMailingListSubscriberInput { + id: ID! +} + input CreateTrustCenterReferenceInput { trustCenterId: ID! name: String! @@ -4606,6 +4690,14 @@ type DeleteTrustCenterAccessPayload { deletedTrustCenterAccessId: ID! } +type CreateMailingListSubscriberPayload { + mailingListSubscriberEdge: MailingListSubscriberEdge! +} + +type DeleteMailingListSubscriberPayload { + deletedMailingListSubscriberId: ID! +} + type CreateTrustCenterReferencePayload { trustCenterReferenceEdge: TrustCenterReferenceEdge! } diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 8fa3fe2c9..b3d6749a9 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -65,6 +65,8 @@ type ResolverRoot interface { File() FileResolver Framework() FrameworkResolver FrameworkConnection() FrameworkConnectionResolver + MailingList() MailingListResolver + MailingListSubscriberConnection() MailingListSubscriberConnectionResolver Measure() MeasureResolver MeasureConnection() MeasureConnectionResolver Meeting() MeetingResolver @@ -398,6 +400,10 @@ type ComplexityRoot struct { FrameworkEdge func(childComplexity int) int } + CreateMailingListSubscriberPayload struct { + MailingListSubscriberEdge func(childComplexity int) int + } + CreateMeasurePayload struct { MeasureEdge func(childComplexity int) int } @@ -639,6 +645,10 @@ type ComplexityRoot struct { DeletedFrameworkID func(childComplexity int) int } + DeleteMailingListSubscriberPayload struct { + DeletedMailingListSubscriberID func(childComplexity int) int + } + DeleteMeasurePayload struct { DeletedMeasureID func(childComplexity int) int } @@ -950,6 +960,32 @@ type ComplexityRoot struct { MeasureEdges func(childComplexity int) int } + MailingList struct { + ID func(childComplexity int) int + ReplyTo func(childComplexity int) int + Subscribers func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int + } + + MailingListSubscriber struct { + CreatedAt func(childComplexity int) int + Email func(childComplexity int) int + FullName func(childComplexity int) int + ID func(childComplexity int) int + Status func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + MailingListSubscriberConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + TotalCount func(childComplexity int) int + } + + MailingListSubscriberEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int + } + Measure struct { Category 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 @@ -1024,6 +1060,7 @@ type ComplexityRoot struct { CreateDocument func(childComplexity int, input types.CreateDocumentInput) int CreateDraftDocumentVersion func(childComplexity int, input types.CreateDraftDocumentVersionInput) int CreateFramework func(childComplexity int, input types.CreateFrameworkInput) int + CreateMailingListSubscriber func(childComplexity int, input types.CreateMailingListSubscriberInput) int CreateMeasure func(childComplexity int, input types.CreateMeasureInput) int CreateMeeting func(childComplexity int, input types.CreateMeetingInput) int CreateNonconformity func(childComplexity int, input types.CreateNonconformityInput) int @@ -1065,6 +1102,7 @@ type ComplexityRoot struct { DeleteDraftDocumentVersion func(childComplexity int, input types.DeleteDraftDocumentVersionInput) int DeleteEvidence func(childComplexity int, input types.DeleteEvidenceInput) int DeleteFramework func(childComplexity int, input types.DeleteFrameworkInput) int + DeleteMailingListSubscriber func(childComplexity int, input types.DeleteMailingListSubscriberInput) int DeleteMeasure func(childComplexity int, input types.DeleteMeasureInput) int DeleteMeeting func(childComplexity int, input types.DeleteMeetingInput) int DeleteNonconformity func(childComplexity int, input types.DeleteNonconformityInput) int @@ -1117,6 +1155,7 @@ type ComplexityRoot struct { UpdateDocument func(childComplexity int, input types.UpdateDocumentInput) int UpdateDocumentVersion func(childComplexity int, input types.UpdateDocumentVersionInput) int UpdateFramework func(childComplexity int, input types.UpdateFrameworkInput) int + UpdateMailingList func(childComplexity int, input types.UpdateMailingListInput) int UpdateMeasure func(childComplexity int, input types.UpdateMeasureInput) int UpdateMeeting func(childComplexity int, input types.UpdateMeetingInput) int UpdateNonconformity func(childComplexity int, input types.UpdateNonconformityInput) int @@ -1574,6 +1613,7 @@ type ComplexityRoot struct { ExternalUrls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceExternalURLOrderField]) int ID func(childComplexity int) int LogoFileURL func(childComplexity int) int + MailingList func(childComplexity int) int NdaFileName func(childComplexity int) int NdaFileURL func(childComplexity int) int Organization func(childComplexity int) int @@ -1729,6 +1769,10 @@ type ComplexityRoot struct { Framework func(childComplexity int) int } + UpdateMailingListPayload struct { + MailingList func(childComplexity int) int + } + UpdateMeasurePayload struct { Measure func(childComplexity int) int } @@ -2200,6 +2244,12 @@ type FrameworkResolver interface { type FrameworkConnectionResolver interface { TotalCount(ctx context.Context, obj *types.FrameworkConnection) (int, error) } +type MailingListResolver interface { + Subscribers(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListSubscriberConnection, error) +} +type MailingListSubscriberConnectionResolver interface { + TotalCount(ctx context.Context, obj *types.MailingListSubscriberConnection) (int, error) +} type MeasureResolver interface { Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) Tasks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) @@ -2228,6 +2278,9 @@ type MutationResolver interface { UpdateTrustCenterBrand(ctx context.Context, input types.UpdateTrustCenterBrandInput) (*types.UpdateTrustCenterBrandPayload, error) UpdateTrustCenterAccess(ctx context.Context, input types.UpdateTrustCenterAccessInput) (*types.UpdateTrustCenterAccessPayload, error) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) + UpdateMailingList(ctx context.Context, input types.UpdateMailingListInput) (*types.UpdateMailingListPayload, error) + CreateMailingListSubscriber(ctx context.Context, input types.CreateMailingListSubscriberInput) (*types.CreateMailingListSubscriberPayload, error) + DeleteMailingListSubscriber(ctx context.Context, input types.DeleteMailingListSubscriberInput) (*types.DeleteMailingListSubscriberPayload, error) CreateTrustCenterReference(ctx context.Context, input types.CreateTrustCenterReferenceInput) (*types.CreateTrustCenterReferencePayload, error) UpdateTrustCenterReference(ctx context.Context, input types.UpdateTrustCenterReferenceInput) (*types.UpdateTrustCenterReferencePayload, error) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) @@ -2528,6 +2581,7 @@ type TrustCenterResolver interface { References(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) (*types.TrustCenterReferenceConnection, error) ComplianceFrameworks(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceFrameworkOrderField]) (*types.ComplianceFrameworkConnection, error) ExternalUrls(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.ComplianceExternalURLOrderField]) (*types.ComplianceExternalURLConnection, error) + MailingList(ctx context.Context, obj *types.TrustCenter) (*types.MailingList, error) Permission(ctx context.Context, obj *types.TrustCenter, action string) (bool, error) } type TrustCenterAccessResolver interface { @@ -3618,6 +3672,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.CreateFrameworkPayload.FrameworkEdge(childComplexity), true + case "CreateMailingListSubscriberPayload.mailingListSubscriberEdge": + if e.ComplexityRoot.CreateMailingListSubscriberPayload.MailingListSubscriberEdge == nil { + break + } + + return e.ComplexityRoot.CreateMailingListSubscriberPayload.MailingListSubscriberEdge(childComplexity), true + case "CreateMeasurePayload.measureEdge": if e.ComplexityRoot.CreateMeasurePayload.MeasureEdge == nil { break @@ -4251,6 +4312,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.DeleteFrameworkPayload.DeletedFrameworkID(childComplexity), true + case "DeleteMailingListSubscriberPayload.deletedMailingListSubscriberId": + if e.ComplexityRoot.DeleteMailingListSubscriberPayload.DeletedMailingListSubscriberID == nil { + break + } + + return e.ComplexityRoot.DeleteMailingListSubscriberPayload.DeletedMailingListSubscriberID(childComplexity), true + case "DeleteMeasurePayload.deletedMeasureId": if e.ComplexityRoot.DeleteMeasurePayload.DeletedMeasureID == nil { break @@ -5254,6 +5322,99 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.ImportMeasurePayload.MeasureEdges(childComplexity), true + case "MailingList.id": + if e.ComplexityRoot.MailingList.ID == nil { + break + } + + return e.ComplexityRoot.MailingList.ID(childComplexity), true + case "MailingList.replyTo": + if e.ComplexityRoot.MailingList.ReplyTo == nil { + break + } + + return e.ComplexityRoot.MailingList.ReplyTo(childComplexity), true + case "MailingList.subscribers": + if e.ComplexityRoot.MailingList.Subscribers == nil { + break + } + + args, err := ec.field_MailingList_subscribers_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.MailingList.Subscribers(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + + case "MailingListSubscriber.createdAt": + if e.ComplexityRoot.MailingListSubscriber.CreatedAt == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.CreatedAt(childComplexity), true + case "MailingListSubscriber.email": + if e.ComplexityRoot.MailingListSubscriber.Email == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.Email(childComplexity), true + case "MailingListSubscriber.fullName": + if e.ComplexityRoot.MailingListSubscriber.FullName == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.FullName(childComplexity), true + case "MailingListSubscriber.id": + if e.ComplexityRoot.MailingListSubscriber.ID == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.ID(childComplexity), true + case "MailingListSubscriber.status": + if e.ComplexityRoot.MailingListSubscriber.Status == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.Status(childComplexity), true + case "MailingListSubscriber.updatedAt": + if e.ComplexityRoot.MailingListSubscriber.UpdatedAt == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.UpdatedAt(childComplexity), true + + case "MailingListSubscriberConnection.edges": + if e.ComplexityRoot.MailingListSubscriberConnection.Edges == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriberConnection.Edges(childComplexity), true + case "MailingListSubscriberConnection.pageInfo": + if e.ComplexityRoot.MailingListSubscriberConnection.PageInfo == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriberConnection.PageInfo(childComplexity), true + case "MailingListSubscriberConnection.totalCount": + if e.ComplexityRoot.MailingListSubscriberConnection.TotalCount == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriberConnection.TotalCount(childComplexity), true + + case "MailingListSubscriberEdge.cursor": + if e.ComplexityRoot.MailingListSubscriberEdge.Cursor == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriberEdge.Cursor(childComplexity), true + case "MailingListSubscriberEdge.node": + if e.ComplexityRoot.MailingListSubscriberEdge.Node == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriberEdge.Node(childComplexity), true + case "Measure.category": if e.ComplexityRoot.Measure.Category == nil { break @@ -5740,6 +5901,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Mutation.CreateFramework(childComplexity, args["input"].(types.CreateFrameworkInput)), true + case "Mutation.createMailingListSubscriber": + if e.ComplexityRoot.Mutation.CreateMailingListSubscriber == nil { + break + } + + args, err := ec.field_Mutation_createMailingListSubscriber_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.CreateMailingListSubscriber(childComplexity, args["input"].(types.CreateMailingListSubscriberInput)), true case "Mutation.createMeasure": if e.ComplexityRoot.Mutation.CreateMeasure == nil { break @@ -6191,6 +6363,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Mutation.DeleteFramework(childComplexity, args["input"].(types.DeleteFrameworkInput)), true + case "Mutation.deleteMailingListSubscriber": + if e.ComplexityRoot.Mutation.DeleteMailingListSubscriber == nil { + break + } + + args, err := ec.field_Mutation_deleteMailingListSubscriber_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.DeleteMailingListSubscriber(childComplexity, args["input"].(types.DeleteMailingListSubscriberInput)), true case "Mutation.deleteMeasure": if e.ComplexityRoot.Mutation.DeleteMeasure == nil { break @@ -6763,6 +6946,17 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Mutation.UpdateFramework(childComplexity, args["input"].(types.UpdateFrameworkInput)), true + case "Mutation.updateMailingList": + if e.ComplexityRoot.Mutation.UpdateMailingList == nil { + break + } + + args, err := ec.field_Mutation_updateMailingList_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.ComplexityRoot.Mutation.UpdateMailingList(childComplexity, args["input"].(types.UpdateMailingListInput)), true case "Mutation.updateMeasure": if e.ComplexityRoot.Mutation.UpdateMeasure == nil { break @@ -9108,6 +9302,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.TrustCenter.LogoFileURL(childComplexity), true + case "TrustCenter.mailingList": + if e.ComplexityRoot.TrustCenter.MailingList == nil { + break + } + + return e.ComplexityRoot.TrustCenter.MailingList(childComplexity), true case "TrustCenter.ndaFileName": if e.ComplexityRoot.TrustCenter.NdaFileName == nil { break @@ -9615,6 +9815,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.UpdateFrameworkPayload.Framework(childComplexity), true + case "UpdateMailingListPayload.mailingList": + if e.ComplexityRoot.UpdateMailingListPayload.MailingList == nil { + break + } + + return e.ComplexityRoot.UpdateMailingListPayload.MailingList(childComplexity), true + case "UpdateMeasurePayload.measure": if e.ComplexityRoot.UpdateMeasurePayload.Measure == nil { break @@ -10757,6 +10964,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputCreateDocumentInput, ec.unmarshalInputCreateDraftDocumentVersionInput, ec.unmarshalInputCreateFrameworkInput, + ec.unmarshalInputCreateMailingListSubscriberInput, ec.unmarshalInputCreateMeasureInput, ec.unmarshalInputCreateMeetingInput, ec.unmarshalInputCreateNonconformityInput, @@ -10802,6 +11010,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputDeleteDraftDocumentVersionInput, ec.unmarshalInputDeleteEvidenceInput, ec.unmarshalInputDeleteFrameworkInput, + ec.unmarshalInputDeleteMailingListSubscriberInput, ec.unmarshalInputDeleteMeasureInput, ec.unmarshalInputDeleteMeetingInput, ec.unmarshalInputDeleteNonconformityInput, @@ -10887,6 +11096,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputUpdateDocumentInput, ec.unmarshalInputUpdateDocumentVersionInput, ec.unmarshalInputUpdateFrameworkInput, + ec.unmarshalInputUpdateMailingListInput, ec.unmarshalInputUpdateMeasureInput, ec.unmarshalInputUpdateMeetingInput, ec.unmarshalInputUpdateNonconformityInput, @@ -12634,7 +12844,10 @@ input VendorFilter { } # Core Types -type TrustCenter implements Node { +type TrustCenter implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrustCenter" + ) { id: ID! active: Boolean! logoFileUrl: String @goField(forceResolver: true) @@ -12677,9 +12890,60 @@ type TrustCenter implements Node { orderBy: ComplianceExternalURLOrder ): ComplianceExternalURLConnection! @goField(forceResolver: true) + mailingList: MailingList @goField(forceResolver: true) + permission(action: String!): Boolean! @goField(forceResolver: true) } +type MailingList implements Node { + id: ID! + replyTo: EmailAddr + + subscribers( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): MailingListSubscriberConnection! @goField(forceResolver: true) +} + +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +type MailingListSubscriber implements Node { + id: ID! + fullName: String! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! +} + +type MailingListSubscriberConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.MailingListSubscriberConnection" + ) { + edges: [MailingListSubscriberEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type MailingListSubscriberEdge { + cursor: CursorKey! + node: MailingListSubscriber! +} + type Organization implements Node { id: ID! name: String! @@ -14353,6 +14617,17 @@ type Mutation { deleteTrustCenterAccess( input: DeleteTrustCenterAccessInput! ): DeleteTrustCenterAccessPayload! + # Mailing List mutations + updateMailingList( + input: UpdateMailingListInput! + ): UpdateMailingListPayload! + # Mailing List Subscriber mutations + createMailingListSubscriber( + input: CreateMailingListSubscriberInput! + ): CreateMailingListSubscriberPayload! + deleteMailingListSubscriber( + input: DeleteMailingListSubscriberInput! + ): DeleteMailingListSubscriberPayload! # Trust Center Reference mutations createTrustCenterReference( input: CreateTrustCenterReferenceInput! @@ -14755,6 +15030,25 @@ input DeleteTrustCenterAccessInput { id: ID! } +input UpdateMailingListInput { + id: ID! + replyTo: EmailAddr +} + +type UpdateMailingListPayload { + mailingList: MailingList! +} + +input CreateMailingListSubscriberInput { + mailingListId: ID! + fullName: String! + email: EmailAddr! +} + +input DeleteMailingListSubscriberInput { + id: ID! +} + input CreateTrustCenterReferenceInput { trustCenterId: ID! name: String! @@ -15606,6 +15900,14 @@ type DeleteTrustCenterAccessPayload { deletedTrustCenterAccessId: ID! } +type CreateMailingListSubscriberPayload { + mailingListSubscriberEdge: MailingListSubscriberEdge! +} + +type DeleteMailingListSubscriberPayload { + deletedMailingListSubscriberId: ID! +} + type CreateTrustCenterReferencePayload { trustCenterReferenceEdge: TrustCenterReferenceEdge! } @@ -17362,6 +17664,32 @@ func (ec *executionContext) field_Framework_permission_args(ctx context.Context, return args, nil } +func (ec *executionContext) field_MailingList_subscribers_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 + return args, nil +} + func (ec *executionContext) field_Measure_controls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -17782,6 +18110,17 @@ func (ec *executionContext) field_Mutation_createFramework_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Mutation_createMailingListSubscriber_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, "input", ec.unmarshalNCreateMailingListSubscriberInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMailingListSubscriberInput) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_createMeasure_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -18233,6 +18572,17 @@ func (ec *executionContext) field_Mutation_deleteFramework_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Mutation_deleteMailingListSubscriber_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, "input", ec.unmarshalNDeleteMailingListSubscriberInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMailingListSubscriberInput) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_deleteMeasure_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -18805,6 +19155,17 @@ func (ec *executionContext) field_Mutation_updateFramework_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_Mutation_updateMailingList_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, "input", ec.unmarshalNUpdateMailingListInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMailingListInput) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateMeasure_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -26346,6 +26707,41 @@ func (ec *executionContext) fieldContext_CreateFrameworkPayload_frameworkEdge(_ return fc, nil } +func (ec *executionContext) _CreateMailingListSubscriberPayload_mailingListSubscriberEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateMailingListSubscriberPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_CreateMailingListSubscriberPayload_mailingListSubscriberEdge, + func(ctx context.Context) (any, error) { + return obj.MailingListSubscriberEdge, nil + }, + nil, + ec.marshalNMailingListSubscriberEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberEdge, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_CreateMailingListSubscriberPayload_mailingListSubscriberEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreateMailingListSubscriberPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_MailingListSubscriberEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_MailingListSubscriberEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriberEdge", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _CreateMeasurePayload_measureEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateMeasurePayload) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -29730,6 +30126,35 @@ func (ec *executionContext) fieldContext_DeleteFrameworkPayload_deletedFramework return fc, nil } +func (ec *executionContext) _DeleteMailingListSubscriberPayload_deletedMailingListSubscriberId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteMailingListSubscriberPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_DeleteMailingListSubscriberPayload_deletedMailingListSubscriberId, + func(ctx context.Context) (any, error) { + return obj.DeletedMailingListSubscriberID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_DeleteMailingListSubscriberPayload_deletedMailingListSubscriberId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeleteMailingListSubscriberPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DeleteMeasurePayload_deletedMeasureId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteMeasurePayload) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -30331,6 +30756,8 @@ func (ec *executionContext) fieldContext_DeleteTrustCenterNDAPayload_trustCenter return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -34835,6 +35262,462 @@ func (ec *executionContext) fieldContext_ImportMeasurePayload_measureEdges(_ con return fc, nil } +func (ec *executionContext) _MailingList_id(ctx context.Context, field graphql.CollectedField, obj *types.MailingList) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingList_id, + func(ctx context.Context) (any, error) { + return obj.ID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingList_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingList", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingList_replyTo(ctx context.Context, field graphql.CollectedField, obj *types.MailingList) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingList_replyTo, + func(ctx context.Context) (any, error) { + return obj.ReplyTo, nil + }, + nil, + ec.marshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_MailingList_replyTo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingList", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type EmailAddr does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingList_subscribers(ctx context.Context, field graphql.CollectedField, obj *types.MailingList) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingList_subscribers, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.MailingList().Subscribers(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) + }, + nil, + ec.marshalNMailingListSubscriberConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberConnection, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingList_subscribers(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingList", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_MailingListSubscriberConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_MailingListSubscriberConnection_pageInfo(ctx, field) + case "totalCount": + return ec.fieldContext_MailingListSubscriberConnection_totalCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriberConnection", 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_MailingList_subscribers_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_id(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_id, + func(ctx context.Context) (any, error) { + return obj.ID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_fullName(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_fullName, + func(ctx context.Context) (any, error) { + return obj.FullName, nil + }, + nil, + ec.marshalNString2string, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_fullName(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_email(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_email, + func(ctx context.Context) (any, error) { + return obj.Email, nil + }, + nil, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_email(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type EmailAddr does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_status(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_status, + func(ctx context.Context) (any, error) { + return obj.Status, nil + }, + nil, + ec.marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_status(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type MailingListSubscriberStatus does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_createdAt, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + ec.marshalNDatetime2timeᚐTime, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_updatedAt, + func(ctx context.Context) (any, error) { + return obj.UpdatedAt, nil + }, + nil, + ec.marshalNDatetime2timeᚐTime, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriberConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriberConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriberConnection_edges, + func(ctx context.Context) (any, error) { + return obj.Edges, nil + }, + nil, + ec.marshalNMailingListSubscriberEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberEdgeᚄ, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriberConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriberConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_MailingListSubscriberEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_MailingListSubscriberEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriberEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriberConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriberConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriberConnection_pageInfo, + func(ctx context.Context) (any, error) { + return obj.PageInfo, nil + }, + nil, + ec.marshalNPageInfo2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriberConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriberConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPreviousPage": + return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field) + case "startCursor": + return ec.fieldContext_PageInfo_startCursor(ctx, field) + case "endCursor": + return ec.fieldContext_PageInfo_endCursor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriberConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriberConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriberConnection_totalCount, + func(ctx context.Context) (any, error) { + return ec.Resolvers.MailingListSubscriberConnection().TotalCount(ctx, obj) + }, + nil, + ec.marshalNInt2int, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriberConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriberConnection", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriberEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriberEdge) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriberEdge_cursor, + func(ctx context.Context) (any, error) { + return obj.Cursor, nil + }, + nil, + ec.marshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriberEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriberEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CursorKey does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriberEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriberEdge) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriberEdge_node, + func(ctx context.Context) (any, error) { + return obj.Node, nil + }, + nil, + ec.marshalNMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriber, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriberEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriberEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_MailingListSubscriber_id(ctx, field) + case "fullName": + return ec.fieldContext_MailingListSubscriber_fullName(ctx, field) + case "email": + return ec.fieldContext_MailingListSubscriber_email(ctx, field) + case "status": + return ec.fieldContext_MailingListSubscriber_status(ctx, field) + case "createdAt": + return ec.fieldContext_MailingListSubscriber_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_MailingListSubscriber_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriber", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Measure_id(ctx context.Context, field graphql.CollectedField, obj *types.Measure) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -36335,6 +37218,141 @@ func (ec *executionContext) fieldContext_Mutation_deleteTrustCenterAccess(ctx co return fc, nil } +func (ec *executionContext) _Mutation_updateMailingList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_updateMailingList, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().UpdateMailingList(ctx, fc.Args["input"].(types.UpdateMailingListInput)) + }, + nil, + ec.marshalNUpdateMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMailingListPayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_updateMailingList(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "mailingList": + return ec.fieldContext_UpdateMailingListPayload_mailingList(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UpdateMailingListPayload", 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_Mutation_updateMailingList_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_createMailingListSubscriber(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_createMailingListSubscriber, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().CreateMailingListSubscriber(ctx, fc.Args["input"].(types.CreateMailingListSubscriberInput)) + }, + nil, + ec.marshalNCreateMailingListSubscriberPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMailingListSubscriberPayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_createMailingListSubscriber(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "mailingListSubscriberEdge": + return ec.fieldContext_CreateMailingListSubscriberPayload_mailingListSubscriberEdge(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CreateMailingListSubscriberPayload", 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_Mutation_createMailingListSubscriber_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_deleteMailingListSubscriber(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_deleteMailingListSubscriber, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.Resolvers.Mutation().DeleteMailingListSubscriber(ctx, fc.Args["input"].(types.DeleteMailingListSubscriberInput)) + }, + nil, + ec.marshalNDeleteMailingListSubscriberPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMailingListSubscriberPayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_deleteMailingListSubscriber(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "deletedMailingListSubscriberId": + return ec.fieldContext_DeleteMailingListSubscriberPayload_deletedMailingListSubscriberId(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeleteMailingListSubscriberPayload", 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_Mutation_deleteMailingListSubscriber_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_createTrustCenterReference(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -45636,6 +46654,8 @@ func (ec *executionContext) fieldContext_Organization_trustCenter(_ context.Cont return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -53989,6 +55009,43 @@ func (ec *executionContext) fieldContext_TrustCenter_externalUrls(ctx context.Co return fc, nil } +func (ec *executionContext) _TrustCenter_mailingList(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_TrustCenter_mailingList, + func(ctx context.Context) (any, error) { + return ec.Resolvers.TrustCenter().MailingList(ctx, obj) + }, + nil, + ec.marshalOMailingList2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingList, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_TrustCenter_mailingList(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "TrustCenter", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_MailingList_id(ctx, field) + case "replyTo": + return ec.fieldContext_MailingList_replyTo(ctx, field) + case "subscribers": + return ec.fieldContext_MailingList_subscribers(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingList", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _TrustCenter_permission(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -55128,6 +56185,8 @@ func (ec *executionContext) fieldContext_TrustCenterEdge_node(_ context.Context, return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -56773,6 +57832,43 @@ func (ec *executionContext) fieldContext_UpdateFrameworkPayload_framework(_ cont return fc, nil } +func (ec *executionContext) _UpdateMailingListPayload_mailingList(ctx context.Context, field graphql.CollectedField, obj *types.UpdateMailingListPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_UpdateMailingListPayload_mailingList, + func(ctx context.Context) (any, error) { + return obj.MailingList, nil + }, + nil, + ec.marshalNMailingList2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingList, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_UpdateMailingListPayload_mailingList(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UpdateMailingListPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_MailingList_id(ctx, field) + case "replyTo": + return ec.fieldContext_MailingList_replyTo(ctx, field) + case "subscribers": + return ec.fieldContext_MailingList_subscribers(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingList", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _UpdateMeasurePayload_measure(ctx context.Context, field graphql.CollectedField, obj *types.UpdateMeasurePayload) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -57523,6 +58619,8 @@ func (ec *executionContext) fieldContext_UpdateTrustCenterBrandPayload_trustCent return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -57631,6 +58729,8 @@ func (ec *executionContext) fieldContext_UpdateTrustCenterPayload_trustCenter(_ return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -58173,6 +59273,8 @@ func (ec *executionContext) fieldContext_UploadTrustCenterNDAPayload_trustCenter return ec.fieldContext_TrustCenter_complianceFrameworks(ctx, field) case "externalUrls": return ec.fieldContext_TrustCenter_externalUrls(ctx, field) + case "mailingList": + return ec.fieldContext_TrustCenter_mailingList(ctx, field) case "permission": return ec.fieldContext_TrustCenter_permission(ctx, field) } @@ -66218,6 +67320,46 @@ func (ec *executionContext) unmarshalInputCreateFrameworkInput(ctx context.Conte return it, nil } +func (ec *executionContext) unmarshalInputCreateMailingListSubscriberInput(ctx context.Context, obj any) (types.CreateMailingListSubscriberInput, error) { + var it types.CreateMailingListSubscriberInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"mailingListId", "fullName", "email"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "mailingListId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("mailingListId")) + data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.MailingListID = data + case "fullName": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("fullName")) + data, err := ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + it.FullName = data + case "email": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email")) + data, err := ec.unmarshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) + if err != nil { + return it, err + } + it.Email = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputCreateMeasureInput(ctx context.Context, obj any) (types.CreateMeasureInput, error) { var it types.CreateMeasureInput asMap := map[string]any{} @@ -68305,6 +69447,32 @@ func (ec *executionContext) unmarshalInputDeleteFrameworkInput(ctx context.Conte return it, nil } +func (ec *executionContext) unmarshalInputDeleteMailingListSubscriberInput(ctx context.Context, obj any) (types.DeleteMailingListSubscriberInput, error) { + var it types.DeleteMailingListSubscriberInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ID = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteMeasureInput(ctx context.Context, obj any) (types.DeleteMeasureInput, error) { var it types.DeleteMeasureInput asMap := map[string]any{} @@ -71103,6 +72271,39 @@ func (ec *executionContext) unmarshalInputUpdateFrameworkInput(ctx context.Conte return it, nil } +func (ec *executionContext) unmarshalInputUpdateMailingListInput(ctx context.Context, obj any) (types.UpdateMailingListInput, error) { + var it types.UpdateMailingListInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "replyTo"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "replyTo": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("replyTo")) + data, err := ec.unmarshalOEmailAddr2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr(ctx, v) + if err != nil { + return it, err + } + it.ReplyTo = data + } + } + return it, nil +} + func (ec *executionContext) unmarshalInputUpdateMeasureInput(ctx context.Context, obj any) (types.UpdateMeasureInput, error) { var it types.UpdateMeasureInput asMap := map[string]any{} @@ -73307,6 +74508,20 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._Measure(ctx, sel, obj) + case types.MailingListSubscriber: + return ec._MailingListSubscriber(ctx, sel, &obj) + case *types.MailingListSubscriber: + if obj == nil { + return graphql.Null + } + return ec._MailingListSubscriber(ctx, sel, obj) + case types.MailingList: + return ec._MailingList(ctx, sel, &obj) + case *types.MailingList: + if obj == nil { + return graphql.Null + } + return ec._MailingList(ctx, sel, obj) case types.Framework: return ec._Framework(ctx, sel, &obj) case *types.Framework: @@ -76692,6 +77907,45 @@ func (ec *executionContext) _CreateFrameworkPayload(ctx context.Context, sel ast return out } +var createMailingListSubscriberPayloadImplementors = []string{"CreateMailingListSubscriberPayload"} + +func (ec *executionContext) _CreateMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateMailingListSubscriberPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createMailingListSubscriberPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("CreateMailingListSubscriberPayload") + case "mailingListSubscriberEdge": + out.Values[i] = ec._CreateMailingListSubscriberPayload_mailingListSubscriberEdge(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var createMeasurePayloadImplementors = []string{"CreateMeasurePayload"} func (ec *executionContext) _CreateMeasurePayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateMeasurePayload) graphql.Marshaler { @@ -79117,6 +80371,45 @@ func (ec *executionContext) _DeleteFrameworkPayload(ctx context.Context, sel ast return out } +var deleteMailingListSubscriberPayloadImplementors = []string{"DeleteMailingListSubscriberPayload"} + +func (ec *executionContext) _DeleteMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteMailingListSubscriberPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deleteMailingListSubscriberPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("DeleteMailingListSubscriberPayload") + case "deletedMailingListSubscriberId": + out.Values[i] = ec._DeleteMailingListSubscriberPayload_deletedMailingListSubscriberId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var deleteMeasurePayloadImplementors = []string{"DeleteMeasurePayload"} func (ec *executionContext) _DeleteMeasurePayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteMeasurePayload) graphql.Marshaler { @@ -82586,6 +83879,271 @@ func (ec *executionContext) _ImportMeasurePayload(ctx context.Context, sel ast.S return out } +var mailingListImplementors = []string{"MailingList", "Node"} + +func (ec *executionContext) _MailingList(ctx context.Context, sel ast.SelectionSet, obj *types.MailingList) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mailingListImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MailingList") + case "id": + out.Values[i] = ec._MailingList_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "replyTo": + out.Values[i] = ec._MailingList_replyTo(ctx, field, obj) + case "subscribers": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._MailingList_subscribers(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var mailingListSubscriberImplementors = []string{"MailingListSubscriber", "Node"} + +func (ec *executionContext) _MailingListSubscriber(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListSubscriber) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mailingListSubscriberImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MailingListSubscriber") + case "id": + out.Values[i] = ec._MailingListSubscriber_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "fullName": + out.Values[i] = ec._MailingListSubscriber_fullName(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "email": + out.Values[i] = ec._MailingListSubscriber_email(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "status": + out.Values[i] = ec._MailingListSubscriber_status(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createdAt": + out.Values[i] = ec._MailingListSubscriber_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatedAt": + out.Values[i] = ec._MailingListSubscriber_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var mailingListSubscriberConnectionImplementors = []string{"MailingListSubscriberConnection"} + +func (ec *executionContext) _MailingListSubscriberConnection(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListSubscriberConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mailingListSubscriberConnectionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MailingListSubscriberConnection") + case "edges": + out.Values[i] = ec._MailingListSubscriberConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "pageInfo": + out.Values[i] = ec._MailingListSubscriberConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "totalCount": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._MailingListSubscriberConnection_totalCount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var mailingListSubscriberEdgeImplementors = []string{"MailingListSubscriberEdge"} + +func (ec *executionContext) _MailingListSubscriberEdge(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListSubscriberEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mailingListSubscriberEdgeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MailingListSubscriberEdge") + case "cursor": + out.Values[i] = ec._MailingListSubscriberEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._MailingListSubscriberEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var measureImplementors = []string{"Measure", "Node"} func (ec *executionContext) _Measure(ctx context.Context, sel ast.SelectionSet, obj *types.Measure) graphql.Marshaler { @@ -83317,6 +84875,27 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "updateMailingList": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_updateMailingList(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createMailingListSubscriber": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createMailingListSubscriber(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deleteMailingListSubscriber": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_deleteMailingListSubscriber(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "createTrustCenterReference": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_createTrustCenterReference(ctx, field) @@ -89960,6 +91539,39 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "mailingList": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._TrustCenter_mailingList(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "permission": field := field @@ -91761,6 +93373,45 @@ func (ec *executionContext) _UpdateFrameworkPayload(ctx context.Context, sel ast return out } +var updateMailingListPayloadImplementors = []string{"UpdateMailingListPayload"} + +func (ec *executionContext) _UpdateMailingListPayload(ctx context.Context, sel ast.SelectionSet, obj *types.UpdateMailingListPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, updateMailingListPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("UpdateMailingListPayload") + case "mailingList": + out.Values[i] = ec._UpdateMailingListPayload_mailingList(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var updateMeasurePayloadImplementors = []string{"UpdateMeasurePayload"} func (ec *executionContext) _UpdateMeasurePayload(ctx context.Context, sel ast.SelectionSet, obj *types.UpdateMeasurePayload) graphql.Marshaler { @@ -97412,6 +99063,25 @@ func (ec *executionContext) marshalNCreateFrameworkPayload2ᚖgoᚗproboᚗinc return ec._CreateFrameworkPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNCreateMailingListSubscriberInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMailingListSubscriberInput(ctx context.Context, v any) (types.CreateMailingListSubscriberInput, error) { + res, err := ec.unmarshalInputCreateMailingListSubscriberInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCreateMailingListSubscriberPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, v types.CreateMailingListSubscriberPayload) graphql.Marshaler { + return ec._CreateMailingListSubscriberPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCreateMailingListSubscriberPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, v *types.CreateMailingListSubscriberPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._CreateMailingListSubscriberPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNCreateMeasureInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateMeasureInput(ctx context.Context, v any) (types.CreateMeasureInput, error) { res, err := ec.unmarshalInputCreateMeasureInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -98481,6 +100151,25 @@ func (ec *executionContext) marshalNDeleteFrameworkPayload2ᚖgoᚗproboᚗinc return ec._DeleteFrameworkPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNDeleteMailingListSubscriberInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMailingListSubscriberInput(ctx context.Context, v any) (types.DeleteMailingListSubscriberInput, error) { + res, err := ec.unmarshalInputDeleteMailingListSubscriberInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDeleteMailingListSubscriberPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, v types.DeleteMailingListSubscriberPayload) graphql.Marshaler { + return ec._DeleteMailingListSubscriberPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDeleteMailingListSubscriberPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMailingListSubscriberPayload(ctx context.Context, sel ast.SelectionSet, v *types.DeleteMailingListSubscriberPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._DeleteMailingListSubscriberPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeleteMeasureInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteMeasureInput(ctx context.Context, v any) (types.DeleteMeasureInput, error) { res, err := ec.unmarshalInputDeleteMeasureInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -100008,6 +101697,94 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) marshalNMailingList2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingList(ctx context.Context, sel ast.SelectionSet, v *types.MailingList) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MailingList(ctx, sel, v) +} + +func (ec *executionContext) marshalNMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriber(ctx context.Context, sel ast.SelectionSet, v *types.MailingListSubscriber) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MailingListSubscriber(ctx, sel, v) +} + +func (ec *executionContext) marshalNMailingListSubscriberConnection2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberConnection(ctx context.Context, sel ast.SelectionSet, v types.MailingListSubscriberConnection) graphql.Marshaler { + return ec._MailingListSubscriberConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNMailingListSubscriberConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberConnection(ctx context.Context, sel ast.SelectionSet, v *types.MailingListSubscriberConnection) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MailingListSubscriberConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNMailingListSubscriberEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.MailingListSubscriberEdge) graphql.Marshaler { + ret := graphql.MarshalSliceConcurrently(ctx, len(v), 0, false, func(ctx context.Context, i int) graphql.Marshaler { + fc := graphql.GetFieldContext(ctx) + fc.Result = &v[i] + return ec.marshalNMailingListSubscriberEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberEdge(ctx, sel, v[i]) + }) + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNMailingListSubscriberEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingListSubscriberEdge(ctx context.Context, sel ast.SelectionSet, v *types.MailingListSubscriberEdge) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MailingListSubscriberEdge(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus(ctx context.Context, v any) (coredata.MailingListSubscriberStatus, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus(ctx context.Context, sel ast.SelectionSet, v coredata.MailingListSubscriberStatus) graphql.Marshaler { + _ = sel + res := graphql.MarshalString(marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus = map[string]coredata.MailingListSubscriberStatus{ + "PENDING": coredata.MailingListSubscriberStatusPending, + "CONFIRMED": coredata.MailingListSubscriberStatusConfirmed, + } + marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus = map[coredata.MailingListSubscriberStatus]string{ + coredata.MailingListSubscriberStatusPending: "PENDING", + coredata.MailingListSubscriberStatusConfirmed: "CONFIRMED", + } +) + func (ec *executionContext) marshalNMeasure2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMeasure(ctx context.Context, sel ast.SelectionSet, v types.Measure) graphql.Marshaler { return ec._Measure(ctx, sel, &v) } @@ -102434,6 +104211,25 @@ func (ec *executionContext) marshalNUpdateFrameworkPayload2ᚖgoᚗproboᚗinc return ec._UpdateFrameworkPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNUpdateMailingListInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMailingListInput(ctx context.Context, v any) (types.UpdateMailingListInput, error) { + res, err := ec.unmarshalInputUpdateMailingListInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNUpdateMailingListPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMailingListPayload(ctx context.Context, sel ast.SelectionSet, v types.UpdateMailingListPayload) graphql.Marshaler { + return ec._UpdateMailingListPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUpdateMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMailingListPayload(ctx context.Context, sel ast.SelectionSet, v *types.UpdateMailingListPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._UpdateMailingListPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNUpdateMeasureInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐUpdateMeasureInput(ctx context.Context, v any) (types.UpdateMeasureInput, error) { res, err := ec.unmarshalInputUpdateMeasureInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -104644,6 +106440,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return res } +func (ec *executionContext) marshalOMailingList2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMailingList(ctx context.Context, sel ast.SelectionSet, v *types.MailingList) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._MailingList(ctx, sel, v) +} + func (ec *executionContext) marshalOMeasure2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐMeasure(ctx context.Context, sel ast.SelectionSet, v *types.Measure) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/server/api/console/v1/types/mailing_list.go b/pkg/server/api/console/v1/types/mailing_list.go new file mode 100644 index 000000000..6d0d70cd1 --- /dev/null +++ b/pkg/server/api/console/v1/types/mailing_list.go @@ -0,0 +1,26 @@ +// 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 types + +import ( + "go.probo.inc/probo/pkg/coredata" +) + +func NewMailingList(ml *coredata.MailingList) *MailingList { + return &MailingList{ + ID: ml.ID, + ReplyTo: ml.ReplyTo, + } +} diff --git a/pkg/server/api/console/v1/types/mailing_list_subscriber.go b/pkg/server/api/console/v1/types/mailing_list_subscriber.go new file mode 100644 index 000000000..f636edbb6 --- /dev/null +++ b/pkg/server/api/console/v1/types/mailing_list_subscriber.go @@ -0,0 +1,71 @@ +// 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 types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" +) + +type ( + MailingListSubscriberOrderBy OrderBy[coredata.MailingListSubscriberOrderField] + + MailingListSubscriberConnection struct { + TotalCount int + Edges []*MailingListSubscriberEdge + PageInfo *PageInfo + + Resolver any + ParentID gid.GID + } +) + +func NewMailingListSubscriber(s *coredata.MailingListSubscriber) *MailingListSubscriber { + return &MailingListSubscriber{ + ID: s.ID, + FullName: s.FullName, + Email: s.Email, + Status: s.Status, + CreatedAt: s.CreatedAt, + UpdatedAt: s.UpdatedAt, + } +} + +func NewMailingListSubscriberEdge(s *coredata.MailingListSubscriber, orderBy coredata.MailingListSubscriberOrderField) *MailingListSubscriberEdge { + return &MailingListSubscriberEdge{ + Cursor: s.CursorKey(orderBy), + Node: NewMailingListSubscriber(s), + } +} + +func NewMailingListSubscriberConnection( + p *page.Page[*coredata.MailingListSubscriber, coredata.MailingListSubscriberOrderField], + resolver any, + mailingListID gid.GID, +) *MailingListSubscriberConnection { + var edges = make([]*MailingListSubscriberEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewMailingListSubscriberEdge(p.Data[i], p.Cursor.OrderBy.Field) + } + + return &MailingListSubscriberConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + Resolver: resolver, + ParentID: mailingListID, + } +} diff --git a/pkg/server/api/console/v1/types/trust_center.go b/pkg/server/api/console/v1/types/trust_center.go index 041e5d1c6..349cd2836 100644 --- a/pkg/server/api/console/v1/types/trust_center.go +++ b/pkg/server/api/console/v1/types/trust_center.go @@ -15,9 +15,33 @@ package types import ( + "time" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" ) +type TrustCenter struct { + ID gid.GID `json:"id"` + Active bool `json:"active"` + LogoFileURL *string `json:"logoFileUrl,omitempty"` + DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"` + NdaFileName *string `json:"ndaFileName,omitempty"` + NdaFileURL *string `json:"ndaFileUrl,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Organization *Organization `json:"organization"` + Accesses *TrustCenterAccessConnection `json:"accesses"` + References *TrustCenterReferenceConnection `json:"references"` + ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"` + ExternalUrls *ComplianceExternalURLConnection `json:"externalUrls"` + MailingList *MailingList `json:"mailingList,omitempty"` + Permission bool `json:"permission"` +} + +func (TrustCenter) IsNode() {} +func (t TrustCenter) GetID() gid.GID { return t.ID } + func NewTrustCenter(tc *coredata.TrustCenter, file *coredata.File) *TrustCenter { var ndaFileName *string if file != nil { diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 20646e69d..a1fcd7e79 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -436,6 +436,16 @@ type CreateFrameworkPayload struct { FrameworkEdge *FrameworkEdge `json:"frameworkEdge"` } +type CreateMailingListSubscriberInput struct { + MailingListID gid.GID `json:"mailingListId"` + FullName string `json:"fullName"` + Email mail.Addr `json:"email"` +} + +type CreateMailingListSubscriberPayload struct { + MailingListSubscriberEdge *MailingListSubscriberEdge `json:"mailingListSubscriberEdge"` +} + type CreateMeasureInput struct { OrganizationID gid.GID `json:"organizationId"` Name string `json:"name"` @@ -959,6 +969,14 @@ type DeleteFrameworkPayload struct { DeletedFrameworkID gid.GID `json:"deletedFrameworkId"` } +type DeleteMailingListSubscriberInput struct { + ID gid.GID `json:"id"` +} + +type DeleteMailingListSubscriberPayload struct { + DeletedMailingListSubscriberID gid.GID `json:"deletedMailingListSubscriberId"` +} + type DeleteMeasureInput struct { MeasureID gid.GID `json:"measureId"` } @@ -1431,6 +1449,32 @@ type ImportMeasurePayload struct { MeasureEdges []*MeasureEdge `json:"measureEdges"` } +type MailingList struct { + ID gid.GID `json:"id"` + ReplyTo *mail.Addr `json:"replyTo,omitempty"` + Subscribers *MailingListSubscriberConnection `json:"subscribers"` +} + +func (MailingList) IsNode() {} +func (this MailingList) GetID() gid.GID { return this.ID } + +type MailingListSubscriber struct { + ID gid.GID `json:"id"` + FullName string `json:"fullName"` + Email mail.Addr `json:"email"` + Status coredata.MailingListSubscriberStatus `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (MailingListSubscriber) IsNode() {} +func (this MailingListSubscriber) GetID() gid.GID { return this.ID } + +type MailingListSubscriberEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *MailingListSubscriber `json:"node"` +} + type Measure struct { ID gid.GID `json:"id"` Category string `json:"category"` @@ -1899,26 +1943,6 @@ type TransferImpactAssessmentFilter struct { SnapshotID *gid.GID `json:"snapshotId,omitempty"` } -type TrustCenter struct { - ID gid.GID `json:"id"` - Active bool `json:"active"` - LogoFileURL *string `json:"logoFileUrl,omitempty"` - DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"` - NdaFileName *string `json:"ndaFileName,omitempty"` - NdaFileURL *string `json:"ndaFileUrl,omitempty"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Organization *Organization `json:"organization"` - Accesses *TrustCenterAccessConnection `json:"accesses"` - References *TrustCenterReferenceConnection `json:"references"` - ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"` - ExternalUrls *ComplianceExternalURLConnection `json:"externalUrls"` - Permission bool `json:"permission"` -} - -func (TrustCenter) IsNode() {} -func (this TrustCenter) GetID() gid.GID { return this.ID } - type TrustCenterAccess struct { ID gid.GID `json:"id"` NdaSignature *ElectronicSignature `json:"ndaSignature,omitempty"` @@ -2148,6 +2172,15 @@ type UpdateFrameworkPayload struct { Framework *Framework `json:"framework"` } +type UpdateMailingListInput struct { + ID gid.GID `json:"id"` + ReplyTo *mail.Addr `json:"replyTo,omitempty"` +} + +type UpdateMailingListPayload struct { + MailingList *MailingList `json:"mailingList"` +} + type UpdateMeasureInput struct { ID gid.GID `json:"id"` Name *string `json:"name,omitempty"` diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 82f061912..52952b367 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -1524,6 +1524,47 @@ func (r *frameworkConnectionResolver) TotalCount(ctx context.Context, obj *types panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) } +// Subscribers is the resolver for the subscribers field on MailingList. +func (r *mailingListResolver) Subscribers(ctx context.Context, obj *types.MailingList, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.MailingListSubscriberConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { + return nil, err + } + + pageOrderBy := page.OrderBy[coredata.MailingListSubscriberOrderField]{ + Field: coredata.MailingListSubscriberOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + result, err := r.MailmanService(ctx, obj.ID.TenantID()).ListSubscribers(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list mailing list subscribers", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewMailingListSubscriberConnection(result, r, obj.ID), nil +} + +// TotalCount is the resolver for the totalCount field. +func (r *mailingListSubscriberConnectionResolver) TotalCount(ctx context.Context, obj *types.MailingListSubscriberConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionMailingListSubscriberList); err != nil { + return 0, err + } + + switch obj.Resolver.(type) { + case *mailingListResolver: + count, err := r.MailmanService(ctx, obj.ParentID.TenantID()).CountSubscribers(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count mailing list subscribers", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + panic(fmt.Errorf("not implemented: TotalCount for parent type %T", obj.Resolver)) +} + // Evidences is the resolver for the evidences field. func (r *measureResolver) Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) { if err := r.authorize(ctx, obj.ID, probo.ActionEvidenceList); err != nil { @@ -1987,6 +2028,56 @@ func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input ty }, nil } +// UpdateMailingList is the resolver for the updateMailingList field. +func (r *mutationResolver) UpdateMailingList(ctx context.Context, input types.UpdateMailingListInput) (*types.UpdateMailingListPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListUpdate); err != nil { + return nil, err + } + + ml, err := r.MailmanService(ctx, input.ID.TenantID()).UpdateMailingList(ctx, input.ID, input.ReplyTo) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot update mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UpdateMailingListPayload{ + MailingList: types.NewMailingList(ml), + }, nil +} + +// CreateMailingListSubscriber is the resolver for the createMailingListSubscriber field. +func (r *mutationResolver) CreateMailingListSubscriber(ctx context.Context, input types.CreateMailingListSubscriberInput) (*types.CreateMailingListSubscriberPayload, error) { + if err := r.authorize(ctx, input.MailingListID, probo.ActionMailingListSubscriberCreate); err != nil { + return nil, err + } + + subscriber, err := r.MailmanService(ctx, input.MailingListID.TenantID()).CreateSubscriber(ctx, input.MailingListID, input.Email, input.FullName) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create mailing list subscriber", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateMailingListSubscriberPayload{ + MailingListSubscriberEdge: types.NewMailingListSubscriberEdge(subscriber, coredata.MailingListSubscriberOrderFieldCreatedAt), + }, nil +} + +// DeleteMailingListSubscriber is the resolver for the deleteMailingListSubscriber field. +func (r *mutationResolver) DeleteMailingListSubscriber(ctx context.Context, input types.DeleteMailingListSubscriberInput) (*types.DeleteMailingListSubscriberPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionMailingListSubscriberDelete); err != nil { + return nil, err + } + + if err := r.MailmanService(ctx, input.ID.TenantID()).DeleteSubscriber(ctx, input.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot delete mailing list subscriber", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteMailingListSubscriberPayload{ + DeletedMailingListSubscriberID: input.ID, + }, nil +} + // CreateTrustCenterReference is the resolver for the createTrustCenterReference field. func (r *mutationResolver) CreateTrustCenterReference(ctx context.Context, input types.CreateTrustCenterReferenceInput) (*types.CreateTrustCenterReferencePayload, error) { if err := r.authorize(ctx, input.TrustCenterID, probo.ActionTrustCenterReferenceCreate); err != nil { @@ -7990,6 +8081,31 @@ func (r *trustCenterResolver) ExternalUrls(ctx context.Context, obj *types.Trust return types.NewComplianceExternalURLConnection(result), nil } +// MailingList is the resolver for the mailingList field. +func (r *trustCenterResolver) MailingList(ctx context.Context, obj *types.TrustCenter) (*types.MailingList, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionMailingListSubscriberList); err != nil { + return nil, err + } + + if obj.MailingList != nil { + return obj.MailingList, nil + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + ml, err := prb.TrustCenters.GetMailingList(ctx, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list for trust center", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if ml == nil { + return nil, nil + } + + return types.NewMailingList(ml), nil +} + // Permission is the resolver for the permission field. func (r *trustCenterResolver) Permission(ctx context.Context, obj *types.TrustCenter, action string) (bool, error) { return r.Resolver.Permission(ctx, obj, action) @@ -9072,6 +9188,14 @@ func (r *Resolver) FrameworkConnection() schema.FrameworkConnectionResolver { return &frameworkConnectionResolver{r} } +// MailingList returns schema.MailingListResolver implementation. +func (r *Resolver) MailingList() schema.MailingListResolver { return &mailingListResolver{r} } + +// MailingListSubscriberConnection returns schema.MailingListSubscriberConnectionResolver implementation. +func (r *Resolver) MailingListSubscriberConnection() schema.MailingListSubscriberConnectionResolver { + return &mailingListSubscriberConnectionResolver{r} +} + // Measure returns schema.MeasureResolver implementation. func (r *Resolver) Measure() schema.MeasureResolver { return &measureResolver{r} } @@ -9306,6 +9430,8 @@ type evidenceConnectionResolver struct{ *Resolver } type fileResolver struct{ *Resolver } type frameworkResolver struct{ *Resolver } type frameworkConnectionResolver struct{ *Resolver } +type mailingListResolver struct{ *Resolver } +type mailingListSubscriberConnectionResolver struct{ *Resolver } type measureResolver struct{ *Resolver } type measureConnectionResolver struct{ *Resolver } type meetingResolver struct{ *Resolver } diff --git a/pkg/server/api/trust/v1/graphql_handler.go b/pkg/server/api/trust/v1/graphql_handler.go index 3afc66b0b..77b7ce8cf 100644 --- a/pkg/server/api/trust/v1/graphql_handler.go +++ b/pkg/server/api/trust/v1/graphql_handler.go @@ -21,6 +21,7 @@ import ( "go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/trust/v1/schema" @@ -29,12 +30,13 @@ import ( "go.probo.inc/probo/pkg/trust" ) -func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *esign.Service, logger *log.Logger, baseURL *baseurl.BaseURL, cookieConfig securecookie.Config) http.Handler { +func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *esign.Service, mailmanSvc *mailman.Service, logger *log.Logger, baseURL *baseurl.BaseURL, cookieConfig securecookie.Config, tokenSecret string) http.Handler { config := schema.Config{ Resolvers: &Resolver{ iam: iamSvc, trust: trustSvc, esign: esignSvc, + mailman: mailmanSvc, logger: logger, baseURL: baseURL, sessionCookie: authn.NewCookie(&cookieConfig), diff --git a/pkg/server/api/trust/v1/resolver.go b/pkg/server/api/trust/v1/resolver.go index 12bb8118b..1a60bd517 100644 --- a/pkg/server/api/trust/v1/resolver.go +++ b/pkg/server/api/trust/v1/resolver.go @@ -26,6 +26,7 @@ import ( "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/compliancepage" @@ -39,7 +40,6 @@ type ( CookieDuration time.Duration TokenDuration time.Duration ReportURLDuration time.Duration - TokenSecret string Scope string TokenType string CookieSecure bool @@ -48,6 +48,7 @@ type ( Resolver struct { trust *trust.Service esign *esign.Service + mailman *mailman.Service logger *log.Logger iam *iam.Service sessionCookie *authn.Cookie @@ -78,7 +79,9 @@ func NewMux( iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *esign.Service, + mailmanSvc *mailman.Service, cookieConfig securecookie.Config, + tokenSecret string, baseURL *baseurl.BaseURL, ) *chi.Mux { r := chi.NewMux() @@ -86,17 +89,17 @@ func NewMux( r.Use(compliancepage.NewCompliancePagePresenceMiddleware()) r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) - graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, esignSvc, logger, baseURL, cookieConfig) + graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, esignSvc, mailmanSvc, logger, baseURL, cookieConfig, tokenSecret) r.Handle("/graphql", graphqlHandler) return r } -func (r *Resolver) RootTrustService(ctx context.Context) *trust.TenantService { - return r.trust.WithTenant(gid.NewTenantID()) -} - func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService { return r.trust.WithTenant(tenantID) } + +func (r *Resolver) MailmanService(ctx context.Context, tenantID gid.TenantID) *mailman.TenantService { + return r.mailman.WithTenant(tenantID) +} diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/schema.graphql index e2193bec9..d26488875 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/schema.graphql @@ -530,6 +530,7 @@ type TrustCenter implements Node { nonDisclosureAgreement: NonDisclosureAgreement @goField(forceResolver: true) + viewerSubscription: MailingListSubscriber @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true) documents( @@ -892,4 +893,41 @@ type Mutation { recordSigningEvent( input: RecordSigningEventInput! ): RecordSigningEventPayload @session(required: PRESENT) + + subscribeToMailingList: SubscribeToMailingListPayload! @session(required: PRESENT) + + unsubscribeFromMailingList: UnsubscribeFromMailingListPayload! @session(required: PRESENT) +} + +type SubscribeToMailingListPayload { + subscription: MailingListSubscriber! +} + +type UnsubscribeFromMailingListPayload { + deletedMailingListSubscriberId: ID +} + +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +type MailingListSubscriber implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber" + ) { + id: ID! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! } diff --git a/pkg/server/api/trust/v1/schema/schema.go b/pkg/server/api/trust/v1/schema/schema.go index 529f766d9..0ac7a6db7 100644 --- a/pkg/server/api/trust/v1/schema/schema.go +++ b/pkg/server/api/trust/v1/schema/schema.go @@ -172,6 +172,14 @@ type ComplexityRoot struct { UpdatedAt func(childComplexity int) int } + MailingListSubscriber struct { + CreatedAt func(childComplexity int) int + Email func(childComplexity int) int + ID func(childComplexity int) int + Status func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + Mutation struct { AcceptElectronicSignature func(childComplexity int, input types.AcceptElectronicSignatureInput) int ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int @@ -183,6 +191,8 @@ type ComplexityRoot struct { RequestReportAccess func(childComplexity int, input types.RequestReportAccessInput) int RequestTrustCenterFileAccess func(childComplexity int, input types.RequestTrustCenterFileAccessInput) int SendMagicLink func(childComplexity int, input types.SendMagicLinkInput) int + SubscribeToMailingList func(childComplexity int) int + UnsubscribeFromMailingList func(childComplexity int) int UpdateFullName func(childComplexity int, input types.UpdateFullNameInput) int VerifyMagicLink func(childComplexity int, input types.VerifyMagicLinkInput) int } @@ -247,6 +257,10 @@ type ComplexityRoot struct { Success func(childComplexity int) int } + SubscribeToMailingListPayload struct { + Subscription func(childComplexity int) int + } + TrustCenter struct { Active func(childComplexity int) int Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int @@ -262,6 +276,7 @@ type ComplexityRoot struct { Slug func(childComplexity int) int TrustCenterFiles func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int + ViewerSubscription func(childComplexity int) int } TrustCenterAccess struct { @@ -308,6 +323,10 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + UnsubscribeFromMailingListPayload struct { + DeletedMailingListSubscriberID func(childComplexity int) int + } + UpdateFullNamePayload struct { Success func(childComplexity int) int } @@ -366,6 +385,8 @@ type MutationResolver interface { RequestTrustCenterFileAccess(ctx context.Context, input types.RequestTrustCenterFileAccessInput) (*types.RequestFileAccessPayload, error) AcceptElectronicSignature(ctx context.Context, input types.AcceptElectronicSignatureInput) (*types.AcceptElectronicSignaturePayload, error) RecordSigningEvent(ctx context.Context, input types.RecordSigningEventInput) (*types.RecordSigningEventPayload, error) + SubscribeToMailingList(ctx context.Context) (*types.SubscribeToMailingListPayload, error) + UnsubscribeFromMailingList(ctx context.Context) (*types.UnsubscribeFromMailingListPayload, error) } type NonDisclosureAgreementResolver interface { FileURL(ctx context.Context, obj *types.NonDisclosureAgreement) (string, error) @@ -387,6 +408,7 @@ type TrustCenterResolver interface { LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) NonDisclosureAgreement(ctx context.Context, obj *types.TrustCenter) (*types.NonDisclosureAgreement, error) + ViewerSubscription(ctx context.Context, obj *types.TrustCenter) (*types.MailingListSubscriber, error) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) @@ -771,6 +793,37 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.Identity.UpdatedAt(childComplexity), true + case "MailingListSubscriber.createdAt": + if e.ComplexityRoot.MailingListSubscriber.CreatedAt == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.CreatedAt(childComplexity), true + case "MailingListSubscriber.email": + if e.ComplexityRoot.MailingListSubscriber.Email == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.Email(childComplexity), true + case "MailingListSubscriber.id": + if e.ComplexityRoot.MailingListSubscriber.ID == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.ID(childComplexity), true + case "MailingListSubscriber.status": + if e.ComplexityRoot.MailingListSubscriber.Status == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.Status(childComplexity), true + case "MailingListSubscriber.updatedAt": + if e.ComplexityRoot.MailingListSubscriber.UpdatedAt == nil { + break + } + + return e.ComplexityRoot.MailingListSubscriber.UpdatedAt(childComplexity), true + case "Mutation.acceptElectronicSignature": if e.ComplexityRoot.Mutation.AcceptElectronicSignature == nil { break @@ -876,6 +929,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.Mutation.SendMagicLink(childComplexity, args["input"].(types.SendMagicLinkInput)), true + case "Mutation.subscribeToMailingList": + if e.ComplexityRoot.Mutation.SubscribeToMailingList == nil { + break + } + + return e.ComplexityRoot.Mutation.SubscribeToMailingList(childComplexity), true + case "Mutation.unsubscribeFromMailingList": + if e.ComplexityRoot.Mutation.UnsubscribeFromMailingList == nil { + break + } + + return e.ComplexityRoot.Mutation.UnsubscribeFromMailingList(childComplexity), true case "Mutation.updateFullName": if e.ComplexityRoot.Mutation.UpdateFullName == nil { break @@ -1078,6 +1143,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.SendMagicLinkPayload.Success(childComplexity), true + case "SubscribeToMailingListPayload.subscription": + if e.ComplexityRoot.SubscribeToMailingListPayload.Subscription == nil { + break + } + + return e.ComplexityRoot.SubscribeToMailingListPayload.Subscription(childComplexity), true + case "TrustCenter.active": if e.ComplexityRoot.TrustCenter.Active == nil { break @@ -1197,6 +1269,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.ComplexityRoot.TrustCenter.Vendors(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + case "TrustCenter.viewerSubscription": + if e.ComplexityRoot.TrustCenter.ViewerSubscription == nil { + break + } + + return e.ComplexityRoot.TrustCenter.ViewerSubscription(childComplexity), true case "TrustCenterAccess.createdAt": if e.ComplexityRoot.TrustCenterAccess.CreatedAt == nil { @@ -1343,6 +1421,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.ComplexityRoot.TrustCenterReferenceEdge.Node(childComplexity), true + case "UnsubscribeFromMailingListPayload.deletedMailingListSubscriberId": + if e.ComplexityRoot.UnsubscribeFromMailingListPayload.DeletedMailingListSubscriberID == nil { + break + } + + return e.ComplexityRoot.UnsubscribeFromMailingListPayload.DeletedMailingListSubscriberID(childComplexity), true + case "UpdateFullNamePayload.success": if e.ComplexityRoot.UpdateFullNamePayload.Success == nil { break @@ -2058,6 +2143,7 @@ type TrustCenter implements Node { nonDisclosureAgreement: NonDisclosureAgreement @goField(forceResolver: true) + viewerSubscription: MailingListSubscriber @goField(forceResolver: true) organization: Organization! @goField(forceResolver: true) documents( @@ -2420,6 +2506,43 @@ type Mutation { recordSigningEvent( input: RecordSigningEventInput! ): RecordSigningEventPayload @session(required: PRESENT) + + subscribeToMailingList: SubscribeToMailingListPayload! @session(required: PRESENT) + + unsubscribeFromMailingList: UnsubscribeFromMailingListPayload! @session(required: PRESENT) +} + +type SubscribeToMailingListPayload { + subscription: MailingListSubscriber! +} + +type UnsubscribeFromMailingListPayload { + deletedMailingListSubscriberId: ID +} + +enum MailingListSubscriberStatus + @goModel( + model: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatus" + ) { + PENDING + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusPending" + ) + CONFIRMED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.MailingListSubscriberStatusConfirmed" + ) +} + +type MailingListSubscriber implements Node + @goModel( + model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber" + ) { + id: ID! + email: EmailAddr! + status: MailingListSubscriberStatus! + createdAt: Datetime! + updatedAt: Datetime! } `, BuiltIn: false}, {Name: "../../../../gqlutils/directives/session/schema.graphql", Input: `# Session directive for GraphQL APIs @@ -4698,6 +4821,151 @@ func (ec *executionContext) fieldContext_Identity_updatedAt(_ context.Context, f return fc, nil } +func (ec *executionContext) _MailingListSubscriber_id(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_id, + func(ctx context.Context) (any, error) { + return obj.ID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_email(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_email, + func(ctx context.Context) (any, error) { + return obj.Email, nil + }, + nil, + ec.marshalNEmailAddr2goᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddr, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_email(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type EmailAddr does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_status(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_status, + func(ctx context.Context) (any, error) { + return obj.Status, nil + }, + nil, + ec.marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_status(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type MailingListSubscriberStatus does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_createdAt, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + ec.marshalNDatetime2timeᚐTime, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _MailingListSubscriber_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.MailingListSubscriber) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_MailingListSubscriber_updatedAt, + func(ctx context.Context) (any, error) { + return obj.UpdatedAt, nil + }, + nil, + ec.marshalNDatetime2timeᚐTime, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_MailingListSubscriber_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "MailingListSubscriber", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -5491,6 +5759,108 @@ func (ec *executionContext) fieldContext_Mutation_recordSigningEvent(ctx context return fc, nil } +func (ec *executionContext) _Mutation_subscribeToMailingList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_subscribeToMailingList, + func(ctx context.Context) (any, error) { + return ec.Resolvers.Mutation().SubscribeToMailingList(ctx) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") + if err != nil { + var zeroVal *types.SubscribeToMailingListPayload + return zeroVal, err + } + if ec.Directives.Session == nil { + var zeroVal *types.SubscribeToMailingListPayload + return zeroVal, errors.New("directive session is not implemented") + } + return ec.Directives.Session(ctx, nil, directive0, required) + } + + next = directive1 + return next + }, + ec.marshalNSubscribeToMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSubscribeToMailingListPayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_subscribeToMailingList(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "subscription": + return ec.fieldContext_SubscribeToMailingListPayload_subscription(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type SubscribeToMailingListPayload", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Mutation_unsubscribeFromMailingList(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_unsubscribeFromMailingList, + func(ctx context.Context) (any, error) { + return ec.Resolvers.Mutation().UnsubscribeFromMailingList(ctx) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") + if err != nil { + var zeroVal *types.UnsubscribeFromMailingListPayload + return zeroVal, err + } + if ec.Directives.Session == nil { + var zeroVal *types.UnsubscribeFromMailingListPayload + return zeroVal, errors.New("directive session is not implemented") + } + return ec.Directives.Session(ctx, nil, directive0, required) + } + + next = directive1 + return next + }, + ec.marshalNUnsubscribeFromMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUnsubscribeFromMailingListPayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_unsubscribeFromMailingList(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "deletedMailingListSubscriberId": + return ec.fieldContext_UnsubscribeFromMailingListPayload_deletedMailingListSubscriberId(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type UnsubscribeFromMailingListPayload", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _NonDisclosureAgreement_fileName(ctx context.Context, field graphql.CollectedField, obj *types.NonDisclosureAgreement) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -6035,6 +6405,8 @@ func (ec *executionContext) fieldContext_Query_currentTrustCenter(_ context.Cont return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "nonDisclosureAgreement": return ec.fieldContext_TrustCenter_nonDisclosureAgreement(ctx, field) + case "viewerSubscription": + return ec.fieldContext_TrustCenter_viewerSubscription(ctx, field) case "organization": return ec.fieldContext_TrustCenter_organization(ctx, field) case "documents": @@ -6547,6 +6919,47 @@ func (ec *executionContext) fieldContext_SendMagicLinkPayload_success(_ context. return fc, nil } +func (ec *executionContext) _SubscribeToMailingListPayload_subscription(ctx context.Context, field graphql.CollectedField, obj *types.SubscribeToMailingListPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_SubscribeToMailingListPayload_subscription, + func(ctx context.Context) (any, error) { + return obj.Subscription, nil + }, + nil, + ec.marshalNMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListSubscriber, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_SubscribeToMailingListPayload_subscription(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "SubscribeToMailingListPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_MailingListSubscriber_id(ctx, field) + case "email": + return ec.fieldContext_MailingListSubscriber_email(ctx, field) + case "status": + return ec.fieldContext_MailingListSubscriber_status(ctx, field) + case "createdAt": + return ec.fieldContext_MailingListSubscriber_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_MailingListSubscriber_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriber", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _TrustCenter_id(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -6729,6 +7142,47 @@ func (ec *executionContext) fieldContext_TrustCenter_nonDisclosureAgreement(_ co return fc, nil } +func (ec *executionContext) _TrustCenter_viewerSubscription(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_TrustCenter_viewerSubscription, + func(ctx context.Context) (any, error) { + return ec.Resolvers.TrustCenter().ViewerSubscription(ctx, obj) + }, + nil, + ec.marshalOMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListSubscriber, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_TrustCenter_viewerSubscription(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "TrustCenter", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_MailingListSubscriber_id(ctx, field) + case "email": + return ec.fieldContext_MailingListSubscriber_email(ctx, field) + case "status": + return ec.fieldContext_MailingListSubscriber_status(ctx, field) + case "createdAt": + return ec.fieldContext_MailingListSubscriber_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_MailingListSubscriber_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type MailingListSubscriber", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _TrustCenter_organization(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -7951,6 +8405,35 @@ func (ec *executionContext) fieldContext_TrustCenterReferenceEdge_node(_ context return fc, nil } +func (ec *executionContext) _UnsubscribeFromMailingListPayload_deletedMailingListSubscriberId(ctx context.Context, field graphql.CollectedField, obj *types.UnsubscribeFromMailingListPayload) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_UnsubscribeFromMailingListPayload_deletedMailingListSubscriberId, + func(ctx context.Context) (any, error) { + return obj.DeletedMailingListSubscriberID, nil + }, + nil, + ec.marshalOID2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_UnsubscribeFromMailingListPayload_deletedMailingListSubscriberId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "UnsubscribeFromMailingListPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _UpdateFullNamePayload_success(ctx context.Context, field graphql.CollectedField, obj *types.UpdateFullNamePayload) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -10218,6 +10701,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._Organization(ctx, sel, obj) + case types.MailingListSubscriber: + return ec._MailingListSubscriber(ctx, sel, &obj) + case *types.MailingListSubscriber: + if obj == nil { + return graphql.Null + } + return ec._MailingListSubscriber(ctx, sel, obj) case types.Identity: return ec._Identity(ctx, sel, &obj) case *types.Identity: @@ -11438,6 +11928,65 @@ func (ec *executionContext) _Identity(ctx context.Context, sel ast.SelectionSet, return out } +var mailingListSubscriberImplementors = []string{"MailingListSubscriber", "Node"} + +func (ec *executionContext) _MailingListSubscriber(ctx context.Context, sel ast.SelectionSet, obj *types.MailingListSubscriber) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, mailingListSubscriberImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("MailingListSubscriber") + case "id": + out.Values[i] = ec._MailingListSubscriber_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "email": + out.Values[i] = ec._MailingListSubscriber_email(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "status": + out.Values[i] = ec._MailingListSubscriber_status(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createdAt": + out.Values[i] = ec._MailingListSubscriber_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatedAt": + out.Values[i] = ec._MailingListSubscriber_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -11526,6 +12075,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_recordSigningEvent(ctx, field) }) + case "subscribeToMailingList": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_subscribeToMailingList(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "unsubscribeFromMailingList": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_unsubscribeFromMailingList(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -12238,6 +12801,45 @@ func (ec *executionContext) _SendMagicLinkPayload(ctx context.Context, sel ast.S return out } +var subscribeToMailingListPayloadImplementors = []string{"SubscribeToMailingListPayload"} + +func (ec *executionContext) _SubscribeToMailingListPayload(ctx context.Context, sel ast.SelectionSet, obj *types.SubscribeToMailingListPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, subscribeToMailingListPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("SubscribeToMailingListPayload") + case "subscription": + out.Values[i] = ec._SubscribeToMailingListPayload_subscription(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var trustCenterImplementors = []string{"TrustCenter", "Node"} func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionSet, obj *types.TrustCenter) graphql.Marshaler { @@ -12362,6 +12964,39 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "viewerSubscription": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._TrustCenter_viewerSubscription(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "organization": field := field @@ -13114,6 +13749,42 @@ func (ec *executionContext) _TrustCenterReferenceEdge(ctx context.Context, sel a return out } +var unsubscribeFromMailingListPayloadImplementors = []string{"UnsubscribeFromMailingListPayload"} + +func (ec *executionContext) _UnsubscribeFromMailingListPayload(ctx context.Context, sel ast.SelectionSet, obj *types.UnsubscribeFromMailingListPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, unsubscribeFromMailingListPayloadImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("UnsubscribeFromMailingListPayload") + case "deletedMailingListSubscriberId": + out.Values[i] = ec._UnsubscribeFromMailingListPayload_deletedMailingListSubscriberId(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.Deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.ProcessDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var updateFullNamePayloadImplementors = []string{"UpdateFullNamePayload"} func (ec *executionContext) _UpdateFullNamePayload(ctx context.Context, sel ast.SelectionSet, obj *types.UpdateFullNamePayload) graphql.Marshaler { @@ -14823,6 +15494,44 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) marshalNMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListSubscriber(ctx context.Context, sel ast.SelectionSet, v *types.MailingListSubscriber) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._MailingListSubscriber(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus(ctx context.Context, v any) (coredata.MailingListSubscriberStatus, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus(ctx context.Context, sel ast.SelectionSet, v coredata.MailingListSubscriberStatus) graphql.Marshaler { + _ = sel + res := graphql.MarshalString(marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus = map[string]coredata.MailingListSubscriberStatus{ + "PENDING": coredata.MailingListSubscriberStatusPending, + "CONFIRMED": coredata.MailingListSubscriberStatusConfirmed, + } + marshalNMailingListSubscriberStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐMailingListSubscriberStatus = map[coredata.MailingListSubscriberStatus]string{ + coredata.MailingListSubscriberStatusPending: "PENDING", + coredata.MailingListSubscriberStatusConfirmed: "CONFIRMED", + } +) + func (ec *executionContext) marshalNNode2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐNode(ctx context.Context, sel ast.SelectionSet, v types.Node) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -14988,6 +15697,20 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S return res } +func (ec *executionContext) marshalNSubscribeToMailingListPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSubscribeToMailingListPayload(ctx context.Context, sel ast.SelectionSet, v types.SubscribeToMailingListPayload) graphql.Marshaler { + return ec._SubscribeToMailingListPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNSubscribeToMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐSubscribeToMailingListPayload(ctx context.Context, sel ast.SelectionSet, v *types.SubscribeToMailingListPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._SubscribeToMailingListPayload(ctx, sel, v) +} + func (ec *executionContext) marshalNTrustCenterAccess2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐTrustCenterAccess(ctx context.Context, sel ast.SelectionSet, v *types.TrustCenterAccess) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -15098,6 +15821,20 @@ func (ec *executionContext) marshalNTrustCenterReferenceEdge2ᚖgoᚗproboᚗinc return ec._TrustCenterReferenceEdge(ctx, sel, v) } +func (ec *executionContext) marshalNUnsubscribeFromMailingListPayload2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUnsubscribeFromMailingListPayload(ctx context.Context, sel ast.SelectionSet, v types.UnsubscribeFromMailingListPayload) graphql.Marshaler { + return ec._UnsubscribeFromMailingListPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUnsubscribeFromMailingListPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUnsubscribeFromMailingListPayload(ctx context.Context, sel ast.SelectionSet, v *types.UnsubscribeFromMailingListPayload) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._UnsubscribeFromMailingListPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNUpdateFullNameInput2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐUpdateFullNameInput(ctx context.Context, v any) (types.UpdateFullNameInput, error) { res, err := ec.unmarshalInputUpdateFullNameInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -15468,6 +16205,24 @@ func (ec *executionContext) marshalOElectronicSignature2ᚖgoᚗproboᚗincᚋpr return ec._ElectronicSignature(ctx, sel, v) } +func (ec *executionContext) unmarshalOID2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, v any) (*gid.GID, error) { + if v == nil { + return nil, nil + } + res, err := gid1.UnmarshalGIDScalar(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOID2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, sel ast.SelectionSet, v *gid.GID) graphql.Marshaler { + if v == nil { + return graphql.Null + } + _ = sel + _ = ctx + res := gid1.MarshalGIDScalar(*v) + return res +} + func (ec *executionContext) marshalOIdentity2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐIdentity(ctx context.Context, sel ast.SelectionSet, v *types.Identity) graphql.Marshaler { if v == nil { return graphql.Null @@ -15493,6 +16248,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return res } +func (ec *executionContext) marshalOMailingListSubscriber2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐMailingListSubscriber(ctx context.Context, sel ast.SelectionSet, v *types.MailingListSubscriber) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._MailingListSubscriber(ctx, sel, v) +} + func (ec *executionContext) marshalONonDisclosureAgreement2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐNonDisclosureAgreement(ctx context.Context, sel ast.SelectionSet, v *types.NonDisclosureAgreement) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/server/api/trust/v1/types/mailing_list_subscriber.go b/pkg/server/api/trust/v1/types/mailing_list_subscriber.go new file mode 100644 index 000000000..f864bcedf --- /dev/null +++ b/pkg/server/api/trust/v1/types/mailing_list_subscriber.go @@ -0,0 +1,44 @@ +// 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 types + +import ( + "time" + + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/mail" +) + +type MailingListSubscriber struct { + ID gid.GID `json:"id"` + Email mail.Addr `json:"email"` + Status coredata.MailingListSubscriberStatus `json:"status"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (MailingListSubscriber) IsNode() {} +func (m MailingListSubscriber) GetID() gid.GID { return m.ID } + +func NewMailingListSubscriber(s *coredata.MailingListSubscriber) *MailingListSubscriber { + return &MailingListSubscriber{ + ID: s.ID, + Email: s.Email, + Status: s.Status, + CreatedAt: s.CreatedAt, + UpdatedAt: s.UpdatedAt, + } +} diff --git a/pkg/server/api/trust/v1/types/types.go b/pkg/server/api/trust/v1/types/types.go index 6dec3dc0b..8ff3cf906 100644 --- a/pkg/server/api/trust/v1/types/types.go +++ b/pkg/server/api/trust/v1/types/types.go @@ -241,6 +241,10 @@ type SendMagicLinkPayload struct { Success bool `json:"success"` } +type SubscribeToMailingListPayload struct { + Subscription *MailingListSubscriber `json:"subscription"` +} + type TrustCenter struct { ID gid.GID `json:"id"` Active bool `json:"active"` @@ -248,6 +252,7 @@ type TrustCenter struct { LogoFileURL *string `json:"logoFileUrl,omitempty"` DarkLogoFileURL *string `json:"darkLogoFileUrl,omitempty"` NonDisclosureAgreement *NonDisclosureAgreement `json:"nonDisclosureAgreement,omitempty"` + ViewerSubscription *MailingListSubscriber `json:"viewerSubscription,omitempty"` Organization *Organization `json:"organization"` Documents *DocumentConnection `json:"documents"` Audits *AuditConnection `json:"audits"` @@ -314,6 +319,10 @@ type TrustCenterReferenceEdge struct { Node *TrustCenterReference `json:"node"` } +type UnsubscribeFromMailingListPayload struct { + DeletedMailingListSubscriberID *gid.GID `json:"deletedMailingListSubscriberId,omitempty"` +} + type UpdateFullNameInput struct { FullName string `json:"fullName"` } diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index ed2372839..ce0974612 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -19,6 +19,7 @@ import ( "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/saferedirect" "go.probo.inc/probo/pkg/server/api/authn" @@ -694,6 +695,62 @@ func (r *mutationResolver) RecordSigningEvent(ctx context.Context, input types.R return &types.RecordSigningEventPayload{Success: true}, nil } +// SubscribeToMailingList is the resolver for the subscribeToMailingList field. +func (r *mutationResolver) SubscribeToMailingList(ctx context.Context) (*types.SubscribeToMailingListPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, gqlutils.NotFoundf(ctx, "mailing list not found") + } + + mlSvc := r.MailmanService(ctx, trustCenter.ID.TenantID()) + identity := authn.IdentityFromContext(ctx) + + subscriber, err := mlSvc.CreateSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress, identity.FullName) + if err != nil { + if errors.Is(err, mailman.ErrSubscriberAlreadyExist) { + subscriber, err = mlSvc.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get existing mailing list subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } else { + r.logger.ErrorCtx(ctx, "cannot subscribe to mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + return &types.SubscribeToMailingListPayload{ + Subscription: types.NewMailingListSubscriber(subscriber), + }, nil +} + +// UnsubscribeFromMailingList is the resolver for the unsubscribeFromMailingList field. +func (r *mutationResolver) UnsubscribeFromMailingList(ctx context.Context) (*types.UnsubscribeFromMailingListPayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, gqlutils.NotFoundf(ctx, "mailing list not found") + } + + mlSvc := r.MailmanService(ctx, trustCenter.ID.TenantID()) + identity := authn.IdentityFromContext(ctx) + + subscriber, err := mlSvc.GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + if subscriber == nil { + return nil, gqlutils.NotFoundf(ctx, "mailing list subscription not found") + } + + if err := mlSvc.DeleteSubscriber(ctx, subscriber.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot unsubscribe from mailing list", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.UnsubscribeFromMailingListPayload{DeletedMailingListSubscriberID: &subscriber.ID}, nil +} + // FileURL is the resolver for the fileUrl field. func (r *nonDisclosureAgreementResolver) FileURL(ctx context.Context, obj *types.NonDisclosureAgreement) (string, error) { trustCenter := compliancepage.CompliancePageFromContext(ctx) @@ -983,6 +1040,31 @@ func (r *trustCenterResolver) NonDisclosureAgreement(ctx context.Context, obj *t return types.NewNonDisclosureAgreement(file), nil } +// ViewerSubscription is the resolver for the viewerSubscription field. +func (r *trustCenterResolver) ViewerSubscription(ctx context.Context, obj *types.TrustCenter) (*types.MailingListSubscriber, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + if trustCenter.MailingListID == nil { + return nil, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, nil + } + + subscriber, err := r.MailmanService(ctx, trustCenter.ID.TenantID()).GetSubscriber(ctx, *trustCenter.MailingListID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get mailing list subscription", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if subscriber == nil { + return nil, nil + } + + return types.NewMailingListSubscriber(subscriber), nil +} + // Organization is the resolver for the organization field. func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) { return obj.Organization, nil diff --git a/pkg/server/server.go b/pkg/server/server.go index 2de2d8017..a74162c7c 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -27,6 +27,7 @@ import ( "go.probo.inc/probo/pkg/connector" "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/server/api" @@ -46,6 +47,7 @@ type Config struct { Trust *trust.Service ESign *esign.Service Slack *slack.Service + Mailman *mailman.Service Cookie securecookie.Config TokenSecret string ConnectorRegistry *connector.ConnectorRegistry @@ -74,6 +76,7 @@ func NewServer(cfg Config) (*Server, error) { Trust: cfg.Trust, ESign: cfg.ESign, Slack: cfg.Slack, + Mailman: cfg.Mailman, Cookie: cfg.Cookie, TokenSecret: cfg.TokenSecret, ConnectorRegistry: cfg.ConnectorRegistry, diff --git a/pkg/trust/trust_center_service.go b/pkg/trust/trust_center_service.go index 2aadf54a6..2d3ec2970 100644 --- a/pkg/trust/trust_center_service.go +++ b/pkg/trust/trust_center_service.go @@ -333,3 +333,36 @@ func (s *TrustCenterService) EmailPresenterConfig(ctx context.Context, complianc return emailPresenterCfg, nil } + +func (s *TrustCenterService) GetMailingList( + ctx context.Context, + trustCenterID gid.GID, +) (*coredata.MailingList, error) { + var mailingList *coredata.MailingList + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + trustCenter := &coredata.TrustCenter{} + if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil { + return fmt.Errorf("cannot load trust center: %w", err) + } + + if trustCenter.MailingListID == nil { + return nil + } + + mailingList = &coredata.MailingList{} + if err := mailingList.LoadByID(ctx, conn, s.svc.scope, *trustCenter.MailingListID); err != nil { + return fmt.Errorf("cannot load mailing list: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return mailingList, nil +}