diff --git a/packages/builder/src/pages/builder/portal/settings/branding.svelte b/packages/builder/src/pages/builder/portal/settings/branding.svelte index ae22d310a1..ec2159d62b 100644 --- a/packages/builder/src/pages/builder/portal/settings/branding.svelte +++ b/packages/builder/src/pages/builder/portal/settings/branding.svelte @@ -44,14 +44,14 @@ let config = {} let updated = false - $: onConfigUpdate(config, mounted) - $: init = Object.keys(config).length > 0 + $: onConfigUpdate(config) + $: initialised = Object.keys(config).length > 0 $: isCloud = $admin.cloud $: brandingEnabled = $licensing.brandingEnabled const onConfigUpdate = () => { - if (!mounted || updated || !init) { + if (!mounted || updated || !initialised) { return } updated = true @@ -122,34 +122,27 @@ return response } - async function saveConfig() { - saving = true - + async function saveFiles() { if (logoFile) { const logoResp = await uploadLogo(logoFile) if (logoResp.url) { - config = { - ...config, - logoUrl: logoResp.url, - } logoFile = null logoPreview = null } + config.logoUrl = undefined } if (faviconFile) { const faviconResp = await uploadFavicon(faviconFile) if (faviconResp.url) { - config = { - ...config, - faviconUrl: faviconResp.url, - } faviconFile = null faviconPreview = null } + config.faviconUrl = undefined } + } - // Trim + function trimFields() { const userStrings = [ "metaTitle", "platformTitle", @@ -168,11 +161,18 @@ ...config, ...trimmed, } + } + + async function saveConfig() { + saving = true + + await saveFiles() + trimFields() try { // Update settings await organisation.save(config) - await organisation.init() + await init() notifications.success("Branding settings updated") } catch (e) { console.error("Branding updated failed", e) @@ -182,9 +182,10 @@ saving = false } - onMount(async () => { - await organisation.init() - + async function init() { + if (!$organisation.loaded) { + await organisation.init() + } config = { faviconUrl: $organisation.faviconUrl, logoUrl: $organisation.logoUrl, @@ -197,6 +198,10 @@ metaImageUrl: $organisation.metaImageUrl, metaTitle: $organisation.metaTitle, } + } + + onMount(async () => { + await init() mounted = true }) @@ -262,6 +267,7 @@ faviconFile = e.detail faviconPreview = null } else { + faviconFile = null clone.faviconUrl = "" } config = clone @@ -408,7 +414,11 @@ Upgrade {/if} - diff --git a/packages/builder/src/stores/portal/organisation.js b/packages/builder/src/stores/portal/organisation.js index ed7dd36636..ab33c7a5fa 100644 --- a/packages/builder/src/stores/portal/organisation.js +++ b/packages/builder/src/stores/portal/organisation.js @@ -23,6 +23,7 @@ const DEFAULT_CONFIG = { oidcCallbackUrl: "", googleCallbackUrl: "", isSSOEnforced: false, + loaded: false, } export function createOrganisationStore() { @@ -32,7 +33,7 @@ export function createOrganisationStore() { async function init() { const tenantId = get(auth).tenantId const settingsConfigDoc = await API.getTenantConfig(tenantId) - set({ ...DEFAULT_CONFIG, ...settingsConfigDoc.config }) + set({ ...DEFAULT_CONFIG, ...settingsConfigDoc.config, loaded: true }) } async function save(config) { @@ -43,6 +44,10 @@ export function createOrganisationStore() { delete storeConfig.googleDatasourceConfigured delete storeConfig.oidcCallbackUrl delete storeConfig.googleCallbackUrl + + // delete internal store field + delete storeConfig.loaded + await API.saveConfig({ type: "settings", config: { ...storeConfig, ...config }, diff --git a/packages/worker/src/api/controllers/global/configs.ts b/packages/worker/src/api/controllers/global/configs.ts index afbb7c931d..0f22d4aae0 100644 --- a/packages/worker/src/api/controllers/global/configs.ts +++ b/packages/worker/src/api/controllers/global/configs.ts @@ -23,6 +23,7 @@ import { isSettingsConfig, isSMTPConfig, OIDCConfigs, + SettingsBrandingConfig, SettingsInnerConfig, SSOConfig, SSOConfigType, @@ -142,13 +143,29 @@ async function hasActivatedConfig(ssoConfigs?: SSOConfigs) { return !!Object.values(ssoConfigs).find(c => c?.activated) } -async function verifySettingsConfig(config: SettingsInnerConfig) { +async function verifySettingsConfig( + config: SettingsInnerConfig & SettingsBrandingConfig, + existingConfig?: SettingsInnerConfig & SettingsBrandingConfig +) { if (config.isSSOEnforced) { const valid = await hasActivatedConfig() if (!valid) { throw new Error("Cannot enforce SSO without an activated configuration") } } + + // always preserve file attributes + // these should be set via upload instead + // only allow for deletion by checking empty string to bypass this behaviour + + if (existingConfig && config.logoUrl !== "") { + config.logoUrl = existingConfig.logoUrl + config.logoUrlEtag = existingConfig.logoUrlEtag + } + if (existingConfig && config.faviconUrl !== "") { + config.faviconUrl = existingConfig.faviconUrl + config.faviconUrlEtag = existingConfig.faviconUrlEtag + } } async function verifySSOConfig(type: SSOConfigType, config: SSOConfig) { @@ -198,7 +215,7 @@ export async function save(ctx: UserCtx) { await email.verifyConfig(config) break case ConfigType.SETTINGS: - await verifySettingsConfig(config) + await verifySettingsConfig(config, existingConfig?.config) break case ConfigType.GOOGLE: await verifyGoogleConfig(config) @@ -320,14 +337,15 @@ export async function publicSettings( ) } - if (branding.faviconUrl && branding.faviconUrl !== "") { - // @ts-ignore - config.faviconUrl = objectStore.getGlobalFileUrl( - "settings", - "faviconUrl", - branding.faviconUrl - ) - } + // enrich the favicon url - empty url means deleted + const faviconUrl = + branding.faviconUrl && branding.faviconUrl !== "" + ? objectStore.getGlobalFileUrl( + "settings", + "faviconUrl", + branding.faviconUrlEtag + ) + : undefined // google const googleConfig = await configs.getGoogleConfig() @@ -352,6 +370,7 @@ export async function publicSettings( config: { ...config, ...branding, + ...{ faviconUrl }, google, googleDatasourceConfigured, oidc,