Migrate Connect org logos to File type

Replace Organization.logoUrl and horizontalLogoUrl with nested File
objects whose downloadUrl points at /api/files/v1/public/{id}, matching
the Console migration.

Org logos are FileVisibilityPublic and served without HTTP auth, so
Connect File.downloadUrl is built eagerly in NewFile with no field-level
authorize. Logo loading moves to iam.OrganizationService.LogoFile and
HorizontalLogoFile; the old URL generators are removed.

Sync IAM Relay components and n8n organization operations. Add an e2e
test for Connect multipart logo upload and ExecuteConnectWithFile.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-10 16:19:45 +02:00
parent c7e2fc209f
commit e06f3e0520
17 changed files with 315 additions and 84 deletions

View File

@@ -46,7 +46,9 @@ const organizationFragment = graphql`
fragment MembershipCard_organizationFragment on Organization {
id
name
logoUrl
logo {
downloadUrl
}
}
`;
@@ -101,7 +103,7 @@ export function MembershipCard(props: MembershipCardProps) {
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 hover:text-primary flex-1">
<Avatar
src={organization.logoUrl}
src={organization.logo?.downloadUrl}
name={organization.name}
size="l"
/>

View File

@@ -41,7 +41,9 @@ const organizationFragment = graphql`
fragment MembershipsDropdownMenuItem_organizationFragment on Organization {
id
name
logoUrl
logo {
downloadUrl
}
}
`;
@@ -63,7 +65,7 @@ export function MembershipsDropdownMenuItem(props: {
return (
<DropdownItem key={id} asChild>
<Link to={`/organizations/${organization.id}`}>
<Avatar name={organization.name} src={organization.logoUrl} />
<Avatar name={organization.name} src={organization.logo?.downloadUrl} />
<span className="flex-1">{organization.name}</span>
{isAssuming && (
<IconCheckmark1 size={16} className="text-green-600" />

View File

@@ -41,8 +41,12 @@ const fragment = graphql`
fragment OrganizationFormFragment on Organization {
id
name @required(action: THROW)
logoUrl
horizontalLogoUrl
logo {
downloadUrl
}
horizontalLogo {
downloadUrl
}
description
websiteUrl
email
@@ -57,8 +61,12 @@ const updateOrganizationMutation = graphql`
organization {
id
name
logoUrl
horizontalLogoUrl
logo {
downloadUrl
}
horizontalLogo {
downloadUrl
}
description
websiteUrl
email
@@ -75,7 +83,9 @@ const deleteHorizontalLogoMutation = graphql`
deleteOrganizationHorizontalLogo(input: $input) {
organization {
id
horizontalLogoUrl
horizontalLogo {
downloadUrl
}
}
}
}
@@ -225,8 +235,8 @@ export function OrganizationForm(props: {
<Label>{__("Organization logo")}</Label>
<div className="flex w-max items-center gap-4">
<Avatar
className={logoPreview || organization.logoUrl ? "bg-transparent" : undefined}
src={logoPreview || organization.logoUrl}
className={logoPreview || organization.logo?.downloadUrl ? "bg-transparent" : undefined}
src={logoPreview || organization.logo?.downloadUrl}
name={organization.name}
size="xl"
/>
@@ -253,12 +263,12 @@ export function OrganizationForm(props: {
)}
</p>
<div className="flex items-center gap-4">
{(horizontalLogoPreview || organization.horizontalLogoUrl) && (
{(horizontalLogoPreview || organization.horizontalLogo?.downloadUrl) && (
<div className="border border-border-solid rounded-md p-4 bg-surface-secondary">
<img
src={
horizontalLogoPreview
|| organization.horizontalLogoUrl
|| organization.horizontalLogo?.downloadUrl
|| undefined
}
alt={__("Horizontal logo")}
@@ -275,12 +285,12 @@ export function OrganizationForm(props: {
>
{isUpdatingOrganization
? __("Uploading...")
: horizontalLogoPreview || organization.horizontalLogoUrl
: horizontalLogoPreview || organization.horizontalLogo?.downloadUrl
? __("Change horizontal logo")
: __("Upload horizontal logo")}
</FileButton>
)}
{canUpdate && organization.horizontalLogoUrl && (
{canUpdate && organization.horizontalLogo?.downloadUrl && (
<Dialog
ref={deleteDialogRef}
trigger={(