Adopt File type for trust logos and MCP

Trust GraphQL and MCP still exposed presigned URL strings for
trust-center logos while console and connect already serve stable
File.downloadUrl paths. Phase 1 migrates the seven public logo
fields on trust GraphQL and the trust-center file references on MCP
to the shared File type; trust GraphQL NDA stays on fileUrl for a
follow-up.

Trust resolvers load public files through filemanager and map them
with types.NewFile. The trust app Relay queries and components now
read logo.downloadUrl. MCP specification, resolvers, and helpers
are updated in sync, including NDA on MCP where callers already
have file access.

filemanager is split into focused files and its URL surface is
narrowed to GenerateFileURL(file) for stable app URLs and
GeneratePresignedURL for S3 redirects. GetPublicFile remains the
DB entry point when only a file ID is known.

Add trust and MCP e2e coverage for public logo download URLs.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-11 13:54:59 +02:00
parent e06f3e0520
commit eccef41767
60 changed files with 1004 additions and 400 deletions

View File

@@ -66,8 +66,12 @@ const auditRowFragment = graphql`
framework {
id
name
lightLogoURL
darkLogoURL
lightLogo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
}
`;
@@ -176,8 +180,8 @@ export function AuditRowAvatar(props: { audit: AuditRowFragment$key }) {
<div className="flex flex-col gap-2 items-center w-19">
<FrameworkLogo
className="size-19"
lightLogoURL={audit.framework.lightLogoURL}
darkLogoURL={audit.framework.darkLogoURL}
lightLogoURL={audit.framework.lightLogo?.downloadUrl}
darkLogoURL={audit.framework.darkLogo?.downloadUrl}
name={audit.framework.name}
/>
<div className="txt-primary text-sm max-w-19 overflow-hidden min-w-0 whitespace-nowrap text-ellipsis">
@@ -215,8 +219,8 @@ function AuditDialog(
<DialogContent className="p-4 lg:p-8 space-y-6">
<FrameworkLogo
className="size-24 mx-auto"
lightLogoURL={audit.framework.lightLogoURL}
darkLogoURL={audit.framework.darkLogoURL}
lightLogoURL={audit.framework.lightLogo?.downloadUrl}
darkLogoURL={audit.framework.darkLogo?.downloadUrl}
name={audit.framework.name}
/>
<h2 className="text-xl font-semibold mb-1">{audit.framework.name}</h2>

View File

@@ -23,8 +23,12 @@ const frameworkFragment = graphql`
# eslint-disable-next-line relay/unused-fields
id
name
lightLogoURL
darkLogoURL
lightLogo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
`;
@@ -35,8 +39,8 @@ export function FrameworkBadge(props: { framework: FrameworkBadgeFragment$key })
<div className="flex flex-col gap-2 items-center w-19">
<FrameworkLogo
className="size-19"
lightLogoURL={framework.lightLogoURL}
darkLogoURL={framework.darkLogoURL}
lightLogoURL={framework.lightLogo?.downloadUrl}
darkLogoURL={framework.darkLogo?.downloadUrl}
name={framework.name}
/>
<div className="txt-primary text-xs max-w-19 min-w-0 text-center">

View File

@@ -85,7 +85,9 @@ export function OrganizationSidebar({
const navigate = useNavigate();
const location = useLocation();
const logoFileUrl = theme === "dark" ? (trustCenter?.darkLogoFileUrl ?? trustCenter?.logoFileUrl) : trustCenter?.logoFileUrl;
const logoFileUrl = theme === "dark"
? (trustCenter?.darkLogo?.downloadUrl ?? trustCenter?.logo?.downloadUrl)
: trustCenter?.logo?.downloadUrl;
const [requestAllAccesses, isRequestingAccess]
= useMutation<OrganizationSidebar_requestAllAccessesMutation>(

View File

@@ -38,8 +38,8 @@ export function MainLayout(props: Props) {
useFavicon(
theme === "dark"
? (trustCenter?.darkLogoFileUrl ?? trustCenter?.logoFileUrl)
: trustCenter?.logoFileUrl,
? (trustCenter?.darkLogo?.downloadUrl ?? trustCenter?.logo?.downloadUrl)
: trustCenter?.logo?.downloadUrl,
);
useRequestAccessCallback();

View File

@@ -47,8 +47,12 @@ import type { DocumentPageRequestTrustCenterFileAccessMutation } from "./__gener
export const documentPageQuery = graphql`
query DocumentPageQuery($id: ID!) {
currentTrustCenter {
logoFileUrl
darkLogoFileUrl
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
node(id: $id) @required(action: THROW) {
__typename
@@ -230,8 +234,8 @@ export function DocumentPage({ queryRef }: Props) {
const nodeId = getNodeId(node);
const logoFileUrl = theme === "dark"
? (trustCenter?.darkLogoFileUrl ?? trustCenter?.logoFileUrl)
: trustCenter?.logoFileUrl;
? (trustCenter?.darkLogo?.downloadUrl ?? trustCenter?.logo?.downloadUrl)
: trustCenter?.logo?.downloadUrl;
const [exportDocument, isExportingDocument]
= useMutation<DocumentPageExportDocumentMutation>(exportDocumentMutation);

View File

@@ -46,7 +46,9 @@ const overviewFragment = graphql`
node {
id
name
logoUrl
logo {
downloadUrl
}
websiteUrl
}
}
@@ -223,7 +225,7 @@ function Subprocessors({
type Reference = {
name: string;
logoUrl: string;
logo: { downloadUrl: string } | null;
websiteUrl: string;
id: string;
};
@@ -248,7 +250,7 @@ function References({ references }: { references: Reference[] }) {
className="flex flex-col justify-center items-center gap-2"
>
<img
src={reference.logoUrl}
src={reference.logo?.downloadUrl}
alt={reference.name}
className="rounded-2xl size-12 block"
/>

View File

@@ -23,8 +23,12 @@ import type { AuthLayoutQuery } from "./__generated__/AuthLayoutQuery.graphql";
export const authLayoutQuery = graphql`
query AuthLayoutQuery {
currentTrustCenter @required(action: THROW) {
logoFileUrl
darkLogoFileUrl
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
}
`;
@@ -36,8 +40,8 @@ export function AuthLayout(props: { queryRef: PreloadedQuery<AuthLayoutQuery> })
const theme = useSystemTheme();
const logoFileUrl = theme === "dark"
? compliancePage.darkLogoFileUrl ?? compliancePage.logoFileUrl
: compliancePage.logoFileUrl;
? compliancePage.darkLogo?.downloadUrl ?? compliancePage.logo?.downloadUrl
: compliancePage.logo?.downloadUrl;
return (
<div className="min-h-screen text-txt-primary bg-level-0 flex flex-col items-center justify-center">

View File

@@ -31,8 +31,12 @@ export const currentTrustGraphQuery = graphql`
createdAt
updatedAt
}
logoFileUrl
darkLogoFileUrl
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
nonDisclosureAgreement {
fileName
fileUrl