UNPKG

@strapi/data-transfer

Version:

Data transfer capabilities for Strapi

175 lines (171 loc) 6.49 kB
'use strict'; var path = require('path'); var stream = require('stream'); var fp = require('lodash/fp'); var fs = require('fs-extra'); var mimeTypes = require('mime-types'); const PROJECT_SETTINGS_CORE_STORE_KEY = 'core_admin_project-settings'; // The shape persisted back to core-store, matching what the admin project-settings // service stores (so the admin UI keeps behaving identically after a transfer). const LOGO_PERSISTED_FIELDS = [ 'name', 'hash', 'url', 'width', 'height', 'ext', 'size', 'provider' ]; const isProjectSettingsRow = (row)=>row.key === PROJECT_SETTINGS_CORE_STORE_KEY; const getConfiguredUploadProvider = (strapi)=>strapi.config.get('plugin::upload')?.provider; // Admin logos with no provider were uploaded with the local provider (the default). const isLocalProviderLogo = (logo)=>!logo.provider || logo.provider === 'local'; // Best-effort mime type for a logo so that remote providers (S3, Cloudinary, …) // store the correct Content-Type and the image renders instead of downloading. // The admin does not persist the mime type, so it is reconstructed from the // file extension (falling back to the file name). const getLogoMimeType = (logo)=>{ const fromExt = logo.ext ? mimeTypes.lookup(logo.ext) : false; if (fromExt) { return fromExt; } const fromName = logo.name ? mimeTypes.lookup(logo.name) : false; if (fromName) { return fromName; } return undefined; }; /** * Resolve the URL to read a non-local logo from during export. * * Mirrors the media-library asset export: when the file lives on the * currently-configured provider and that provider serves private files, * a signed URL is generated so the bytes can be fetched. */ const getRemoteLogoUrl = async (strapi, logo)=>{ const { provider } = strapi.plugins.upload; const providerName = getConfiguredUploadProvider(strapi); if (logo.provider === providerName && typeof provider?.isPrivate === 'function' && typeof provider?.getSignedUrl === 'function' && await provider.isPrivate()) { const { url } = await provider.getSignedUrl(logo); return url; } return logo.url; }; const logoLabel = (logo)=>logo.name ?? logo.hash ?? 'unknown'; const readLocalLogoBuffer = async (strapi, logo)=>{ const filepath = path.join(strapi.dirs.static.public, logo.url); try { const buffer = await fs.readFile(filepath); return buffer.toString('base64'); } catch (error) { const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined; if (code === 'ENOENT') { strapi.log.warn(`[Data transfer] Admin logo "${logoLabel(logo)}" exists in project settings but no corresponding file was found to transfer. Path: ${filepath}`); return undefined; } throw error; } }; const readRemoteLogoBuffer = async (strapi, logo)=>{ const url = await getRemoteLogoUrl(strapi, logo); const response = await strapi.fetch(url); if (response.status !== 200 || !response.body) { strapi.log.warn(`[Data transfer] Admin logo "${logoLabel(logo)}" exists in project settings but could not be fetched for transfer. URL: ${url} (status: ${response.status})`); return undefined; } const chunks = []; for await (const chunk of stream.Readable.fromWeb(response.body)){ chunks.push(chunk); } return Buffer.concat(chunks).toString('base64'); }; const readLogoTransferBuffer = async (strapi, logo)=>{ if (!logo.url) { return undefined; } if (isLocalProviderLogo(logo)) { return readLocalLogoBuffer(strapi, logo); } return readRemoteLogoBuffer(strapi, logo); }; const enrichLogoForExport = async (strapi, logo)=>{ if (!logo?.url) { return logo; } const transferBuffer = await readLogoTransferBuffer(strapi, logo); if (!transferBuffer) { return logo; } return { ...logo, __transferBuffer: transferBuffer }; }; const enrichProjectSettingsForExport = async (strapi, row)=>{ if (!isProjectSettingsRow(row)) { return row; } const settings = row.value; const [menuLogo, authLogo] = await Promise.all([ enrichLogoForExport(strapi, settings.menuLogo), enrichLogoForExport(strapi, settings.authLogo) ]); return { ...row, value: { ...settings, menuLogo, authLogo } }; }; /** * Re-upload a transferred logo to the destination's configured upload provider. * * The file is always re-uploaded through the destination provider, so the * persisted `url` and `provider` reflect the destination (matching how the * media-library asset restore behaves). A best-effort mime type is supplied so * remote providers serve the logo with the correct Content-Type. */ const uploadLogoFromTransferBuffer = async (strapi, logo)=>{ if (!logo.__transferBuffer) { return logo; } const { __transferBuffer: transferBuffer, ...logoWithoutBuffer } = logo; const provider = getConfiguredUploadProvider(strapi); const mime = getLogoMimeType(logoWithoutBuffer); const file = { ...logoWithoutBuffer, ...mime ? { mime } : {}, stream: stream.Readable.from(Buffer.from(transferBuffer, 'base64')), provider }; await strapi.plugin('upload').provider.uploadStream(file); return fp.pick(LOGO_PERSISTED_FIELDS, file); }; const restoreProjectSettingsLogos = async (strapi, settings)=>{ const [menuLogo, authLogo] = await Promise.all([ settings.menuLogo ? uploadLogoFromTransferBuffer(strapi, settings.menuLogo) : settings.menuLogo, settings.authLogo ? uploadLogoFromTransferBuffer(strapi, settings.authLogo) : settings.authLogo ]); return { ...settings, menuLogo, authLogo }; }; const restoreProjectSettingsRow = async (strapi, row)=>{ if (!isProjectSettingsRow(row)) { return row; } return { ...row, value: await restoreProjectSettingsLogos(strapi, row.value) }; }; exports.PROJECT_SETTINGS_CORE_STORE_KEY = PROJECT_SETTINGS_CORE_STORE_KEY; exports.enrichProjectSettingsForExport = enrichProjectSettingsForExport; exports.restoreProjectSettingsLogos = restoreProjectSettingsLogos; exports.restoreProjectSettingsRow = restoreProjectSettingsRow; //# sourceMappingURL=project-settings-logos.js.map