UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 16.2 kB
{"version":3,"file":"update.mjs","names":["mediaServices.checks.checkHasMediaStrategy","mediaServices.checks.checkCanUpdateMedia"],"sources":["../../../../src/services/media/strategies/update.ts"],"sourcesContent":["import { copy } from \"../../../libs/i18n/index.js\";\nimport type { ErrorCopy, LucidErrorData } from \"../../../types/errors.js\";\nimport type { MediaType } from \"../../../types/response.js\";\nimport { formatBytes } from \"../../../utils/helpers/index.js\";\nimport {\n\tgetMediaKeyTenantKey,\n\tresolveMediaKeyTenant,\n} from \"../../../utils/media/index.js\";\nimport type { ServiceFn } from \"../../../utils/services/types.js\";\nimport { mediaServices } from \"../../index.js\";\nimport adjustStorageUsage from \"../adjust-storage-usage.js\";\nimport validateUploadedMedia from \"../helpers/validate-uploaded-media.js\";\n\nconst update: ServiceFn<\n\t[\n\t\t{\n\t\t\tfileName: string;\n\t\t\tpreviousEtag?: string | null;\n\t\t\tpreviousSize: number;\n\t\t\tpreviousKey: string;\n\t\t\ttenantKey: string | null;\n\t\t\tpreviousType: MediaType;\n\t\t\tupdatedKey: string;\n\t\t\ttargetKey: string;\n\t\t\tallowedType?: MediaType;\n\t\t},\n\t],\n\t{\n\t\tmimeType: string;\n\t\ttype: MediaType;\n\t\textension: string;\n\t\tsize: number;\n\t\tkey: string;\n\t\tetag: string | null;\n\t\tsourceDeleted: boolean;\n\t}\n> = async (context, data) => {\n\tconst mediaStrategyRes =\n\t\tawait mediaServices.checks.checkHasMediaStrategy(context);\n\tif (mediaStrategyRes.error) return mediaStrategyRes;\n\n\tconst updatedTenant = resolveMediaKeyTenant(context.config, data.updatedKey);\n\tconst targetTenant = resolveMediaKeyTenant(context.config, data.targetKey);\n\tconst previousTenant = resolveMediaKeyTenant(\n\t\tcontext.config,\n\t\tdata.previousKey,\n\t);\n\n\tconst cleanupUpdatedKey = async () => {\n\t\tawait mediaStrategyRes.data.delete(context, {\n\t\t\tkey: data.updatedKey,\n\t\t\ttenant: updatedTenant,\n\t\t});\n\t};\n\n\tconst revertStorageDelta = async (delta: number) => {\n\t\tif (delta === 0) return;\n\n\t\tawait adjustStorageUsage(context, {\n\t\t\ttenantKey: data.tenantKey,\n\t\t\tdelta: delta * -1,\n\t\t\tmin: 0,\n\t\t});\n\t};\n\n\t// Fetch meta data from new file\n\tconst mediaMetaRes = await mediaStrategyRes.data.getMeta(context, {\n\t\tkey: data.updatedKey,\n\t\ttenant: updatedTenant,\n\t});\n\tif (mediaMetaRes.error) return mediaMetaRes;\n\n\t// Ensure we available storage space\n\tconst proposedSizeRes = await mediaServices.checks.checkCanUpdateMedia(\n\t\tcontext,\n\t\t{\n\t\t\tsize: mediaMetaRes.data.size,\n\t\t},\n\t);\n\tif (proposedSizeRes.error) {\n\t\tawait cleanupUpdatedKey();\n\t\treturn proposedSizeRes;\n\t}\n\n\tconst fileMetaData = await validateUploadedMedia({\n\t\tcontext,\n\t\tstream: mediaStrategyRes.data.stream,\n\t\ttenant: updatedTenant,\n\t\tkey: data.updatedKey,\n\t\tfileName: data.fileName,\n\t\tmimeType: mediaMetaRes.data.mimeType,\n\t\tallowedType: data.allowedType,\n\t\texpectedType: data.previousType,\n\t});\n\tif (fileMetaData.error) {\n\t\tawait cleanupUpdatedKey();\n\t\treturn fileMetaData;\n\t}\n\n\tconst delta = mediaMetaRes.data.size - data.previousSize;\n\tconst storageLimit = context.config.media.limits.storageBytes;\n\tconst storageRes = await adjustStorageUsage(context, {\n\t\ttenantKey: data.tenantKey,\n\t\tdelta: delta,\n\t\tmax: storageLimit === false ? undefined : storageLimit,\n\t\tmin: 0,\n\t});\n\tif (storageRes.error) {\n\t\tawait cleanupUpdatedKey();\n\t\treturn storageRes;\n\t}\n\tif (!storageRes.data.applied) {\n\t\tawait cleanupUpdatedKey();\n\n\t\tif (storageLimit === false) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\ttype: \"basic\",\n\t\t\t\t\tstatus: 500,\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\"server:core.files.validation.storage.limit.exceeded\", {\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tsize: formatBytes(storageLimit),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tstatus: 500,\n\t\t\t\terrors: {\n\t\t\t\t\tfile: {\n\t\t\t\t\t\tcode: \"storage\",\n\t\t\t\t\t\tmessage: copy(\n\t\t\t\t\t\t\t\"server:core.files.validation.storage.limit.exceeded\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\t\t\tsize: formatBytes(storageLimit),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst getVerifiedTargetMeta = async (options?: {\n\t\trequireChangedEtag?: boolean;\n\t}) => {\n\t\tconst targetMetaRes = await mediaStrategyRes.data.getMeta(context, {\n\t\t\tkey: data.targetKey,\n\t\t\ttenant: targetTenant,\n\t\t});\n\t\tif (targetMetaRes.error) return null;\n\n\t\tconst matchesTarget =\n\t\t\ttargetMetaRes.data.size === mediaMetaRes.data.size &&\n\t\t\ttargetMetaRes.data.mimeType === fileMetaData.data.mimeType;\n\t\tif (!matchesTarget) return null;\n\n\t\tif (\n\t\t\toptions?.requireChangedEtag &&\n\t\t\tdata.previousEtag &&\n\t\t\ttargetMetaRes.data.etag === data.previousEtag &&\n\t\t\ttargetMetaRes.data.etag !== mediaMetaRes.data.etag\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn targetMetaRes.data;\n\t};\n\n\tconst failUpdate = async (\n\t\tmessage: ErrorCopy,\n\t): Promise<{ error: LucidErrorData; data: undefined }> => {\n\t\tawait revertStorageDelta(delta);\n\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage,\n\t\t\t\tstatus: 500,\n\t\t\t\terrors: {\n\t\t\t\t\tfile: {\n\t\t\t\t\t\tcode: \"media_error\",\n\t\t\t\t\t\tmessage,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t};\n\n\tconst uploadChangesTenantScope =\n\t\tgetMediaKeyTenantKey(data.updatedKey) !==\n\t\tgetMediaKeyTenantKey(data.targetKey);\n\n\tif (data.targetKey === data.previousKey || uploadChangesTenantScope) {\n\t\tconst updatedStreamRes = await mediaStrategyRes.data.stream(context, {\n\t\t\tkey: data.updatedKey,\n\t\t\ttenant: updatedTenant,\n\t\t});\n\t\tif (updatedStreamRes.error) {\n\t\t\tawait cleanupUpdatedKey();\n\t\t\treturn await failUpdate(\n\t\t\t\tupdatedStreamRes.error.message ?? copy(\"server:core.errors.unknown\"),\n\t\t\t);\n\t\t}\n\n\t\tconst uploadRes = await mediaStrategyRes.data.upload(context, {\n\t\t\tkey: data.targetKey,\n\t\t\tbody:\n\t\t\t\tupdatedStreamRes.data.body instanceof Uint8Array\n\t\t\t\t\t? Buffer.from(updatedStreamRes.data.body)\n\t\t\t\t\t: updatedStreamRes.data.body,\n\t\t\tmimeType: fileMetaData.data.mimeType,\n\t\t\textension: fileMetaData.data.extension,\n\t\t\tsize: mediaMetaRes.data.size,\n\t\t\ttype: fileMetaData.data.type,\n\t\t\ttenant: targetTenant,\n\t\t});\n\n\t\tconst targetMeta =\n\t\t\tuploadRes.error === undefined\n\t\t\t\t? await getVerifiedTargetMeta()\n\t\t\t\t: await getVerifiedTargetMeta(\n\t\t\t\t\t\tdata.targetKey === data.previousKey\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\trequireChangedEtag: true,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t);\n\n\t\tif (!targetMeta) {\n\t\t\tawait cleanupUpdatedKey();\n\n\t\t\treturn await failUpdate(\n\t\t\t\tuploadRes.error?.message ?? copy(\"server:core.errors.unknown\"),\n\t\t\t);\n\t\t}\n\n\t\tif (data.targetKey !== data.previousKey) {\n\t\t\tconst deleteOldRes = await mediaStrategyRes.data.delete(context, {\n\t\t\t\tkey: data.previousKey,\n\t\t\t\ttenant: previousTenant,\n\t\t\t});\n\t\t\tif (deleteOldRes.error) {\n\t\t\t\tawait mediaStrategyRes.data.delete(context, {\n\t\t\t\t\tkey: data.targetKey,\n\t\t\t\t\ttenant: targetTenant,\n\t\t\t\t});\n\t\t\t\tawait revertStorageDelta(delta);\n\n\t\t\t\treturn {\n\t\t\t\t\terror: {\n\t\t\t\t\t\ttype: \"basic\",\n\t\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\t\tstatus: 500,\n\t\t\t\t\t\terrors: {\n\t\t\t\t\t\t\tfile: {\n\t\t\t\t\t\t\t\tcode: \"media_error\",\n\t\t\t\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tdata: undefined,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tlet sourceDeleted = true;\n\t\tconst deleteUpdatedRes = await mediaStrategyRes.data.delete(context, {\n\t\t\tkey: data.updatedKey,\n\t\t\ttenant: updatedTenant,\n\t\t});\n\t\tif (deleteUpdatedRes.error) {\n\t\t\tsourceDeleted = false;\n\t\t}\n\n\t\treturn {\n\t\t\terror: undefined,\n\t\t\tdata: {\n\t\t\t\tmimeType: targetMeta.mimeType ?? fileMetaData.data.mimeType,\n\t\t\t\ttype: fileMetaData.data.type,\n\t\t\t\textension: fileMetaData.data.extension,\n\t\t\t\tsize: targetMeta.size,\n\t\t\t\tkey: data.targetKey,\n\t\t\t\tetag: targetMeta.etag,\n\t\t\t\tsourceDeleted,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst promoteRes = await mediaStrategyRes.data.rename(context, {\n\t\tfrom: data.updatedKey,\n\t\tto: data.targetKey,\n\t\ttenant: targetTenant,\n\t});\n\tif (promoteRes.error) {\n\t\tconst targetVerified = await getVerifiedTargetMeta();\n\t\tif (!targetVerified) {\n\t\t\treturn await failUpdate(\n\t\t\t\tpromoteRes.error.message ?? copy(\"server:core.errors.unknown\"),\n\t\t\t);\n\t\t}\n\n\t\tconst deleteUpdatedRes = await mediaStrategyRes.data.delete(context, {\n\t\t\tkey: data.updatedKey,\n\t\t\ttenant: updatedTenant,\n\t\t});\n\t\tconst sourceDeleted = deleteUpdatedRes.error === undefined;\n\n\t\tif (data.targetKey !== data.previousKey) {\n\t\t\tconst deleteOldRes = await mediaStrategyRes.data.delete(context, {\n\t\t\t\tkey: data.previousKey,\n\t\t\t\ttenant: previousTenant,\n\t\t\t});\n\t\t\tif (deleteOldRes.error) {\n\t\t\t\tawait mediaStrategyRes.data.delete(context, {\n\t\t\t\t\tkey: data.targetKey,\n\t\t\t\t\ttenant: targetTenant,\n\t\t\t\t});\n\t\t\t\tawait revertStorageDelta(delta);\n\n\t\t\t\treturn {\n\t\t\t\t\terror: {\n\t\t\t\t\t\ttype: \"basic\",\n\t\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\t\tstatus: 500,\n\t\t\t\t\t\terrors: {\n\t\t\t\t\t\t\tfile: {\n\t\t\t\t\t\t\t\tcode: \"media_error\",\n\t\t\t\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tdata: undefined,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\terror: undefined,\n\t\t\tdata: {\n\t\t\t\tmimeType: targetVerified.mimeType ?? fileMetaData.data.mimeType,\n\t\t\t\ttype: fileMetaData.data.type,\n\t\t\t\textension: fileMetaData.data.extension,\n\t\t\t\tsize: targetVerified.size,\n\t\t\t\tkey: data.targetKey,\n\t\t\t\tetag: targetVerified.etag,\n\t\t\t\tsourceDeleted,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst targetMeta = await getVerifiedTargetMeta();\n\tif (!targetMeta) {\n\t\treturn await failUpdate(copy(\"server:core.errors.unknown\"));\n\t}\n\n\tif (data.targetKey !== data.previousKey) {\n\t\tconst deleteOldRes = await mediaStrategyRes.data.delete(context, {\n\t\t\tkey: data.previousKey,\n\t\t\ttenant: previousTenant,\n\t\t});\n\t\tif (deleteOldRes.error) {\n\t\t\tawait mediaStrategyRes.data.delete(context, {\n\t\t\t\tkey: data.targetKey,\n\t\t\t\ttenant: targetTenant,\n\t\t\t});\n\t\t\tawait revertStorageDelta(delta);\n\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\ttype: \"basic\",\n\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\terrors: {\n\t\t\t\t\t\tfile: {\n\t\t\t\t\t\t\tcode: \"media_error\",\n\t\t\t\t\t\t\tmessage: deleteOldRes.error.message,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: {\n\t\t\tmimeType: targetMeta.mimeType ?? fileMetaData.data.mimeType,\n\t\t\ttype: fileMetaData.data.type,\n\t\t\textension: fileMetaData.data.extension,\n\t\t\tsize: targetMeta.size,\n\t\t\tkey: data.targetKey,\n\t\t\tetag: targetMeta.etag,\n\t\t\tsourceDeleted: true,\n\t\t},\n\t};\n};\n\nexport default update;\n"],"mappings":"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"}