Add watermark to public trust center documents

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-11 17:37:56 +02:00
parent b06bd113f3
commit c3ea7a3cd0
11 changed files with 558 additions and 184 deletions

View File

@@ -16,7 +16,6 @@ export interface TrustCenterAudit {
report: { report: {
id: string; id: string;
filename: string; filename: string;
downloadUrl: string | null;
} | null; } | null;
} }
@@ -71,6 +70,12 @@ interface ExportDocumentPDFData {
}; };
} }
interface ExportReportPDFData {
exportReportPDF: {
data: string;
};
}
interface CreateTrustCenterAccessData { interface CreateTrustCenterAccessData {
createTrustCenterAccess: { createTrustCenterAccess: {
trustCenterAccess: { trustCenterAccess: {
@@ -111,7 +116,13 @@ interface AcceptNonDisclosureAgreementVariables {
}; };
} }
type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | CreateTrustCenterAccessVariables | AcceptNonDisclosureAgreementVariables | Record<string, never>; interface ExportReportPDFVariables {
input: {
reportId: string;
};
}
type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | ExportReportPDFVariables | CreateTrustCenterAccessVariables | AcceptNonDisclosureAgreementVariables | Record<string, never>;
async function trustCenterGraphQLRequest<T = unknown>( async function trustCenterGraphQLRequest<T = unknown>(
operationName: string, operationName: string,
@@ -189,7 +200,6 @@ const TRUST_CENTER_QUERY = `
report { report {
id id
filename filename
downloadUrl
} }
} }
} }
@@ -219,6 +229,16 @@ const EXPORT_DOCUMENT_PDF_MUTATION = `
} }
`; `;
const EXPORT_REPORT_PDF_MUTATION = `
mutation PublicTrustCenterAuditsExportReportPDFMutation(
$input: ExportReportPDFInput!
) {
exportReportPDF(input: $input) {
data
}
}
`;
const CREATE_TRUST_CENTER_ACCESS_MUTATION = ` const CREATE_TRUST_CENTER_ACCESS_MUTATION = `
mutation PublicTrustCenterAccessRequestDialogMutation( mutation PublicTrustCenterAccessRequestDialogMutation(
$input: CreateTrustCenterAccessInput! $input: CreateTrustCenterAccessInput!
@@ -304,6 +324,30 @@ export function useExportDocumentPDF() {
}); });
} }
export function useExportReportPDF() {
return useMutation<ExportReportPDFData, Error, string>({
mutationFn: async (reportId: string) => {
const result = await trustCenterGraphQLRequest<ExportReportPDFData>(
"PublicTrustCenterAuditsExportReportPDFMutation",
EXPORT_REPORT_PDF_MUTATION,
{ input: { reportId } }
);
if (result.errors && result.errors.length > 0) {
throw new Error(
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
);
}
if (!result.data) {
throw new Error("No data returned from mutation");
}
return result.data;
},
});
}
export function useCreateTrustCenterAccess() { export function useCreateTrustCenterAccess() {
return useMutation<CreateTrustCenterAccessData, Error, { trustCenterId: string; email: string; name: string }>({ return useMutation<CreateTrustCenterAccessData, Error, { trustCenterId: string; email: string; name: string }>({
mutationFn: async (input: { trustCenterId: string; email: string; name: string }) => { mutationFn: async (input: { trustCenterId: string; email: string; name: string }) => {

View File

@@ -9,11 +9,13 @@ import {
Button, Button,
IconArrowDown, IconArrowDown,
IconLock, IconLock,
useToast,
} from "@probo/ui"; } from "@probo/ui";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { sprintf } from "@probo/helpers"; import { sprintf } from "@probo/helpers";
import { FrameworkLogo } from "/components/FrameworkLogo"; import { FrameworkLogo } from "/components/FrameworkLogo";
import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog"; import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog";
import { useExportReportPDF } from "../../hooks/useTrustCenterQueries";
import type { TrustCenterAudit } from "../pages/PublicTrustCenterPage"; import type { TrustCenterAudit } from "../pages/PublicTrustCenterPage";
type Props = { type Props = {
@@ -30,6 +32,31 @@ export function PublicTrustCenterAudits({
trustCenterId trustCenterId
}: Props) { }: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const { toast } = useToast();
const mutation = useExportReportPDF();
const handleDownload = (report: NonNullable<TrustCenterAudit["report"]>) => {
mutation.mutate(report.id, {
onSuccess: (data) => {
if (data.exportReportPDF?.data) {
const link = window.document.createElement("a");
link.href = data.exportReportPDF.data;
link.download = `${report.filename}`;
window.document.body.appendChild(link);
link.click();
window.document.body.removeChild(link);
}
},
onError: () => {
toast({
title: __("Download Failed"),
description: __("Unable to download the report. Please try again."),
variant: "error",
});
},
});
};
if (audits.length === 0) { if (audits.length === 0) {
return ( return (
@@ -67,8 +94,6 @@ export function PublicTrustCenterAudits({
<Tbody> <Tbody>
{audits.map((audit) => { {audits.map((audit) => {
const hasReport = audit.report !== null; const hasReport = audit.report !== null;
const downloadUrl = audit.report?.downloadUrl;
const reportName = audit.report?.filename || __("Compliance Report");
return ( return (
<Tr key={audit.id}> <Tr key={audit.id}>
@@ -102,25 +127,15 @@ export function PublicTrustCenterAudits({
trustCenterId={trustCenterId} trustCenterId={trustCenterId}
organizationName={organizationName} organizationName={organizationName}
/> />
) : downloadUrl ? ( ) : (
<Button <Button
variant="secondary" variant="secondary"
icon={IconArrowDown} icon={IconArrowDown}
onClick={() => { onClick={() => handleDownload(audit.report!)}
const link = document.createElement('a'); disabled={mutation.isPending}
link.href = downloadUrl;
link.download = reportName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
> >
{__("Download")} {mutation.isPending ? __("Downloading...") : __("Download")}
</Button> </Button>
) : (
<span className="text-txt-tertiary text-sm">
{__("Not available")}
</span>
)} )}
</Td> </Td>
</Tr> </Tr>

6
go.mod
View File

@@ -18,6 +18,7 @@ require (
github.com/jackc/pgx/v5 v5.7.5 github.com/jackc/pgx/v5 v5.7.5
github.com/jhillyerd/enmime v1.3.0 github.com/jhillyerd/enmime v1.3.0
github.com/openai/openai-go v1.8.2 github.com/openai/openai-go v1.8.2
github.com/pdfcpu/pdfcpu v0.11.0
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/vektah/gqlparser/v2 v2.5.30 github.com/vektah/gqlparser/v2 v2.5.30
@@ -61,6 +62,9 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hhrutter/lzw v1.0.0 // indirect
github.com/hhrutter/pkcs7 v0.2.0 // indirect
github.com/hhrutter/tiff v1.0.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
@@ -96,6 +100,7 @@ require (
go.opentelemetry.io/otel/sdk v1.37.0 // indirect go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/image v0.27.0 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.15.0 // indirect
@@ -106,6 +111,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect sigs.k8s.io/yaml v1.5.0 // indirect
) )

14
go.sum
View File

@@ -92,6 +92,12 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+u
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -125,6 +131,8 @@ github.com/openai/openai-go v1.8.2 h1:UqSkJ1vCOPUpz9Ka5tS0324EJFEuOvMc+lA/EarJWP
github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= github.com/openai/openai-go v1.8.2/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pdfcpu/pdfcpu v0.11.0 h1:mL18Y3hSHzSezmnrzA21TqlayBOXuAx7BUzzZyroLGM=
github.com/pdfcpu/pdfcpu v0.11.0/go.mod h1:F1ca4GIVFdPtmgvIdvXAycAm88noyNxZwzr9CpTy+Mw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -222,8 +230,8 @@ go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
@@ -248,6 +256,8 @@ google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -73,7 +73,6 @@ type Framework implements Node {
type Report implements Node { type Report implements Node {
id: ID! id: ID!
filename: String! filename: String!
downloadUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
} }
type Audit implements Node { type Audit implements Node {
@@ -249,6 +248,10 @@ input ExportDocumentPDFInput {
documentId: ID! documentId: ID!
} }
input ExportReportPDFInput {
reportId: ID!
}
input AcceptNonDisclosureAgreementInput { input AcceptNonDisclosureAgreementInput {
trustCenterId: ID! trustCenterId: ID!
} }
@@ -257,6 +260,10 @@ type ExportDocumentPDFPayload {
data: String! data: String!
} }
type ExportReportPDFPayload {
data: String!
}
type AcceptNonDisclosureAgreementPayload{ type AcceptNonDisclosureAgreementPayload{
success: Boolean! success: Boolean!
} }
@@ -274,6 +281,10 @@ type Mutation {
input: ExportDocumentPDFInput! input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER) ): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
exportReportPDF(
input: ExportReportPDFInput!
): ExportReportPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement( acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput! input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER) ): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)

View File

@@ -48,7 +48,6 @@ type ResolverRoot interface {
Mutation() MutationResolver Mutation() MutationResolver
Organization() OrganizationResolver Organization() OrganizationResolver
Query() QueryResolver Query() QueryResolver
Report() ReportResolver
TrustCenter() TrustCenterResolver TrustCenter() TrustCenterResolver
} }
@@ -101,6 +100,10 @@ type ComplexityRoot struct {
Data func(childComplexity int) int Data func(childComplexity int) int
} }
ExportReportPDFPayload struct {
Data func(childComplexity int) int
}
Framework struct { Framework struct {
ID func(childComplexity int) int ID func(childComplexity int) int
Name func(childComplexity int) int Name func(childComplexity int) int
@@ -110,6 +113,7 @@ type ComplexityRoot struct {
AcceptNonDisclosureAgreement func(childComplexity int, input types.AcceptNonDisclosureAgreementInput) int AcceptNonDisclosureAgreement func(childComplexity int, input types.AcceptNonDisclosureAgreementInput) int
CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int CreateTrustCenterAccess func(childComplexity int, input types.CreateTrustCenterAccessInput) int
ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int ExportDocumentPDF func(childComplexity int, input types.ExportDocumentPDFInput) int
ExportReportPDF func(childComplexity int, input types.ExportReportPDFInput) int
} }
Organization struct { Organization struct {
@@ -130,7 +134,6 @@ type ComplexityRoot struct {
} }
Report struct { Report struct {
DownloadURL func(childComplexity int) int
Filename func(childComplexity int) int Filename func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
} }
@@ -183,6 +186,7 @@ type AuditResolver interface {
type MutationResolver interface { type MutationResolver interface {
CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error)
ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error)
ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error)
AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error)
} }
type OrganizationResolver interface { type OrganizationResolver interface {
@@ -191,9 +195,6 @@ type OrganizationResolver interface {
type QueryResolver interface { type QueryResolver interface {
TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error)
} }
type ReportResolver interface {
DownloadURL(ctx context.Context, obj *types.Report) (*string, error)
}
type TrustCenterResolver interface { type TrustCenterResolver interface {
NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error)
Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error)
@@ -342,6 +343,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.ExportDocumentPDFPayload.Data(childComplexity), true return e.complexity.ExportDocumentPDFPayload.Data(childComplexity), true
case "ExportReportPDFPayload.data":
if e.complexity.ExportReportPDFPayload.Data == nil {
break
}
return e.complexity.ExportReportPDFPayload.Data(childComplexity), true
case "Framework.id": case "Framework.id":
if e.complexity.Framework.ID == nil { if e.complexity.Framework.ID == nil {
break break
@@ -392,6 +400,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Mutation.ExportDocumentPDF(childComplexity, args["input"].(types.ExportDocumentPDFInput)), true return e.complexity.Mutation.ExportDocumentPDF(childComplexity, args["input"].(types.ExportDocumentPDFInput)), true
case "Mutation.exportReportPDF":
if e.complexity.Mutation.ExportReportPDF == nil {
break
}
args, err := ec.field_Mutation_exportReportPDF_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.ExportReportPDF(childComplexity, args["input"].(types.ExportReportPDFInput)), true
case "Organization.id": case "Organization.id":
if e.complexity.Organization.ID == nil { if e.complexity.Organization.ID == nil {
break break
@@ -453,13 +473,6 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Query.TrustCenterBySlug(childComplexity, args["slug"].(string)), true return e.complexity.Query.TrustCenterBySlug(childComplexity, args["slug"].(string)), true
case "Report.downloadUrl":
if e.complexity.Report.DownloadURL == nil {
break
}
return e.complexity.Report.DownloadURL(childComplexity), true
case "Report.filename": case "Report.filename":
if e.complexity.Report.Filename == nil { if e.complexity.Report.Filename == nil {
break break
@@ -675,6 +688,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputAcceptNonDisclosureAgreementInput, ec.unmarshalInputAcceptNonDisclosureAgreementInput,
ec.unmarshalInputCreateTrustCenterAccessInput, ec.unmarshalInputCreateTrustCenterAccessInput,
ec.unmarshalInputExportDocumentPDFInput, ec.unmarshalInputExportDocumentPDFInput,
ec.unmarshalInputExportReportPDFInput,
) )
first := true first := true
@@ -847,7 +861,6 @@ type Framework implements Node {
type Report implements Node { type Report implements Node {
id: ID! id: ID!
filename: String! filename: String!
downloadUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
} }
type Audit implements Node { type Audit implements Node {
@@ -1023,6 +1036,10 @@ input ExportDocumentPDFInput {
documentId: ID! documentId: ID!
} }
input ExportReportPDFInput {
reportId: ID!
}
input AcceptNonDisclosureAgreementInput { input AcceptNonDisclosureAgreementInput {
trustCenterId: ID! trustCenterId: ID!
} }
@@ -1031,6 +1048,10 @@ type ExportDocumentPDFPayload {
data: String! data: String!
} }
type ExportReportPDFPayload {
data: String!
}
type AcceptNonDisclosureAgreementPayload{ type AcceptNonDisclosureAgreementPayload{
success: Boolean! success: Boolean!
} }
@@ -1048,6 +1069,10 @@ type Mutation {
input: ExportDocumentPDFInput! input: ExportDocumentPDFInput!
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER) ): ExportDocumentPDFPayload! @mustBeAuthenticated(role: USER)
exportReportPDF(
input: ExportReportPDFInput!
): ExportReportPDFPayload! @mustBeAuthenticated(role: USER)
acceptNonDisclosureAgreement( acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput! input: AcceptNonDisclosureAgreementInput!
): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER) ): AcceptNonDisclosureAgreementPayload! @mustBeAuthenticated(role: USER)
@@ -1157,6 +1182,29 @@ func (ec *executionContext) field_Mutation_exportDocumentPDF_argsInput(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_exportReportPDF_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_exportReportPDF_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_exportReportPDF_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.ExportReportPDFInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNExportReportPDFInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFInput(ctx, tmp)
}
var zeroVal types.ExportReportPDFInput
return zeroVal, nil
}
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -1712,8 +1760,6 @@ func (ec *executionContext) fieldContext_Audit_report(_ context.Context, field g
return ec.fieldContext_Report_id(ctx, field) return ec.fieldContext_Report_id(ctx, field)
case "filename": case "filename":
return ec.fieldContext_Report_filename(ctx, field) return ec.fieldContext_Report_filename(ctx, field)
case "downloadUrl":
return ec.fieldContext_Report_downloadUrl(ctx, field)
} }
return nil, fmt.Errorf("no field named %q was found under type Report", field.Name) return nil, fmt.Errorf("no field named %q was found under type Report", field.Name)
}, },
@@ -2353,6 +2399,50 @@ func (ec *executionContext) fieldContext_ExportDocumentPDFPayload_data(_ context
return fc, nil return fc, nil
} }
func (ec *executionContext) _ExportReportPDFPayload_data(ctx context.Context, field graphql.CollectedField, obj *types.ExportReportPDFPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ExportReportPDFPayload_data(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Data, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ExportReportPDFPayload_data(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ExportReportPDFPayload",
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) _Framework_id(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) { func (ec *executionContext) _Framework_id(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Framework_id(ctx, field) fc, err := ec.fieldContext_Framework_id(ctx, field)
if err != nil { if err != nil {
@@ -2613,6 +2703,92 @@ func (ec *executionContext) fieldContext_Mutation_exportDocumentPDF(ctx context.
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_exportReportPDF(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_exportReportPDF(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
directive0 := func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().ExportReportPDF(rctx, fc.Args["input"].(types.ExportReportPDFInput))
}
directive1 := func(ctx context.Context) (any, error) {
role, err := ec.unmarshalORole2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRole(ctx, "USER")
if err != nil {
var zeroVal *types.ExportReportPDFPayload
return zeroVal, err
}
if ec.directives.MustBeAuthenticated == nil {
var zeroVal *types.ExportReportPDFPayload
return zeroVal, errors.New("directive mustBeAuthenticated is not implemented")
}
return ec.directives.MustBeAuthenticated(ctx, nil, directive0, role)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*types.ExportReportPDFPayload); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/getprobo/probo/pkg/server/api/trust/v1/types.ExportReportPDFPayload`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.ExportReportPDFPayload)
fc.Result = res
return ec.marshalNExportReportPDFPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_exportReportPDF(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 "data":
return ec.fieldContext_ExportReportPDFPayload_data(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ExportReportPDFPayload", 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_exportReportPDF_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_acceptNonDisclosureAgreement(ctx, field) fc, err := ec.fieldContext_Mutation_acceptNonDisclosureAgreement(ctx, field)
if err != nil { if err != nil {
@@ -3320,74 +3496,6 @@ func (ec *executionContext) fieldContext_Report_filename(_ context.Context, fiel
return fc, nil return fc, nil
} }
func (ec *executionContext) _Report_downloadUrl(ctx context.Context, field graphql.CollectedField, obj *types.Report) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Report_downloadUrl(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
directive0 := func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Report().DownloadURL(rctx, obj)
}
directive1 := func(ctx context.Context) (any, error) {
role, err := ec.unmarshalORole2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRole(ctx, "USER")
if err != nil {
var zeroVal *string
return zeroVal, err
}
if ec.directives.MustBeAuthenticated == nil {
var zeroVal *string
return zeroVal, errors.New("directive mustBeAuthenticated is not implemented")
}
return ec.directives.MustBeAuthenticated(ctx, obj, directive0, role)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*string); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *string`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Report_downloadUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Report",
Field: field,
IsMethod: true,
IsResolver: true,
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) _TrustCenter_id(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { func (ec *executionContext) _TrustCenter_id(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_TrustCenter_id(ctx, field) fc, err := ec.fieldContext_TrustCenter_id(ctx, field)
if err != nil { if err != nil {
@@ -6609,6 +6717,33 @@ func (ec *executionContext) unmarshalInputExportDocumentPDFInput(ctx context.Con
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputExportReportPDFInput(ctx context.Context, obj any) (types.ExportReportPDFInput, error) {
var it types.ExportReportPDFInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"reportId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "reportId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reportId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.ReportID = data
}
}
return it, nil
}
// endregion **************************** input.gotpl ***************************** // endregion **************************** input.gotpl *****************************
// region ************************** interface.gotpl *************************** // region ************************** interface.gotpl ***************************
@@ -7132,6 +7267,45 @@ func (ec *executionContext) _ExportDocumentPDFPayload(ctx context.Context, sel a
return out return out
} }
var exportReportPDFPayloadImplementors = []string{"ExportReportPDFPayload"}
func (ec *executionContext) _ExportReportPDFPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ExportReportPDFPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, exportReportPDFPayloadImplementors)
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("ExportReportPDFPayload")
case "data":
out.Values[i] = ec._ExportReportPDFPayload_data(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 frameworkImplementors = []string{"Framework", "Node"} var frameworkImplementors = []string{"Framework", "Node"}
func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet, obj *types.Framework) graphql.Marshaler { func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet, obj *types.Framework) graphql.Marshaler {
@@ -7209,6 +7383,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "exportReportPDF":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_exportReportPDF(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "acceptNonDisclosureAgreement": case "acceptNonDisclosureAgreement":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_acceptNonDisclosureAgreement(ctx, field) return ec._Mutation_acceptNonDisclosureAgreement(ctx, field)
@@ -7447,46 +7628,13 @@ func (ec *executionContext) _Report(ctx context.Context, sel ast.SelectionSet, o
case "id": case "id":
out.Values[i] = ec._Report_id(ctx, field, obj) out.Values[i] = ec._Report_id(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) out.Invalids++
} }
case "filename": case "filename":
out.Values[i] = ec._Report_filename(ctx, field, obj) out.Values[i] = ec._Report_filename(ctx, field, obj)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
atomic.AddUint32(&out.Invalids, 1) out.Invalids++
} }
case "downloadUrl":
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._Report_downloadUrl(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) })
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -8636,6 +8784,25 @@ func (ec *executionContext) marshalNExportDocumentPDFPayload2ᚖgithubᚗcomᚋg
return ec._ExportDocumentPDFPayload(ctx, sel, v) return ec._ExportDocumentPDFPayload(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNExportReportPDFInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFInput(ctx context.Context, v any) (types.ExportReportPDFInput, error) {
res, err := ec.unmarshalInputExportReportPDFInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNExportReportPDFPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFPayload(ctx context.Context, sel ast.SelectionSet, v types.ExportReportPDFPayload) graphql.Marshaler {
return ec._ExportReportPDFPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNExportReportPDFPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFPayload(ctx context.Context, sel ast.SelectionSet, v *types.ExportReportPDFPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ExportReportPDFPayload(ctx, sel, v)
}
func (ec *executionContext) marshalNFramework2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v types.Framework) graphql.Marshaler { func (ec *executionContext) marshalNFramework2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v types.Framework) graphql.Marshaler {
return ec._Framework(ctx, sel, &v) return ec._Framework(ctx, sel, &v)
} }

View File

@@ -83,6 +83,14 @@ type ExportDocumentPDFPayload struct {
Data string `json:"data"` Data string `json:"data"`
} }
type ExportReportPDFInput struct {
ReportID gid.GID `json:"reportId"`
}
type ExportReportPDFPayload struct {
Data string `json:"data"`
}
type Framework struct { type Framework struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -116,7 +124,6 @@ type Query struct {
type Report struct { type Report struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Filename string `json:"filename"` Filename string `json:"filename"`
DownloadURL *string `json:"downloadUrl,omitempty"`
} }
func (Report) IsNode() {} func (Report) IsNode() {}

View File

@@ -105,7 +105,15 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
return nil, fmt.Errorf("user has not accepted NDA") return nil, fmt.Errorf("user has not accepted NDA")
} }
pdf, err := privateTrustService.Documents.ExportPDF(ctx, input.DocumentID) userEmail := ""
if userData != nil {
userEmail = userData.EmailAddress
}
if tokenData != nil {
userEmail = tokenData.GetEmail()
}
pdf, err := privateTrustService.Documents.ExportPDF(ctx, input.DocumentID, userEmail)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot export document PDF: %w", err)) panic(fmt.Errorf("cannot export document PDF: %w", err))
} }
@@ -115,6 +123,49 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex
}, nil }, nil
} }
// ExportReportPDF is the resolver for the exportReportPDF field.
func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) {
privateTrustService, err := r.PrivateTrustService(ctx, input.ReportID.TenantID())
if err != nil {
return nil, fmt.Errorf("cannot export report PDF: %w", err)
}
hasAcceptedNDA := false
userData := r.UserFromContext(ctx)
if userData != nil {
hasAcceptedNDA = true
}
tokenData := TokenAccessFromContext(ctx)
if tokenData != nil {
hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail())
if err != nil {
panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err))
}
}
if !hasAcceptedNDA {
return nil, fmt.Errorf("user has not accepted NDA")
}
userEmail := ""
if userData != nil {
userEmail = userData.EmailAddress
}
if tokenData != nil {
userEmail = tokenData.GetEmail()
}
pdf, err := privateTrustService.Reports.ExportPDF(ctx, input.ReportID, userEmail)
if err != nil {
panic(fmt.Errorf("cannot export report PDF: %w", err))
}
return &types.ExportReportPDFPayload{
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
}, nil
}
// AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field. // AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field.
func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) { func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) {
privateTrustService, err := r.PrivateTrustService(ctx, input.TrustCenterID.TenantID()) privateTrustService, err := r.PrivateTrustService(ctx, input.TrustCenterID.TenantID())
@@ -171,39 +222,6 @@ func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*ty
return response, nil return response, nil
} }
// DownloadURL is the resolver for the downloadUrl field.
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
if err != nil {
return nil, fmt.Errorf("cannot generate download URL: %w", err)
}
hasAcceptedNDA := false
userData := UserFromContext(ctx)
if userData != nil {
hasAcceptedNDA = true
}
tokenData := TokenAccessFromContext(ctx)
if tokenData != nil {
hasAcceptedNDA, err = privateTrustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, tokenData.TrustCenterID, tokenData.GetEmail())
if err != nil {
panic(fmt.Errorf("cannot check if user has accepted NDA: %w", err))
}
}
if !hasAcceptedNDA {
return nil, fmt.Errorf("user has not accepted NDA")
}
url, err := privateTrustService.Reports.GenerateDownloadURL(ctx, obj.ID, r.trustAuthCfg.ReportURLDuration)
if err != nil {
panic(fmt.Errorf("cannot generate download URL: %w", err))
}
return url, nil
}
// NdaFileURL is the resolver for the ndaFileUrl field. // NdaFileURL is the resolver for the ndaFileUrl field.
func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) {
privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID()) privateTrustService, err := r.PrivateTrustService(ctx, obj.ID.TenantID())
@@ -324,9 +342,6 @@ func (r *Resolver) Organization() schema.OrganizationResolver { return &organiza
// Query returns schema.QueryResolver implementation. // Query returns schema.QueryResolver implementation.
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
// Report returns schema.ReportResolver implementation.
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
// TrustCenter returns schema.TrustCenterResolver implementation. // TrustCenter returns schema.TrustCenterResolver implementation.
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} } func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
@@ -334,5 +349,4 @@ type auditResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver } type organizationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
type reportResolver struct{ *Resolver }
type trustCenterResolver struct{ *Resolver } type trustCenterResolver struct{ *Resolver }

View File

@@ -24,6 +24,7 @@ import (
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/html2pdf" "github.com/getprobo/probo/pkg/html2pdf"
"github.com/getprobo/probo/pkg/page" "github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/watermarkpdf"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
) )
@@ -86,6 +87,7 @@ func (s *DocumentService) ListForOrganizationId(
func (s *DocumentService) ExportPDF( func (s *DocumentService) ExportPDF(
ctx context.Context, ctx context.Context,
documentID gid.GID, documentID gid.GID,
email string,
) ([]byte, error) { ) ([]byte, error) {
document := &coredata.Document{} document := &coredata.Document{}
version := &coredata.DocumentVersion{} version := &coredata.DocumentVersion{}
@@ -169,5 +171,11 @@ func (s *DocumentService) ExportPDF(
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot read PDF data: %w", err) return nil, fmt.Errorf("cannot read PDF data: %w", err)
} }
return pdfData, nil
watermarkedPDF, err := watermarkpdf.AddConfidentialWithTimestamp(pdfData, email)
if err != nil {
return nil, fmt.Errorf("cannot add watermark to PDF: %w", err)
}
return watermarkedPDF, nil
} }

View File

@@ -17,12 +17,14 @@ package trust
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"time" "time"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/getprobo/probo/pkg/coredata" "github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/watermarkpdf"
"go.gearno.de/kit/pg" "go.gearno.de/kit/pg"
) )
@@ -82,3 +84,35 @@ func (s ReportService) GenerateDownloadURL(
return &presignedReq.URL, nil return &presignedReq.URL, nil
} }
func (s ReportService) ExportPDF(
ctx context.Context,
reportID gid.GID,
email string,
) ([]byte, error) {
report, err := s.Get(ctx, reportID)
if err != nil {
return nil, fmt.Errorf("cannot get report: %w", err)
}
result, err := s.svc.s3.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(s.svc.bucket),
Key: aws.String(report.ObjectKey),
})
if err != nil {
return nil, fmt.Errorf("cannot download PDF from S3: %w", err)
}
defer result.Body.Close()
pdfData, err := io.ReadAll(result.Body)
if err != nil {
return nil, fmt.Errorf("cannot read PDF data: %w", err)
}
watermarkedPDF, err := watermarkpdf.AddConfidentialWithTimestamp(pdfData, email)
if err != nil {
return nil, fmt.Errorf("cannot add watermark to PDF: %w", err)
}
return watermarkedPDF, nil
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// 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 watermarkpdf
import (
"bytes"
"fmt"
"strings"
"time"
"github.com/pdfcpu/pdfcpu/pkg/api"
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
)
func AddConfidentialWithTimestamp(pdfData []byte, email string) ([]byte, error) {
reader := bytes.NewReader(pdfData)
var buf bytes.Buffer
// Replace email with invisible characters to prevent auto-linking
formattedEmail := strings.ReplaceAll(email, "@", "\u200B@\u200B")
formattedEmail = strings.ReplaceAll(formattedEmail, ".", "\u200B.\u200B")
watermarkText := strings.Join([]string{
"Confidential",
formattedEmail,
time.Now().Format("02/01/2006"),
}, "\n")
watermarkConf := model.DefaultWatermarkConfig()
watermarkConf.Mode = model.WMText
watermarkConf.TextString = watermarkText
watermarkConf.FontName = "Helvetica"
watermarkConf.FontSize = 120
watermarkConf.Rotation = 55
watermarkConf.Opacity = 0.20
watermarkConf.OnTop = true
watermarkConf.ScaleAbs = true
watermarkConf.Update = false
err := api.AddWatermarks(reader, &buf, nil, watermarkConf, nil)
if err != nil {
return nil, fmt.Errorf("failed to add watermark: %w", err)
}
return buf.Bytes(), nil
}