From 8b5b9e732f335f08240b64c030fa85b16c30faf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Thu, 29 Jan 2026 16:22:23 +0400 Subject: [PATCH] Add new console compliance page brand tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../CompliancePageBrandPageQuery.graphql.ts | 167 ++++++++++++++++ .../compliance-page/CompliancePageLayout.tsx | 6 +- .../brand/CompliancePageBrandPage.tsx | 25 +++ .../brand/CompliancePageBrandPageLoader.tsx | 32 +++ .../organizations/compliance-page/routes.ts | 5 + pkg/probo/trust_center_service.go | 112 +++++++++-- pkg/server/api/console/v1/schema.graphql | 2 + pkg/server/api/console/v1/schema/schema.go | 183 +++++++++++++++++- pkg/server/api/console/v1/types/types.go | 22 ++- pkg/server/api/console/v1/v1_resolver.go | 34 ++++ pkg/trust/trust_center_service.go | 120 ++++++------ 11 files changed, 610 insertions(+), 98 deletions(-) create mode 100644 apps/console/src/__generated__/core/CompliancePageBrandPageQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPage.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPageLoader.tsx diff --git a/apps/console/src/__generated__/core/CompliancePageBrandPageQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageBrandPageQuery.graphql.ts new file mode 100644 index 000000000..3e948af62 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageBrandPageQuery.graphql.ts @@ -0,0 +1,167 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type CompliancePageBrandPageQuery$variables = { + organizationId: string; +}; +export type CompliancePageBrandPageQuery$data = { + readonly organization: { + readonly compliancePage?: { + readonly darkLogoFileUrl: string | null | undefined; + readonly logoFileUrl: string | null | undefined; + } | null | undefined; + }; +}; +export type CompliancePageBrandPageQuery = { + response: CompliancePageBrandPageQuery$data; + variables: CompliancePageBrandPageQuery$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": "logoFileUrl", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "darkLogoFileUrl", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageBrandPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageBrandPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + }, + (v4/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "17e4be042ed0e09a4b299d38f33dff32", + "id": null, + "metadata": {}, + "name": "CompliancePageBrandPageQuery", + "operationKind": "query", + "text": "query CompliancePageBrandPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n logoFileUrl\n darkLogoFileUrl\n id\n }\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "3364d36a8dd77d39d00d50f451a027a7"; + +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 ea2f355a5..7d045155a 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, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui"; +import { Badge, 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"; @@ -52,6 +52,10 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery {__("Overview")} + + + {__("Brand")} + {__("Domain")} diff --git a/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPage.tsx b/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPage.tsx new file mode 100644 index 000000000..923f89c8a --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPage.tsx @@ -0,0 +1,25 @@ +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageBrandPageQuery } from "#/__generated__/core/CompliancePageBrandPageQuery.graphql"; + +export const compliancePageBrandPageQuery = graphql` + query CompliancePageBrandPageQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + ... on Organization { + compliancePage: trustCenter { + logoFileUrl + darkLogoFileUrl + } + } + } + } +`; + +export function CompliancePageBrandPage(props: { queryRef: PreloadedQuery }) { + const { queryRef } = props; + + const { organization } = usePreloadedQuery(compliancePageBrandPageQuery, queryRef); + + return
{JSON.stringify(organization, null, 2)}
; +} diff --git a/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPageLoader.tsx b/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPageLoader.tsx new file mode 100644 index 000000000..a2b873d82 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/brand/CompliancePageBrandPageLoader.tsx @@ -0,0 +1,32 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import type { CompliancePageBrandPageQuery } from "#/__generated__/core/CompliancePageBrandPageQuery.graphql"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; + +import { CompliancePageBrandPage, compliancePageBrandPageQuery } from "./CompliancePageBrandPage"; + +function CompliancePageBrandPageQueryLoader() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] = useQueryLoader(compliancePageBrandPageQuery); + + useEffect(() => { + if (!queryRef) { + loadQuery({ organizationId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function CompliancePageBrandPageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/routes.ts b/apps/console/src/pages/organizations/compliance-page/routes.ts index 7fc03a133..b3d296fb6 100644 --- a/apps/console/src/pages/organizations/compliance-page/routes.ts +++ b/apps/console/src/pages/organizations/compliance-page/routes.ts @@ -22,6 +22,11 @@ export const compliancePageRoutes = [ import("#/pages/organizations/compliance-page/domain/CompliancePageDomainPageLoader"), ), }, + { + path: "brand", + Fallback: LinkCardSkeleton, + Component: lazy(() => import("#/pages/organizations/compliance-page/brand/CompliancePageBrandPageLoader")), + }, { path: "references", Fallback: LinkCardSkeleton, diff --git a/pkg/probo/trust_center_service.go b/pkg/probo/trust_center_service.go index 4c869d139..458693c93 100644 --- a/pkg/probo/trust_center_service.go +++ b/pkg/probo/trust_center_service.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "mime" - "net/url" "path/filepath" "time" @@ -338,23 +337,102 @@ func (s TrustCenterService) GenerateNDAFileURL( return nil, nil } - presignClient := s3.NewPresignClient(s.svc.s3) - - encodedFilename := url.QueryEscape(file.FileName) - contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", - encodedFilename, encodedFilename) - - presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ - Bucket: aws.String(s.svc.bucket), - Key: aws.String(file.FileKey), - ResponseCacheControl: aws.String("max-age=3600, public"), - ResponseContentDisposition: aws.String(contentDisposition), - }, func(opts *s3.PresignOptions) { - opts.Expires = expiresIn - }) + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) if err != nil { - return nil, fmt.Errorf("cannot presign GetObject request: %w", err) + return nil, fmt.Errorf("cannot generate file URL: %w", err) } - return &presignedReq.URL, nil + return &presignedURL, nil +} + +func (s TrustCenterService) GenerateLogoURL( + ctx context.Context, + compliancePageID gid.GID, + expiresIn time.Duration, +) (*string, error) { + file := &coredata.File{} + compliancePage := &coredata.TrustCenter{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := compliancePage.LoadByID(ctx, conn, s.svc.scope, compliancePageID); err != nil { + return fmt.Errorf("cannot load compliance page: %w", err) + } + + if compliancePage.LogoFileID == nil { + return nil + } + + if err := file.LoadByID(ctx, conn, s.svc.scope, *compliancePage.LogoFileID); err != nil { + return fmt.Errorf("cannot load file: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + if compliancePage.LogoFileID == nil { + return nil, nil + } + + if file.FileKey == "" { + return nil, nil + } + + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) + if err != nil { + return nil, fmt.Errorf("cannot generate file URL: %w", err) + } + + return &presignedURL, nil +} + +func (s TrustCenterService) GenerateDarkLogoURL( + ctx context.Context, + compliancePageID gid.GID, + expiresIn time.Duration, +) (*string, error) { + file := &coredata.File{} + compliancePage := &coredata.TrustCenter{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := compliancePage.LoadByID(ctx, conn, s.svc.scope, compliancePageID); err != nil { + return fmt.Errorf("cannot load compliance page: %w", err) + } + + if compliancePage.DarkLogoFileID == nil { + return nil + } + + if err := file.LoadByID(ctx, conn, s.svc.scope, *compliancePage.DarkLogoFileID); err != nil { + return fmt.Errorf("cannot load file: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + if compliancePage.LogoFileID == nil { + return nil, nil + } + + if file.FileKey == "" { + return nil, nil + } + + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) + if err != nil { + return nil, fmt.Errorf("cannot generate file URL: %w", err) + } + + return &presignedURL, nil } diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index bc0fff325..f05f1f226 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -1562,6 +1562,8 @@ input VendorFilter { type TrustCenter implements Node { id: ID! active: Boolean! + logoFileUrl: String @goField(forceResolver: true) + darkLogoFileUrl: String @goField(forceResolver: true) ndaFileName: String ndaFileUrl: String @goField(forceResolver: true) createdAt: Datetime! diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 02e046dcd..8cb9c5fc8 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -1495,16 +1495,18 @@ type ComplexityRoot struct { } TrustCenter struct { - Accesses func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) int - Active func(childComplexity int) int - CreatedAt func(childComplexity int) int - ID func(childComplexity int) int - NdaFileName func(childComplexity int) int - NdaFileURL func(childComplexity int) int - Organization func(childComplexity int) int - Permission func(childComplexity int, action string) int - References func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) int - UpdatedAt func(childComplexity int) int + Accesses func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) int + Active func(childComplexity int) int + CreatedAt func(childComplexity int) int + DarkLogoFileURL func(childComplexity int) int + ID func(childComplexity int) int + LogoFileURL func(childComplexity int) int + NdaFileName func(childComplexity int) int + NdaFileURL func(childComplexity int) int + Organization func(childComplexity int) int + Permission func(childComplexity int, action string) int + References func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterReferenceOrderField]) int + UpdatedAt func(childComplexity int) int } TrustCenterAccess struct { @@ -2374,6 +2376,9 @@ type TransferImpactAssessmentConnectionResolver interface { TotalCount(ctx context.Context, obj *types.TransferImpactAssessmentConnection) (int, error) } type TrustCenterResolver interface { + LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) + DarkLogoFileURL(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) @@ -8575,12 +8580,24 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin } return e.complexity.TrustCenter.CreatedAt(childComplexity), true + case "TrustCenter.darkLogoFileUrl": + if e.complexity.TrustCenter.DarkLogoFileURL == nil { + break + } + + return e.complexity.TrustCenter.DarkLogoFileURL(childComplexity), true case "TrustCenter.id": if e.complexity.TrustCenter.ID == nil { break } return e.complexity.TrustCenter.ID(childComplexity), true + case "TrustCenter.logoFileUrl": + if e.complexity.TrustCenter.LogoFileURL == nil { + break + } + + return e.complexity.TrustCenter.LogoFileURL(childComplexity), true case "TrustCenter.ndaFileName": if e.complexity.TrustCenter.NdaFileName == nil { break @@ -11873,6 +11890,8 @@ input VendorFilter { type TrustCenter implements Node { id: ID! active: Boolean! + logoFileUrl: String @goField(forceResolver: true) + darkLogoFileUrl: String @goField(forceResolver: true) ndaFileName: String ndaFileUrl: String @goField(forceResolver: true) createdAt: Datetime! @@ -28154,6 +28173,10 @@ func (ec *executionContext) fieldContext_DeleteTrustCenterNDAPayload_trustCenter return ec.fieldContext_TrustCenter_id(ctx, field) case "active": return ec.fieldContext_TrustCenter_active(ctx, field) + case "logoFileUrl": + return ec.fieldContext_TrustCenter_logoFileUrl(ctx, field) + case "darkLogoFileUrl": + return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "ndaFileName": return ec.fieldContext_TrustCenter_ndaFileName(ctx, field) case "ndaFileUrl": @@ -42676,6 +42699,10 @@ func (ec *executionContext) fieldContext_Organization_trustCenter(_ context.Cont return ec.fieldContext_TrustCenter_id(ctx, field) case "active": return ec.fieldContext_TrustCenter_active(ctx, field) + case "logoFileUrl": + return ec.fieldContext_TrustCenter_logoFileUrl(ctx, field) + case "darkLogoFileUrl": + return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "ndaFileName": return ec.fieldContext_TrustCenter_ndaFileName(ctx, field) case "ndaFileUrl": @@ -50470,6 +50497,64 @@ func (ec *executionContext) fieldContext_TrustCenter_active(_ context.Context, f return fc, nil } +func (ec *executionContext) _TrustCenter_logoFileUrl(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_TrustCenter_logoFileUrl, + func(ctx context.Context) (any, error) { + return ec.resolvers.TrustCenter().LogoFileURL(ctx, obj) + }, + nil, + ec.marshalOString2ᚖstring, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_TrustCenter_logoFileUrl(_ 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) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _TrustCenter_darkLogoFileUrl(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_TrustCenter_darkLogoFileUrl, + func(ctx context.Context) (any, error) { + return ec.resolvers.TrustCenter().DarkLogoFileURL(ctx, obj) + }, + nil, + ec.marshalOString2ᚖstring, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_TrustCenter_darkLogoFileUrl(_ 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) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _TrustCenter_ndaFileName(ctx context.Context, field graphql.CollectedField, obj *types.TrustCenter) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -51887,6 +51972,10 @@ func (ec *executionContext) fieldContext_TrustCenterEdge_node(_ context.Context, return ec.fieldContext_TrustCenter_id(ctx, field) case "active": return ec.fieldContext_TrustCenter_active(ctx, field) + case "logoFileUrl": + return ec.fieldContext_TrustCenter_logoFileUrl(ctx, field) + case "darkLogoFileUrl": + return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "ndaFileName": return ec.fieldContext_TrustCenter_ndaFileName(ctx, field) case "ndaFileUrl": @@ -54292,6 +54381,10 @@ func (ec *executionContext) fieldContext_UpdateTrustCenterPayload_trustCenter(_ return ec.fieldContext_TrustCenter_id(ctx, field) case "active": return ec.fieldContext_TrustCenter_active(ctx, field) + case "logoFileUrl": + return ec.fieldContext_TrustCenter_logoFileUrl(ctx, field) + case "darkLogoFileUrl": + return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "ndaFileName": return ec.fieldContext_TrustCenter_ndaFileName(ctx, field) case "ndaFileUrl": @@ -54777,6 +54870,10 @@ func (ec *executionContext) fieldContext_UploadTrustCenterNDAPayload_trustCenter return ec.fieldContext_TrustCenter_id(ctx, field) case "active": return ec.fieldContext_TrustCenter_active(ctx, field) + case "logoFileUrl": + return ec.fieldContext_TrustCenter_logoFileUrl(ctx, field) + case "darkLogoFileUrl": + return ec.fieldContext_TrustCenter_darkLogoFileUrl(ctx, field) case "ndaFileName": return ec.fieldContext_TrustCenter_ndaFileName(ctx, field) case "ndaFileUrl": @@ -84633,6 +84730,72 @@ func (ec *executionContext) _TrustCenter(ctx context.Context, sel ast.SelectionS if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "logoFileUrl": + 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_logoFileUrl(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 "darkLogoFileUrl": + 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_darkLogoFileUrl(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 "ndaFileName": out.Values[i] = ec._TrustCenter_ndaFileName(ctx, field, obj) case "ndaFileUrl": diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index e431cfa04..e23f62629 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -1841,16 +1841,18 @@ type TransferImpactAssessmentFilter struct { } type TrustCenter struct { - ID gid.GID `json:"id"` - Active bool `json:"active"` - 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"` - Permission bool `json:"permission"` + 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"` + Permission bool `json:"permission"` } func (TrustCenter) IsNode() {} diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index e6cc1ccbd..ede486b55 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -7577,6 +7577,40 @@ func (r *transferImpactAssessmentConnectionResolver) TotalCount(ctx context.Cont panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) } +// LogoFileURL is the resolver for the logoFileUrl field. +func (r *trustCenterResolver) LogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + logoURL, err := prb.TrustCenters.GenerateLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + // TODO no panic use gqlutils.InternalError + panic(fmt.Errorf("cannot generate logo url: %w", err)) + } + + return logoURL, nil +} + +// DarkLogoFileURL is the resolver for the darkLogoFileUrl field. +func (r *trustCenterResolver) DarkLogoFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrustCenterGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + logoURL, err := prb.TrustCenters.GenerateDarkLogoURL(ctx, obj.ID, 1*time.Hour) + if err != nil { + // TODO no panic use gqlutils.InternalError + panic(fmt.Errorf("cannot generate logo url: %w", err)) + } + + return logoURL, nil +} + // NdaFileURL is the resolver for the ndaFileUrl field. func (r *trustCenterResolver) NdaFileURL(ctx context.Context, obj *types.TrustCenter) (*string, error) { hasPermission, err := r.Resolver.Permission(ctx, obj, probo.ActionTrustCenterGetNda) diff --git a/pkg/trust/trust_center_service.go b/pkg/trust/trust_center_service.go index ab69b175d..1c5f3871a 100644 --- a/pkg/trust/trust_center_service.go +++ b/pkg/trust/trust_center_service.go @@ -17,11 +17,8 @@ package trust import ( "context" "fmt" - "net/url" "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/s3" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" @@ -120,25 +117,12 @@ func (s TrustCenterService) GenerateNDAFileURL( return "", err } - presignClient := s3.NewPresignClient(s.svc.s3) - - encodedFilename := url.QueryEscape(file.FileName) - contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", - encodedFilename, encodedFilename) - - presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ - Bucket: aws.String(s.svc.bucket), - Key: aws.String(file.FileKey), - ResponseCacheControl: aws.String("max-age=3600, public"), - ResponseContentDisposition: aws.String(contentDisposition), - }, func(opts *s3.PresignOptions) { - opts.Expires = expiresIn - }) + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) if err != nil { - return "", fmt.Errorf("cannot presign GetObject request: %w", err) + return "", fmt.Errorf("cannot generate file URL: %w", err) } - return presignedReq.URL, nil + return presignedURL, nil } func (s TrustCenterService) GenerateLogoURL( @@ -146,21 +130,45 @@ func (s TrustCenterService) GenerateLogoURL( compliancePageID gid.GID, expiresIn time.Duration, ) (*string, error) { - compliancePage, _, err := s.Get(ctx, compliancePageID) + file := &coredata.File{} + compliancePage := &coredata.TrustCenter{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := compliancePage.LoadByID(ctx, conn, s.svc.scope, compliancePageID); err != nil { + return fmt.Errorf("cannot load compliance page: %w", err) + } + + if compliancePage.LogoFileID == nil { + return nil + } + + if err := file.LoadByID(ctx, conn, s.svc.scope, *compliancePage.LogoFileID); err != nil { + return fmt.Errorf("cannot load file: %w", err) + } + + return nil + }, + ) if err != nil { - return nil, fmt.Errorf("cannot get compliance page: %w", err) + return nil, err } if compliancePage.LogoFileID == nil { return nil, nil } - url, err := s.generateFileURL(ctx, *compliancePage.LogoFileID, expiresIn) + if file.FileKey == "" { + return nil, nil + } + + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) if err != nil { return nil, fmt.Errorf("cannot generate file URL: %w", err) } - return url, nil + return &presignedURL, nil } func (s TrustCenterService) GenerateDarkLogoURL( @@ -168,51 +176,43 @@ func (s TrustCenterService) GenerateDarkLogoURL( compliancePageID gid.GID, expiresIn time.Duration, ) (*string, error) { - compliancePage, _, err := s.Get(ctx, compliancePageID) + file := &coredata.File{} + compliancePage := &coredata.TrustCenter{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := compliancePage.LoadByID(ctx, conn, s.svc.scope, compliancePageID); err != nil { + return fmt.Errorf("cannot load compliance page: %w", err) + } + + if compliancePage.DarkLogoFileID == nil { + return nil + } + + if err := file.LoadByID(ctx, conn, s.svc.scope, *compliancePage.DarkLogoFileID); err != nil { + return fmt.Errorf("cannot load file: %w", err) + } + + return nil + }, + ) if err != nil { - return nil, fmt.Errorf("cannot get compliance page: %w", err) + return nil, err } - if compliancePage.DarkLogoFileID == nil { + if compliancePage.LogoFileID == nil { return nil, nil } - url, err := s.generateFileURL(ctx, *compliancePage.DarkLogoFileID, expiresIn) + if file.FileKey == "" { + return nil, nil + } + + presignedURL, err := s.svc.fileManager.GenerateFileUrl(ctx, file, expiresIn) if err != nil { return nil, fmt.Errorf("cannot generate file URL: %w", err) } - return url, nil -} - -func (s TrustCenterService) generateFileURL(ctx context.Context, fileID gid.GID, expiresIn time.Duration) (*string, error) { - file := &coredata.File{} - if err := s.svc.pg.WithConn( - ctx, - func(conn pg.Conn) error { - return file.LoadByID(ctx, conn, s.svc.scope, fileID) - }, - ); err != nil { - return nil, fmt.Errorf("cannot load file: %w", err) - } - - presignClient := s3.NewPresignClient(s.svc.s3) - - encodedFilename := url.QueryEscape(file.FileName) - contentDisposition := fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", - encodedFilename, encodedFilename) - - presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{ - Bucket: aws.String(s.svc.bucket), - Key: aws.String(file.FileKey), - ResponseCacheControl: aws.String("max-age=3600, public"), - ResponseContentDisposition: aws.String(contentDisposition), - }, func(opts *s3.PresignOptions) { - opts.Expires = expiresIn - }) - if err != nil { - return nil, fmt.Errorf("cannot presign GetObject request: %w", err) - } - - return &presignedReq.URL, nil + return &presignedURL, nil }