From b1021affead79429cbfb625949bb56faa3cedef3 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Thu, 30 Jul 2026 17:43:56 +0200 Subject: [PATCH] Implement delete organization horizontal logo Wire the Connect mutation through IAM so owners and admins can clear an organization horizontal logo. Soft-delete the underlying public file so existing download URLs stop serving the image, and expose the operation in the n8n organization node. Signed-off-by: Sacha Al Himdani --- .../deleteHorizontalLogo.operation.ts | 72 +++++++++++++++++++ .../nodes/Probo/actions/organization/index.ts | 17 ++++- pkg/iam/organization_service.go | 39 ++++++++++ .../api/connect/v1/organization_resolvers.go | 15 +++- 4 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 packages/n8n-node/nodes/Probo/actions/organization/deleteHorizontalLogo.operation.ts diff --git a/packages/n8n-node/nodes/Probo/actions/organization/deleteHorizontalLogo.operation.ts b/packages/n8n-node/nodes/Probo/actions/organization/deleteHorizontalLogo.operation.ts new file mode 100644 index 000000000..30d31fcfc --- /dev/null +++ b/packages/n8n-node/nodes/Probo/actions/organization/deleteHorizontalLogo.operation.ts @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import type { INodeProperties, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { proboConnectApiRequest } from '../../GenericFunctions'; + +export const description: INodeProperties[] = [ + { + displayName: 'Organization ID', + name: 'organizationId', + type: 'string', + displayOptions: { + show: { + resource: ['organization'], + operation: ['deleteHorizontalLogo'], + }, + }, + default: '', + description: 'The ID of the organization whose horizontal logo to delete', + required: true, + }, +]; + +export async function execute( + this: IExecuteFunctions, + itemIndex: number, +): Promise { + const organizationId = this.getNodeParameter('organizationId', itemIndex) as string; + + const query = ` + mutation DeleteOrganizationHorizontalLogo($input: DeleteOrganizationHorizontalLogoInput!) { + deleteOrganizationHorizontalLogo(input: $input) { + organization { + id + name + horizontalLogo { + id + fileName + downloadUrl + } + updatedAt + } + } + } + `; + + const responseData = await proboConnectApiRequest.call(this, query, { + input: { organizationId }, + }); + + return { + json: responseData, + pairedItem: { item: itemIndex }, + }; +} diff --git a/packages/n8n-node/nodes/Probo/actions/organization/index.ts b/packages/n8n-node/nodes/Probo/actions/organization/index.ts index 4afa318a3..e20b1b0d6 100644 --- a/packages/n8n-node/nodes/Probo/actions/organization/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/organization/index.ts @@ -22,6 +22,7 @@ import type { INodeProperties } from 'n8n-workflow'; import * as createOp from './create.operation'; import * as updateOp from './update.operation'; import * as deleteOp from './delete.operation'; +import * as deleteHorizontalLogoOp from './deleteHorizontalLogo.operation'; import * as getOp from './get.operation'; import * as getAllOp from './getAll.operation'; @@ -49,6 +50,12 @@ export const description: INodeProperties[] = [ description: 'Delete an organization', action: 'Delete an organization', }, + { + name: 'Delete Horizontal Logo', + value: 'deleteHorizontalLogo', + description: 'Delete an organization horizontal logo', + action: 'Delete an organization horizontal logo', + }, { name: 'Get', value: 'get', @@ -73,8 +80,16 @@ export const description: INodeProperties[] = [ ...createOp.description, ...updateOp.description, ...deleteOp.description, + ...deleteHorizontalLogoOp.description, ...getOp.description, ...getAllOp.description, ]; -export { createOp as create, updateOp as update, deleteOp as delete, getOp as get, getAllOp as getAll }; +export { + createOp as create, + updateOp as update, + deleteOp as delete, + deleteHorizontalLogoOp as deleteHorizontalLogo, + getOp as get, + getAllOp as getAll, +}; diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index b9af97550..e10e24817 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -1438,6 +1438,45 @@ func (s OrganizationService) HorizontalLogoFile( return file, nil } +func (s OrganizationService) DeleteHorizontalLogo( + ctx context.Context, + organizationID gid.GID, +) (*coredata.Organization, error) { + scope := coredata.NewScopeFromObjectID(organizationID) + organization := &coredata.Organization{} + + err := s.pg.WithTx( + ctx, + func(ctx context.Context, tx pg.Tx) error { + if err := organization.LoadByID(ctx, tx, scope, organizationID); err != nil { + return fmt.Errorf("cannot load organization: %w", err) + } + + if organization.HorizontalLogoFileID != nil { + file := coredata.File{ID: *organization.HorizontalLogoFileID} + + if err := file.SoftDelete(ctx, tx, scope); err != nil { + return fmt.Errorf("cannot soft-delete horizontal logo file: %w", err) + } + } + + organization.HorizontalLogoFileID = nil + organization.UpdatedAt = time.Now() + + if err := organization.Update(ctx, scope, tx); err != nil { + return fmt.Errorf("cannot update organization: %w", err) + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return organization, nil +} + func (s OrganizationService) DeleteSAMLConfiguration( ctx context.Context, organizationID gid.GID, diff --git a/pkg/server/api/connect/v1/organization_resolvers.go b/pkg/server/api/connect/v1/organization_resolvers.go index 61bbdc08d..77ae01751 100644 --- a/pkg/server/api/connect/v1/organization_resolvers.go +++ b/pkg/server/api/connect/v1/organization_resolvers.go @@ -8,7 +8,6 @@ package connect_v1 import ( "context" "errors" - "fmt" "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/coredata" @@ -145,7 +144,19 @@ func (r *mutationResolver) DeleteOrganization(ctx context.Context, input types.D // DeleteOrganizationHorizontalLogo is the resolver for the deleteOrganizationHorizontalLogo field. func (r *mutationResolver) DeleteOrganizationHorizontalLogo(ctx context.Context, input types.DeleteOrganizationHorizontalLogoInput) (*types.DeleteOrganizationHorizontalLogoPayload, error) { - panic(fmt.Errorf("not implemented: DeleteOrganizationHorizontalLogo - deleteOrganizationHorizontalLogo")) + if _, err := r.authorize(ctx, input.OrganizationID, iam.ActionOrganizationUpdate); err != nil { + return nil, err + } + + organization, err := r.iam.OrganizationService.DeleteHorizontalLogo(ctx, input.OrganizationID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete organization horizontal logo", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteOrganizationHorizontalLogoPayload{ + Organization: types.NewOrganization(organization), + }, nil } // Logo is the resolver for the logo field.