@maas/payload-plugin-media-cloud 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/upload-handler/upload-handler.d.mts +2 -2
- package/dist/components/upload-handler/upload-handler.mjs +22 -4
- package/dist/components/upload-handler/upload-handler.mjs.map +1 -1
- package/dist/endpoints/tusCleanupHandler.d.mts +11 -0
- package/dist/endpoints/tusCleanupHandler.mjs +33 -0
- package/dist/endpoints/tusCleanupHandler.mjs.map +1 -0
- package/dist/tus/stores/s3/file-operations.mjs +1 -1
- package/dist/tus/stores/s3/file-operations.mjs.map +1 -1
- package/dist/tus/stores/s3/metadata-manager.d.mts +2 -2
- package/dist/tus/stores/s3/metadata-manager.mjs +4 -3
- package/dist/tus/stores/s3/metadata-manager.mjs.map +1 -1
- package/dist/types/errors.d.mts +4 -0
- package/dist/types/errors.mjs +4 -0
- package/dist/types/errors.mjs.map +1 -1
- package/dist/utils/mux.d.mts +3 -3
- package/dist/utils/tus.d.mts +6 -6
- package/dist/utils/tus.mjs +6 -0
- package/dist/utils/tus.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react0 from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as payload1 from "payload";
|
|
3
3
|
|
|
4
4
|
//#region src/components/upload-handler/upload-handler.d.ts
|
|
5
5
|
declare const UploadHandler: ({
|
|
@@ -11,7 +11,7 @@ declare const UploadHandler: ({
|
|
|
11
11
|
serverHandlerPath
|
|
12
12
|
}: {
|
|
13
13
|
children: react0.ReactNode;
|
|
14
|
-
collectionSlug:
|
|
14
|
+
collectionSlug: payload1.UploadCollectionSlug;
|
|
15
15
|
enabled?: boolean;
|
|
16
16
|
extra: Record<string, unknown>;
|
|
17
17
|
prefix?: string;
|
|
@@ -85,25 +85,41 @@ async function tusUpload(args) {
|
|
|
85
85
|
const filename = file.name;
|
|
86
86
|
const filetype = file.type;
|
|
87
87
|
const filesize = file.size.toString();
|
|
88
|
+
Object.keys(localStorage).filter((key) => key.startsWith("tus::")).forEach((key) => localStorage.removeItem(key));
|
|
88
89
|
let uploadUrlAvailable = false;
|
|
90
|
+
function onBeforeUnload(e) {
|
|
91
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
}
|
|
94
|
+
function onPageUnload() {
|
|
95
|
+
window.removeEventListener("unload", onPageUnload);
|
|
96
|
+
navigator.sendBeacon(`${serverURL}${apiRoute}/uploads/cleanup`, JSON.stringify({ filename }));
|
|
97
|
+
}
|
|
89
98
|
return new Promise((resolve) => {
|
|
90
99
|
new tus.Upload(file, {
|
|
91
100
|
endpoint: `${serverURL}${apiRoute}/uploads`,
|
|
92
101
|
retryDelays: TUS_RETRY_DELAYS,
|
|
93
102
|
chunkSize: TUS_CHUNK_SIZE,
|
|
103
|
+
storeFingerprintForResuming: false,
|
|
94
104
|
metadata: {
|
|
95
105
|
filetype,
|
|
96
106
|
filesize,
|
|
97
107
|
filename,
|
|
98
108
|
contentType: filetype,
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
contentLength: filesize,
|
|
110
|
+
contentDisposition: "inline"
|
|
101
111
|
},
|
|
102
|
-
onError
|
|
112
|
+
onError(error) {
|
|
103
113
|
logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message);
|
|
114
|
+
logError(error.message);
|
|
115
|
+
navigator.sendBeacon(`${serverURL}${apiRoute}/uploads/cleanup`, JSON.stringify({ filename }));
|
|
104
116
|
toast.error("File upload failed");
|
|
105
117
|
resolve(null);
|
|
106
118
|
},
|
|
119
|
+
onBeforeRequest() {
|
|
120
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
121
|
+
window.addEventListener("unload", onPageUnload);
|
|
122
|
+
},
|
|
107
123
|
onProgress: function(bytesUploaded, bytesTotal) {
|
|
108
124
|
const percentage = Math.round(bytesUploaded / bytesTotal * 100);
|
|
109
125
|
emitter.emit("updateUpload", {
|
|
@@ -111,7 +127,9 @@ async function tusUpload(args) {
|
|
|
111
127
|
progress: percentage
|
|
112
128
|
});
|
|
113
129
|
},
|
|
114
|
-
onSuccess
|
|
130
|
+
onSuccess() {
|
|
131
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
132
|
+
window.removeEventListener("unload", onPageUnload);
|
|
115
133
|
emitter.emit("uploadComplete", { filename });
|
|
116
134
|
fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`);
|
|
117
135
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-handler.mjs","names":["uploadArgs: UploadArgs"],"sources":["../../../src/components/upload-handler/upload-handler.tsx"],"sourcesContent":["'use client'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { toast } from '@payloadcms/ui'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { isVideo, getFileType, generateUniqueFilename } from '../../utils/file'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: string\n}\n\ninterface UploadResult {\n filename: string\n uploadId?: string\n mimeType: string\n storage: 'mux' | 's3'\n}\n\ninterface MuxCreateUploadResponse {\n url: string\n uploadId: string\n filename: string\n}\n\nconst { logError, throwError } = useErrorHandler()\nconst emitter = useMediaCloudEmitter()\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { file, serverURL, apiRoute, mimeType } = args\n\n const filename = file.name\n try {\n // Request upload URL from Mux\n const response = await fetch(`${serverURL}${apiRoute}/mux/upload`, {\n body: JSON.stringify({ filename, mimeType }),\n credentials: 'include',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n // Create upchunk uploader\n const uploader = await upchunk.createUpload({\n endpoint: url,\n file,\n chunkSize: MUX_CHUNK_SIZE,\n })\n\n // Add upload to tracker\n emitter.emit('addUpload', {\n filename,\n uploadId,\n polling: false,\n pollingUrl: `${serverURL}${apiRoute}/mux/asset`,\n })\n\n // Set up event handlers\n uploader.on('error', function () {\n logError(MediaCloudErrors.MUX_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n emitter.emit('removeUpload', { uploadId })\n })\n\n uploader.on('progress', function (progress) {\n emitter.emit('updateUpload', {\n filename,\n progress: progress.detail,\n })\n })\n\n uploader.on('success', function () {\n emitter.emit('uploadComplete', { filename })\n })\n\n // Update collection entry\n // with filename, uploadId, mimeType, and storage\n return {\n filename,\n uploadId,\n mimeType,\n storage: 'mux',\n }\n } catch (_error) {\n logError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n return null\n }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { apiRoute, serverURL, file, mimeType } = args\n\n const filename = file.name\n const filetype = file.type\n const filesize = file.size.toString()\n\n // Flag to prevent multiple onUploadUrlAvailable calls\n let uploadUrlAvailable = false\n\n return new Promise((resolve) => {\n const upload = new tus.Upload(file, {\n endpoint: `${serverURL}${apiRoute}/uploads`,\n retryDelays: TUS_RETRY_DELAYS,\n chunkSize: TUS_CHUNK_SIZE,\n metadata: {\n filetype,\n filesize,\n filename,\n contentType: filetype,\n contentDisposition: 'inline',\n contentLength: filesize,\n },\n onError: function () {\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n toast.error('File upload failed')\n resolve(null)\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n onSuccess: function () {\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`)\n },\n onUploadUrlAvailable: async function () {\n // Prevent multiple callbacks\n if (uploadUrlAvailable) {\n return\n }\n\n // Update flag\n uploadUrlAvailable = true\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n // with filename, mimeType, and storage\n resolve({\n filename,\n mimeType,\n storage: 's3',\n })\n },\n })\n\n upload.start()\n })\n}\n\nconst { log } = useErrorHandler()\n\nexport const UploadHandler = createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, updateFilename } = args\n console.log('upload handler called', file)\n try {\n const mimeType =\n (await getFileType(file)) || file.type || 'application/octet-stream'\n if (!mimeType) {\n throwError(MediaCloudErrors.FILE_TYPE_UNKNOWN)\n return null\n }\n\n const isVideoFile = await isVideo(file)\n\n // Clone file\n let mappedFile = file\n\n if (!isVideoFile) {\n try {\n // Check if file with same name exists\n const response = await fetch(\n `${serverURL}${apiRoute}/uploads/${file.name}/exists`\n )\n\n // If file exists, generate a cloned file with a unique filename\n if (response.status === 200) {\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n\n const newFilename = generateUniqueFilename(file.name)\n mappedFile = new File([file], newFilename, { type: file.type })\n await updateFilename(newFilename)\n }\n } catch (_error) {\n logError(MediaCloudErrors.NAMING_FUNCTION_ERROR.message)\n }\n }\n\n const uploadArgs: UploadArgs = {\n file: mappedFile,\n serverURL,\n apiRoute,\n mimeType,\n }\n\n if (isVideoFile) {\n return await muxUpload(uploadArgs)\n } else {\n return await tusUpload(uploadArgs)\n }\n } catch (error) {\n console.error(\n '[PLUGIN-MEDIA-CLOUD] Upload handler detailed error:',\n error\n )\n logError(MediaCloudErrors.UPLOAD_HANDLER_ERROR.message)\n toast.error(\n `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n return null\n }\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"upload-handler.mjs","names":["uploadArgs: UploadArgs"],"sources":["../../../src/components/upload-handler/upload-handler.tsx"],"sourcesContent":["'use client'\n\nimport * as upchunk from '@mux/upchunk'\nimport * as tus from 'tus-js-client'\n\nimport { toast } from '@payloadcms/ui'\nimport { createClientUploadHandler } from '@payloadcms/plugin-cloud-storage/client'\n\nimport { MediaCloudErrors, MediaCloudLogs } from '../../types/errors'\nimport { useMediaCloudEmitter } from '../../hooks/useMediaCloudEmitter'\nimport { useErrorHandler } from '../../hooks/useErrorHandler'\nimport { isVideo, getFileType, generateUniqueFilename } from '../../utils/file'\n\ninterface UploadArgs {\n serverURL: string\n apiRoute: string\n file: File\n mimeType: string\n}\n\ninterface UploadResult {\n filename: string\n uploadId?: string\n mimeType: string\n storage: 'mux' | 's3'\n}\n\ninterface MuxCreateUploadResponse {\n url: string\n uploadId: string\n filename: string\n}\n\nconst { logError, throwError } = useErrorHandler()\nconst emitter = useMediaCloudEmitter()\n\nconst MUX_CHUNK_SIZE = 30720\nconst TUS_CHUNK_SIZE = 1024 * 1024\nconst TUS_RETRY_DELAYS = [0, 1000, 2000, 5000]\n\n/**\n * Handles Mux video upload with progress tracking\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function muxUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { file, serverURL, apiRoute, mimeType } = args\n\n const filename = file.name\n try {\n // Request upload URL from Mux\n const response = await fetch(`${serverURL}${apiRoute}/mux/upload`, {\n body: JSON.stringify({ filename, mimeType }),\n credentials: 'include',\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n\n const { url, uploadId } = (await response.json()) as MuxCreateUploadResponse\n\n // Create upchunk uploader\n const uploader = await upchunk.createUpload({\n endpoint: url,\n file,\n chunkSize: MUX_CHUNK_SIZE,\n })\n\n // Add upload to tracker\n emitter.emit('addUpload', {\n filename,\n uploadId,\n polling: false,\n pollingUrl: `${serverURL}${apiRoute}/mux/asset`,\n })\n\n // Set up event handlers\n uploader.on('error', function () {\n logError(MediaCloudErrors.MUX_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n emitter.emit('removeUpload', { uploadId })\n })\n\n uploader.on('progress', function (progress) {\n emitter.emit('updateUpload', {\n filename,\n progress: progress.detail,\n })\n })\n\n uploader.on('success', function () {\n emitter.emit('uploadComplete', { filename })\n })\n\n // Update collection entry\n // with filename, uploadId, mimeType, and storage\n return {\n filename,\n uploadId,\n mimeType,\n storage: 'mux',\n }\n } catch (_error) {\n logError(MediaCloudErrors.MUX_DIRECT_UPLOAD_ERROR.message)\n toast.error('Video upload failed')\n return null\n }\n}\n\n/**\n * Handles TUS file upload with resumable capabilities\n * @param args - The upload arguments including file, server URL, and callbacks\n * @returns Promise that resolves to upload result or null if upload fails\n */\nasync function tusUpload(args: UploadArgs): Promise<UploadResult | null> {\n const { apiRoute, serverURL, file, mimeType } = args\n\n const filename = file.name\n const filetype = file.type\n const filesize = file.size.toString()\n\n // Clear any stale TUS uploads from localStorage\n Object.keys(localStorage)\n .filter((key) => key.startsWith('tus::'))\n .forEach((key) => localStorage.removeItem(key))\n\n // Flag to prevent multiple onUploadUrlAvailable calls\n let uploadUrlAvailable = false\n let eventListenerAdded = false\n\n function onBeforeUnload(e: BeforeUnloadEvent) {\n window.removeEventListener('beforeunload', onBeforeUnload)\n e.preventDefault()\n }\n\n function onPageUnload() {\n window.removeEventListener('unload', onPageUnload)\n navigator.sendBeacon(\n `${serverURL}${apiRoute}/uploads/cleanup`,\n JSON.stringify({ filename })\n )\n }\n\n return new Promise((resolve) => {\n const upload = new tus.Upload(file, {\n endpoint: `${serverURL}${apiRoute}/uploads`,\n retryDelays: TUS_RETRY_DELAYS,\n chunkSize: TUS_CHUNK_SIZE,\n storeFingerprintForResuming: false,\n metadata: {\n filetype,\n filesize,\n filename,\n contentType: filetype,\n contentLength: filesize,\n contentDisposition: 'inline',\n },\n onError(error) {\n logError(MediaCloudErrors.TUS_UPLOAD_ERROR.message)\n logError(error.message)\n\n // Clean up\n navigator.sendBeacon(\n `${serverURL}${apiRoute}/uploads/cleanup`,\n JSON.stringify({ filename })\n )\n\n // Inform user\n toast.error('File upload failed')\n resolve(null)\n },\n onBeforeRequest() {\n if (!eventListenerAdded) {\n window.addEventListener('beforeunload', onBeforeUnload)\n window.addEventListener('unload', onPageUnload)\n }\n },\n onProgress: function (bytesUploaded, bytesTotal) {\n const percentage = Math.round((bytesUploaded / bytesTotal) * 100)\n emitter.emit('updateUpload', {\n filename,\n progress: percentage,\n })\n },\n onSuccess() {\n // Clean up beforeunload listener\n window.removeEventListener('beforeunload', onBeforeUnload)\n window.removeEventListener('unload', onPageUnload)\n\n // Mark upload as complete in UI\n emitter.emit('uploadComplete', { filename })\n\n // Trigger post upload processing\n fetch(`${serverURL}${apiRoute}/uploads/${filename}/process`)\n },\n onUploadUrlAvailable: async function () {\n // Prevent multiple callbacks\n if (uploadUrlAvailable) {\n return\n }\n\n // Update flag\n uploadUrlAvailable = true\n\n // Add upload to UI\n emitter.emit('addUpload', { filename })\n\n // Update collection entry\n // with filename, mimeType, and storage\n resolve({\n filename,\n mimeType,\n storage: 's3',\n })\n },\n })\n\n upload.start()\n })\n}\n\nconst { log } = useErrorHandler()\n\nexport const UploadHandler = createClientUploadHandler({\n handler: async function (args) {\n const { serverURL, apiRoute, file, updateFilename } = args\n console.log('upload handler called', file)\n try {\n const mimeType =\n (await getFileType(file)) || file.type || 'application/octet-stream'\n if (!mimeType) {\n throwError(MediaCloudErrors.FILE_TYPE_UNKNOWN)\n return null\n }\n\n const isVideoFile = await isVideo(file)\n\n // Clone file\n let mappedFile = file\n\n if (!isVideoFile) {\n try {\n // Check if file with same name exists\n const response = await fetch(\n `${serverURL}${apiRoute}/uploads/${file.name}/exists`\n )\n\n // If file exists, generate a cloned file with a unique filename\n if (response.status === 200) {\n log(MediaCloudLogs.S3_STORE_FILE_FOUND)\n\n const newFilename = generateUniqueFilename(file.name)\n mappedFile = new File([file], newFilename, { type: file.type })\n await updateFilename(newFilename)\n }\n } catch (_error) {\n logError(MediaCloudErrors.NAMING_FUNCTION_ERROR.message)\n }\n }\n\n const uploadArgs: UploadArgs = {\n file: mappedFile,\n serverURL,\n apiRoute,\n mimeType,\n }\n\n if (isVideoFile) {\n return await muxUpload(uploadArgs)\n } else {\n return await tusUpload(uploadArgs)\n }\n } catch (error) {\n console.error(\n '[PLUGIN-MEDIA-CLOUD] Upload handler detailed error:',\n error\n )\n logError(MediaCloudErrors.UPLOAD_HANDLER_ERROR.message)\n toast.error(\n `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n )\n return null\n }\n },\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createS3Store } from "../tus/stores/s3/index.mjs";
|
|
2
|
+
import { PayloadHandler } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/endpoints/tusCleanupHandler.d.ts
|
|
5
|
+
interface GetTusCleanupHandlerArgs {
|
|
6
|
+
s3Store: ReturnType<typeof createS3Store>;
|
|
7
|
+
}
|
|
8
|
+
declare function getTusCleanupHandler(args: GetTusCleanupHandlerArgs): PayloadHandler;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { getTusCleanupHandler };
|
|
11
|
+
//# sourceMappingURL=tusCleanupHandler.d.mts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { MediaCloudErrors } from "../types/errors.mjs";
|
|
2
|
+
import { useErrorHandler } from "../hooks/useErrorHandler.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/endpoints/tusCleanupHandler.ts
|
|
5
|
+
const { logError } = useErrorHandler();
|
|
6
|
+
function getTusCleanupHandler(args) {
|
|
7
|
+
const { s3Store } = args;
|
|
8
|
+
return async (req) => {
|
|
9
|
+
try {
|
|
10
|
+
const { filename } = await req.json?.();
|
|
11
|
+
if (!filename) return new Response("Missing filename", { status: 400 });
|
|
12
|
+
await s3Store.remove(filename);
|
|
13
|
+
const payload = req.payload;
|
|
14
|
+
const { docs } = await req.payload.find({
|
|
15
|
+
collection: "media",
|
|
16
|
+
where: { filename: { equals: filename } },
|
|
17
|
+
limit: 1
|
|
18
|
+
});
|
|
19
|
+
if (docs?.length > 0) await payload.delete({
|
|
20
|
+
collection: "media",
|
|
21
|
+
id: docs[0].id
|
|
22
|
+
});
|
|
23
|
+
return new Response("Cleanup successful", { status: 200 });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logError(MediaCloudErrors.TUS_CLEANUP_ERROR.message);
|
|
26
|
+
return new Response("Cleanup failed", { status: 500 });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { getTusCleanupHandler };
|
|
33
|
+
//# sourceMappingURL=tusCleanupHandler.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tusCleanupHandler.mjs","names":[],"sources":["../../src/endpoints/tusCleanupHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { createS3Store } from '../tus/stores/s3'\nimport { MediaCloudErrors } from '../types/errors'\n\ninterface GetTusCleanupHandlerArgs {\n s3Store: ReturnType<typeof createS3Store>\n}\n\nconst { logError } = useErrorHandler()\n\nexport function getTusCleanupHandler(\n args: GetTusCleanupHandlerArgs\n): PayloadHandler {\n const { s3Store } = args\n\n return async (req) => {\n try {\n const data = await req.json?.()\n const { filename } = data\n\n if (!filename) {\n return new Response('Missing filename', { status: 400 })\n }\n\n // Abort the multipart upload and delete S3 objects (.info, .part)\n await s3Store.remove(filename)\n\n // Find and delete the Payload document\n const payload = req.payload\n const { docs } = await req.payload.find({\n collection: 'media',\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n })\n\n if (docs?.length > 0) {\n await payload.delete({\n collection: 'media',\n id: docs[0].id,\n })\n }\n\n return new Response('Cleanup successful', { status: 200 })\n } catch (error) {\n logError(MediaCloudErrors.TUS_CLEANUP_ERROR.message)\n return new Response('Cleanup failed', { status: 500 })\n }\n }\n}\n"],"mappings":";;;;AASA,MAAM,EAAE,aAAa,iBAAiB;AAEtC,SAAgB,qBACd,MACgB;CAChB,MAAM,EAAE,YAAY;AAEpB,QAAO,OAAO,QAAQ;AACpB,MAAI;GAEF,MAAM,EAAE,aADK,MAAM,IAAI,QAAQ;AAG/B,OAAI,CAAC,SACH,QAAO,IAAI,SAAS,oBAAoB,EAAE,QAAQ,KAAK,CAAC;AAI1D,SAAM,QAAQ,OAAO,SAAS;GAG9B,MAAM,UAAU,IAAI;GACpB,MAAM,EAAE,SAAS,MAAM,IAAI,QAAQ,KAAK;IACtC,YAAY;IACZ,OAAO,EACL,UAAU,EACR,QAAQ,UACT,EACF;IACD,OAAO;IACR,CAAC;AAEF,OAAI,MAAM,SAAS,EACjB,OAAM,QAAQ,OAAO;IACnB,YAAY;IACZ,IAAI,KAAK,GAAG;IACb,CAAC;AAGJ,UAAO,IAAI,SAAS,sBAAsB,EAAE,QAAQ,KAAK,CAAC;WACnD,OAAO;AACd,YAAS,iBAAiB,kBAAkB,QAAQ;AACpD,UAAO,IAAI,SAAS,kBAAkB,EAAE,QAAQ,KAAK,CAAC"}
|
|
@@ -23,7 +23,7 @@ var S3FileOperations = class {
|
|
|
23
23
|
calculateOptimalPartSize(args) {
|
|
24
24
|
const { size } = args;
|
|
25
25
|
let uploadSize = size;
|
|
26
|
-
if (uploadSize === void 0) uploadSize = this.
|
|
26
|
+
if (uploadSize === void 0) uploadSize = this.preferredPartSize;
|
|
27
27
|
let optimalPartSize;
|
|
28
28
|
if (uploadSize <= this.preferredPartSize) optimalPartSize = uploadSize;
|
|
29
29
|
else if (uploadSize <= this.preferredPartSize * this.maxMultipartParts) optimalPartSize = this.preferredPartSize;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-operations.mjs","names":["maxMultipartParts: number","maxUploadSize: number","minPartSize: number","preferredPartSize: number","optimalPartSize: number"],"sources":["../../../../src/tus/stores/s3/file-operations.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../../types/errors'\n\nimport type { NodeFSError } from '../../../types'\nimport type AWS from '@aws-sdk/client-s3'\n\ntype CalculateOptimalPartSizeArgs = {\n size?: number\n}\n\ntype CalculateOffsetFromPartsArgs = {\n parts?: Array<AWS.Part>\n}\n\ntype CalculatePartNumberArgs = {\n parts: Array<AWS.Part>\n}\n\ntype GenerateUniqueTmpFileNameArgs = {\n template: string\n}\n\ntype CalculateOffsetFromPartsExportArgs = {\n parts?: Array<{ Size?: number }>\n}\n\nconst { throwError } = useErrorHandler()\n\nexport class S3FileOperations {\n constructor(\n private maxMultipartParts: number,\n private maxUploadSize: number,\n private minPartSize: number,\n private preferredPartSize: number\n ) {}\n\n /**\n * Calculates the optimal part size for S3 multipart upload\n * @param args - The function arguments\n * @param args.size - The upload size in bytes (optional)\n * @returns The optimal part size in bytes\n */\n calculateOptimalPartSize(args: CalculateOptimalPartSizeArgs): number {\n const { size } = args\n // When upload size is not known
|
|
1
|
+
{"version":3,"file":"file-operations.mjs","names":["maxMultipartParts: number","maxUploadSize: number","minPartSize: number","preferredPartSize: number","optimalPartSize: number"],"sources":["../../../../src/tus/stores/s3/file-operations.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../../../types/errors'\n\nimport type { NodeFSError } from '../../../types'\nimport type AWS from '@aws-sdk/client-s3'\n\ntype CalculateOptimalPartSizeArgs = {\n size?: number\n}\n\ntype CalculateOffsetFromPartsArgs = {\n parts?: Array<AWS.Part>\n}\n\ntype CalculatePartNumberArgs = {\n parts: Array<AWS.Part>\n}\n\ntype GenerateUniqueTmpFileNameArgs = {\n template: string\n}\n\ntype CalculateOffsetFromPartsExportArgs = {\n parts?: Array<{ Size?: number }>\n}\n\nconst { throwError } = useErrorHandler()\n\nexport class S3FileOperations {\n constructor(\n private maxMultipartParts: number,\n private maxUploadSize: number,\n private minPartSize: number,\n private preferredPartSize: number\n ) {}\n\n /**\n * Calculates the optimal part size for S3 multipart upload\n * @param args - The function arguments\n * @param args.size - The upload size in bytes (optional)\n * @returns The optimal part size in bytes\n */\n calculateOptimalPartSize(args: CalculateOptimalPartSizeArgs): number {\n const { size } = args\n // When upload size is not known, default to preferredPartSize\n // Assuming maxUploadSize can lead to extremely large chunks (hundreds of GB),\n // which breaks streaming/resumable uploads in practice\n let uploadSize = size\n if (uploadSize === undefined) {\n uploadSize = this.preferredPartSize\n }\n\n let optimalPartSize: number\n\n // When upload is smaller or equal to preferredPartSize, upload in just one part\n if (uploadSize <= this.preferredPartSize) {\n optimalPartSize = uploadSize\n }\n // Does the upload fit in maxMultipartParts parts or less with preferredPartSize\n else if (uploadSize <= this.preferredPartSize * this.maxMultipartParts) {\n optimalPartSize = this.preferredPartSize\n // The upload is too big for the preferred size\n // We divide the size with the max amount of parts and round it up\n } else {\n optimalPartSize = Math.ceil(uploadSize / this.maxMultipartParts)\n }\n\n // Always ensure the part size is at least minPartSize\n return Math.max(optimalPartSize, this.minPartSize)\n }\n\n /**\n * Calculates the offset based on uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts (optional)\n * @returns The total offset in bytes\n */\n calculateOffsetFromParts(args: CalculateOffsetFromPartsArgs): number {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n }\n\n /**\n * Calculates the next part number based on existing parts\n * @param args - The function arguments\n * @param args.parts - Array of uploaded parts\n * @returns The next part number to use\n */\n calculatePartNumber(args: CalculatePartNumberArgs): number {\n const { parts } = args\n return parts.length > 0 ? parts[parts.length - 1].PartNumber! + 1 : 1\n }\n\n /**\n * Generates a unique temporary file name\n * @param args - The function arguments\n * @param args.template - The template string for the file name\n * @returns Promise that resolves to the unique file path\n * @throws Error if unable to find unique name after max tries\n */\n async generateUniqueTmpFileName(\n args: GenerateUniqueTmpFileNameArgs\n ): Promise<string> {\n const { template } = args\n const tries = 5\n for (let i = 0; i < tries; i++) {\n const randomId = crypto.randomBytes(16).toString('hex')\n const filePath = path.join(os.tmpdir(), `${template}${randomId}`)\n try {\n await fs.promises.access(filePath, fs.constants.F_OK)\n } catch (error) {\n const nodeError = error as NodeFSError\n if (nodeError.code === 'ENOENT') {\n return filePath\n }\n }\n }\n throwError(MediaCloudErrors.S3_UNIQUE_NAME_ERROR)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n}\n\n/**\n * Calculates the total offset from uploaded parts\n * @param args - The function arguments\n * @param args.parts - Array of parts with size information (optional)\n * @returns The total offset in bytes\n */\nexport function calculateOffsetFromParts(\n args: CalculateOffsetFromPartsExportArgs\n) {\n const { parts } = args\n return parts && parts.length > 0\n ? parts.reduce((a, b) => a + (b.Size ?? 0), 0)\n : 0\n}\n"],"mappings":";;;;;;;;AA+BA,MAAM,EAAE,eAAe,iBAAiB;AAExC,IAAa,mBAAb,MAA8B;CAC5B,YACE,AAAQA,mBACR,AAAQC,eACR,AAAQC,aACR,AAAQC,mBACR;EAJQ;EACA;EACA;EACA;;;;;;;;CASV,yBAAyB,MAA4C;EACnE,MAAM,EAAE,SAAS;EAIjB,IAAI,aAAa;AACjB,MAAI,eAAe,OACjB,cAAa,KAAK;EAGpB,IAAIC;AAGJ,MAAI,cAAc,KAAK,kBACrB,mBAAkB;WAGX,cAAc,KAAK,oBAAoB,KAAK,kBACnD,mBAAkB,KAAK;MAIvB,mBAAkB,KAAK,KAAK,aAAa,KAAK,kBAAkB;AAIlE,SAAO,KAAK,IAAI,iBAAiB,KAAK,YAAY;;;;;;;;CASpD,yBAAyB,MAA4C;EACnE,MAAM,EAAE,UAAU;AAClB,SAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C;;;;;;;;CASN,oBAAoB,MAAuC;EACzD,MAAM,EAAE,UAAU;AAClB,SAAO,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,GAAG,aAAc,IAAI;;;;;;;;;CAUtE,MAAM,0BACJ,MACiB;EACjB,MAAM,EAAE,aAAa;EACrB,MAAM,QAAQ;AACd,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,MAAM,WAAW,OAAO,YAAY,GAAG,CAAC,SAAS,MAAM;GACvD,MAAM,WAAW,KAAK,KAAK,GAAG,QAAQ,EAAE,GAAG,WAAW,WAAW;AACjE,OAAI;AACF,UAAM,GAAG,SAAS,OAAO,UAAU,GAAG,UAAU,KAAK;YAC9C,OAAO;AAEd,QADkB,MACJ,SAAS,SACrB,QAAO;;;AAIb,aAAW,iBAAiB,qBAAqB;AACjD,QAAM,IAAI,OAAO;;;;;;;;;AAUrB,SAAgB,yBACd,MACA;CACA,MAAM,EAAE,UAAU;AAClB,QAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C"}
|
|
@@ -46,9 +46,9 @@ declare class S3MetadataManager {
|
|
|
46
46
|
*/
|
|
47
47
|
generatePartKey(args: GeneratePartKeyArgs): string;
|
|
48
48
|
/**
|
|
49
|
-
* Retrieves upload metadata previously saved in `${file_id}.info
|
|
49
|
+
* Retrieves upload metadata previously saved in `${file_id}.info`
|
|
50
50
|
* There's a small and simple caching mechanism to avoid multiple
|
|
51
|
-
* HTTP calls to S3
|
|
51
|
+
* HTTP calls to S3
|
|
52
52
|
* @param args - The function arguments
|
|
53
53
|
* @param args.id - The file ID to retrieve metadata for
|
|
54
54
|
* @returns Promise that resolves to the upload metadata
|
|
@@ -36,9 +36,9 @@ var S3MetadataManager = class {
|
|
|
36
36
|
return key;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Retrieves upload metadata previously saved in `${file_id}.info
|
|
39
|
+
* Retrieves upload metadata previously saved in `${file_id}.info`
|
|
40
40
|
* There's a small and simple caching mechanism to avoid multiple
|
|
41
|
-
* HTTP calls to S3
|
|
41
|
+
* HTTP calls to S3
|
|
42
42
|
* @param args - The function arguments
|
|
43
43
|
* @param args.id - The file ID to retrieve metadata for
|
|
44
44
|
* @returns Promise that resolves to the upload metadata
|
|
@@ -52,13 +52,14 @@ var S3MetadataManager = class {
|
|
|
52
52
|
Key: this.generateInfoKey({ id })
|
|
53
53
|
});
|
|
54
54
|
const file = JSON.parse(await Body?.transformToString());
|
|
55
|
+
const parsedSize = file.size === void 0 || file.size === null ? void 0 : Number(file.size);
|
|
55
56
|
const metadata = {
|
|
56
57
|
file: new Upload({
|
|
57
58
|
id,
|
|
58
59
|
creation_date: file.creation_date,
|
|
59
60
|
metadata: file.metadata,
|
|
60
61
|
offset: Number.parseInt(file.offset, 10),
|
|
61
|
-
size: Number.isFinite(
|
|
62
|
+
size: Number.isFinite(parsedSize) ? parsedSize : void 0,
|
|
62
63
|
storage: file.storage
|
|
63
64
|
}),
|
|
64
65
|
"tus-version": Metadata?.["tus-version"],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata-manager.mjs","names":["client: S3","bucket: string","cache: KvStore<TusUploadMetadata>","shouldUseExpirationTags: () => boolean","generateCompleteTag: (value: 'false' | 'true') => string | undefined","metadata: TusUploadMetadata","completedMetadata: TusUploadMetadata"],"sources":["../../../../src/tus/stores/s3/metadata-manager.ts"],"sourcesContent":["import { TUS_RESUMABLE, Upload } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type { S3 } from '@aws-sdk/client-s3'\nimport type { KvStore } from '@tus/utils'\nimport type { TusUploadMetadata } from '../../../types'\n\ntype GenerateInfoKeyArgs = {\n id: string\n}\n\ntype GeneratePartKeyArgs = {\n id: string\n isIncomplete?: boolean\n}\n\ntype GetMetadataArgs = {\n id: string\n}\n\ntype SaveMetadataArgs = {\n upload: Upload\n uploadId: string\n}\n\ntype CompleteMetadataArgs = {\n upload: Upload\n}\n\ntype ClearCacheArgs = {\n id: string\n}\n\nconst { log } = useErrorHandler()\n\nexport class S3MetadataManager {\n constructor(\n private client: S3,\n private bucket: string,\n private cache: KvStore<TusUploadMetadata>,\n private shouldUseExpirationTags: () => boolean,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n /**\n * Generates the S3 key for metadata info files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @returns The S3 key for the metadata file\n */\n generateInfoKey(args: GenerateInfoKeyArgs): string {\n const { id } = args\n return `${id}.info`\n }\n\n /**\n * Generates the S3 key for part files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @param args.isIncomplete - Whether this is an incomplete part\n * @returns The S3 key for the part file\n */\n generatePartKey(args: GeneratePartKeyArgs): string {\n const { id, isIncomplete = false } = args\n let key = id\n if (isIncomplete) {\n key += '.part'\n }\n\n // TODO: introduce ObjectPrefixing for parts and incomplete parts
|
|
1
|
+
{"version":3,"file":"metadata-manager.mjs","names":["client: S3","bucket: string","cache: KvStore<TusUploadMetadata>","shouldUseExpirationTags: () => boolean","generateCompleteTag: (value: 'false' | 'true') => string | undefined","metadata: TusUploadMetadata","completedMetadata: TusUploadMetadata"],"sources":["../../../../src/tus/stores/s3/metadata-manager.ts"],"sourcesContent":["import { TUS_RESUMABLE, Upload } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudLogs } from '../../../types/errors'\n\nimport type { S3 } from '@aws-sdk/client-s3'\nimport type { KvStore } from '@tus/utils'\nimport type { TusUploadMetadata } from '../../../types'\n\ntype GenerateInfoKeyArgs = {\n id: string\n}\n\ntype GeneratePartKeyArgs = {\n id: string\n isIncomplete?: boolean\n}\n\ntype GetMetadataArgs = {\n id: string\n}\n\ntype SaveMetadataArgs = {\n upload: Upload\n uploadId: string\n}\n\ntype CompleteMetadataArgs = {\n upload: Upload\n}\n\ntype ClearCacheArgs = {\n id: string\n}\n\nconst { log } = useErrorHandler()\n\nexport class S3MetadataManager {\n constructor(\n private client: S3,\n private bucket: string,\n private cache: KvStore<TusUploadMetadata>,\n private shouldUseExpirationTags: () => boolean,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n /**\n * Generates the S3 key for metadata info files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @returns The S3 key for the metadata file\n */\n generateInfoKey(args: GenerateInfoKeyArgs): string {\n const { id } = args\n return `${id}.info`\n }\n\n /**\n * Generates the S3 key for part files\n * @param args - The function arguments\n * @param args.id - The file ID\n * @param args.isIncomplete - Whether this is an incomplete part\n * @returns The S3 key for the part file\n */\n generatePartKey(args: GeneratePartKeyArgs): string {\n const { id, isIncomplete = false } = args\n let key = id\n if (isIncomplete) {\n key += '.part'\n }\n\n // TODO: introduce ObjectPrefixing for parts and incomplete parts\n // ObjectPrefix is prepended to the name of each S3 object that is created\n // to store uploaded files. It can be used to create a pseudo-directory\n // structure in the bucket, e.g. \"path/to/my/uploads\".\n return key\n }\n\n /**\n * Retrieves upload metadata previously saved in `${file_id}.info`\n * There's a small and simple caching mechanism to avoid multiple\n * HTTP calls to S3\n * @param args - The function arguments\n * @param args.id - The file ID to retrieve metadata for\n * @returns Promise that resolves to the upload metadata\n */\n async getMetadata(args: GetMetadataArgs): Promise<TusUploadMetadata> {\n const { id } = args\n const cached = await this.cache.get(id)\n\n if (cached) {\n return cached\n }\n\n const { Body, Metadata } = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id }),\n })\n const file = JSON.parse((await Body?.transformToString()) as string)\n\n const parsedSize =\n file.size === undefined || file.size === null\n ? undefined\n : Number(file.size)\n const metadata: TusUploadMetadata = {\n file: new Upload({\n id,\n creation_date: file.creation_date,\n metadata: file.metadata,\n offset: Number.parseInt(file.offset, 10),\n size: Number.isFinite(parsedSize) ? parsedSize : undefined,\n storage: file.storage,\n }),\n 'tus-version': Metadata?.['tus-version'] as string,\n 'upload-id': Metadata?.['upload-id'] as string,\n }\n await this.cache.set(id, metadata)\n return metadata\n }\n\n /**\n * Saves upload metadata to a `${file_id}.info` file on S3.\n * Please note that the file is empty and the metadata is saved\n * on the S3 object's `Metadata` field, so that only a `headObject`\n * is necessary to retrieve the data.\n * @param args - The function arguments\n * @param args.upload - The upload object containing metadata\n * @param args.uploadId - The upload ID for the multipart upload\n * @returns Promise that resolves when metadata is saved\n */\n async saveMetadata(args: SaveMetadataArgs): Promise<void> {\n const { upload, uploadId } = args\n log(MediaCloudLogs.S3_STORE_METADATA_SAVING)\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_METADATA_SAVED)\n }\n\n /**\n * Completes metadata by updating it with completion tags\n * @param args - The function arguments\n * @param args.upload - The completed upload object\n * @returns Promise that resolves when metadata is updated\n */\n async completeMetadata(args: CompleteMetadataArgs): Promise<void> {\n const { upload } = args\n // Always update the cache with the completed upload\n const metadata = await this.getMetadata({ id: upload.id })\n const completedMetadata: TusUploadMetadata = {\n ...metadata,\n file: upload,\n }\n await this.cache.set(upload.id, completedMetadata)\n\n if (!this.shouldUseExpirationTags()) {\n return\n }\n\n const { 'upload-id': uploadId } = metadata\n await this.client.putObject({\n Body: JSON.stringify(upload),\n Bucket: this.bucket,\n Key: this.generateInfoKey({ id: upload.id }),\n Metadata: {\n 'tus-version': TUS_RESUMABLE,\n 'upload-id': uploadId,\n },\n Tagging: this.generateCompleteTag('true'),\n })\n }\n\n /**\n * Removes cached data for a given file\n * @param args - The function arguments\n * @param args.id - The file ID to remove from cache\n * @returns Promise that resolves when cache is cleared\n */\n async clearCache(args: ClearCacheArgs): Promise<void> {\n const { id } = args\n log(MediaCloudLogs.S3_STORE_METADATA_CACHE_CLEARED)\n await this.cache.delete(id)\n }\n}\n"],"mappings":";;;;;AAmCA,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,IAAa,oBAAb,MAA+B;CAC7B,YACE,AAAQA,QACR,AAAQC,QACR,AAAQC,OACR,AAAQC,yBACR,AAAQC,qBACR;EALQ;EACA;EACA;EACA;EACA;;;;;;;;CAQV,gBAAgB,MAAmC;EACjD,MAAM,EAAE,OAAO;AACf,SAAO,GAAG,GAAG;;;;;;;;;CAUf,gBAAgB,MAAmC;EACjD,MAAM,EAAE,IAAI,eAAe,UAAU;EACrC,IAAI,MAAM;AACV,MAAI,aACF,QAAO;AAOT,SAAO;;;;;;;;;;CAWT,MAAM,YAAY,MAAmD;EACnE,MAAM,EAAE,OAAO;EACf,MAAM,SAAS,MAAM,KAAK,MAAM,IAAI,GAAG;AAEvC,MAAI,OACF,QAAO;EAGT,MAAM,EAAE,MAAM,aAAa,MAAM,KAAK,OAAO,UAAU;GACrD,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,CAAC;GAClC,CAAC;EACF,MAAM,OAAO,KAAK,MAAO,MAAM,MAAM,mBAAmB,CAAY;EAEpE,MAAM,aACJ,KAAK,SAAS,UAAa,KAAK,SAAS,OACrC,SACA,OAAO,KAAK,KAAK;EACvB,MAAMC,WAA8B;GAClC,MAAM,IAAI,OAAO;IACf;IACA,eAAe,KAAK;IACpB,UAAU,KAAK;IACf,QAAQ,OAAO,SAAS,KAAK,QAAQ,GAAG;IACxC,MAAM,OAAO,SAAS,WAAW,GAAG,aAAa;IACjD,SAAS,KAAK;IACf,CAAC;GACF,eAAe,WAAW;GAC1B,aAAa,WAAW;GACzB;AACD,QAAM,KAAK,MAAM,IAAI,IAAI,SAAS;AAClC,SAAO;;;;;;;;;;;;CAaT,MAAM,aAAa,MAAuC;EACxD,MAAM,EAAE,QAAQ,aAAa;AAC7B,MAAI,eAAe,yBAAyB;AAC5C,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,QAAQ;GAC3C,CAAC;AACF,MAAI,eAAe,wBAAwB;;;;;;;;CAS7C,MAAM,iBAAiB,MAA2C;EAChE,MAAM,EAAE,WAAW;EAEnB,MAAM,WAAW,MAAM,KAAK,YAAY,EAAE,IAAI,OAAO,IAAI,CAAC;EAC1D,MAAMC,oBAAuC;GAC3C,GAAG;GACH,MAAM;GACP;AACD,QAAM,KAAK,MAAM,IAAI,OAAO,IAAI,kBAAkB;AAElD,MAAI,CAAC,KAAK,yBAAyB,CACjC;EAGF,MAAM,EAAE,aAAa,aAAa;AAClC,QAAM,KAAK,OAAO,UAAU;GAC1B,MAAM,KAAK,UAAU,OAAO;GAC5B,QAAQ,KAAK;GACb,KAAK,KAAK,gBAAgB,EAAE,IAAI,OAAO,IAAI,CAAC;GAC5C,UAAU;IACR,eAAe;IACf,aAAa;IACd;GACD,SAAS,KAAK,oBAAoB,OAAO;GAC1C,CAAC;;;;;;;;CASJ,MAAM,WAAW,MAAqC;EACpD,MAAM,EAAE,OAAO;AACf,MAAI,eAAe,gCAAgC;AACnD,QAAM,KAAK,MAAM,OAAO,GAAG"}
|
package/dist/types/errors.d.mts
CHANGED
|
@@ -56,6 +56,10 @@ declare const MediaCloudErrors: {
|
|
|
56
56
|
readonly message: "TUS file upload error occurred";
|
|
57
57
|
readonly errorCode: 500;
|
|
58
58
|
};
|
|
59
|
+
readonly TUS_CLEANUP_ERROR: {
|
|
60
|
+
readonly message: "Error during cleanup";
|
|
61
|
+
readonly errorCode: 500;
|
|
62
|
+
};
|
|
59
63
|
readonly UNKNOWN_STORAGE_TYPE: {
|
|
60
64
|
readonly message: "Unknown storage type for media";
|
|
61
65
|
readonly errorCode: 500;
|
package/dist/types/errors.mjs
CHANGED
|
@@ -56,6 +56,10 @@ const MediaCloudErrors = {
|
|
|
56
56
|
message: "TUS file upload error occurred",
|
|
57
57
|
errorCode: 500
|
|
58
58
|
},
|
|
59
|
+
TUS_CLEANUP_ERROR: {
|
|
60
|
+
message: "Error during cleanup",
|
|
61
|
+
errorCode: 500
|
|
62
|
+
},
|
|
59
63
|
UNKNOWN_STORAGE_TYPE: {
|
|
60
64
|
message: "Unknown storage type for media",
|
|
61
65
|
errorCode: 500
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_METADATA_CACHE_CLEARED = 'Removing cached data',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_METADATA_CACHE_CLEARED = 'Removing cached data',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
package/dist/utils/mux.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import Mux from "@mux/mux-node";
|
|
3
|
-
import * as
|
|
3
|
+
import * as payload2 from "payload";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/mux.d.ts
|
|
6
6
|
|
|
@@ -14,11 +14,11 @@ interface CreateMuxEndpointsArgs {
|
|
|
14
14
|
pluginOptions: MediaCloudPluginOptions;
|
|
15
15
|
}
|
|
16
16
|
declare function createMuxEndpoints(args: CreateMuxEndpointsArgs): ({
|
|
17
|
-
handler:
|
|
17
|
+
handler: payload2.PayloadHandler;
|
|
18
18
|
method: "post";
|
|
19
19
|
path: string;
|
|
20
20
|
} | {
|
|
21
|
-
handler:
|
|
21
|
+
handler: payload2.PayloadHandler;
|
|
22
22
|
method: "get";
|
|
23
23
|
path: string;
|
|
24
24
|
})[];
|
package/dist/utils/tus.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MediaCloudPluginOptions } from "../types/index.mjs";
|
|
2
2
|
import { S3Store } from "../tus/stores/s3/s3-store.mjs";
|
|
3
3
|
import { Server } from "@tus/server";
|
|
4
|
-
import * as
|
|
4
|
+
import * as payload0 from "payload";
|
|
5
5
|
import { PayloadRequest } from "payload";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.d.ts
|
|
@@ -29,10 +29,6 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
29
29
|
handler: (req: PayloadRequest) => Promise<Response>;
|
|
30
30
|
method: "options";
|
|
31
31
|
path: string;
|
|
32
|
-
} | {
|
|
33
|
-
handler: (req: PayloadRequest) => Promise<Response>;
|
|
34
|
-
method: "post";
|
|
35
|
-
path: string;
|
|
36
32
|
} | {
|
|
37
33
|
handler: (req: PayloadRequest) => Promise<Response>;
|
|
38
34
|
method: "put";
|
|
@@ -46,9 +42,13 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
46
42
|
method: "delete";
|
|
47
43
|
path: string;
|
|
48
44
|
} | {
|
|
49
|
-
handler:
|
|
45
|
+
handler: payload0.PayloadHandler;
|
|
50
46
|
method: "get";
|
|
51
47
|
path: string;
|
|
48
|
+
} | {
|
|
49
|
+
handler: payload0.PayloadHandler;
|
|
50
|
+
method: "post";
|
|
51
|
+
path: string;
|
|
52
52
|
})[];
|
|
53
53
|
//#endregion
|
|
54
54
|
export { createTusEndpoints, createTusServer };
|
package/dist/utils/tus.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { generateUniqueFilename } from "./file.mjs";
|
|
2
2
|
import { getTusPostProcessorHandler } from "../endpoints/tusPostProcessorHandler.mjs";
|
|
3
3
|
import { getTusFileExistsHandler } from "../endpoints/tusFileExistsHandler.mjs";
|
|
4
|
+
import { getTusCleanupHandler } from "../endpoints/tusCleanupHandler.mjs";
|
|
4
5
|
import { Server } from "@tus/server";
|
|
5
6
|
|
|
6
7
|
//#region src/utils/tus.ts
|
|
@@ -65,6 +66,11 @@ function createTusEndpoints(args) {
|
|
|
65
66
|
handler: getTusPostProcessorHandler({ s3Store }),
|
|
66
67
|
method: "get",
|
|
67
68
|
path: "/uploads/:filename/process"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
handler: getTusCleanupHandler({ s3Store }),
|
|
72
|
+
method: "post",
|
|
73
|
+
path: "/uploads/cleanup"
|
|
68
74
|
}
|
|
69
75
|
];
|
|
70
76
|
}
|
package/dist/utils/tus.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tus.mjs","names":["TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport { getTusFileExistsHandler } from '../endpoints/tusFileExistsHandler'\n\nimport { generateUniqueFilename } from './file'\n\nimport type { S3Store } from '../tus/stores/s3/s3-store'\nimport type { PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args - The arguments for creating the TUS server\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n s3Store: S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { s3Store, pluginOptions } = args\n\n return new TusServer({\n datastore: s3Store,\n namingFunction: async (_req, metadata) => {\n return metadata?.filename ?? generateUniqueFilename('')\n },\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3.respectForwardedHeaders ?? true,\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param tusServer - The TUS server instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n tusServer: TusServer\n s3Store: S3Store\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { tusServer, s3Store } = args\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return tusServer.handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusFileExistsHandler({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/exists',\n },\n {\n handler: getTusPostProcessorHandler({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n ]\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"tus.mjs","names":["TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport { getTusFileExistsHandler } from '../endpoints/tusFileExistsHandler'\n\nimport { generateUniqueFilename } from './file'\n\nimport type { S3Store } from '../tus/stores/s3/s3-store'\nimport type { PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types'\nimport { getTusCleanupHandler } from '../endpoints/tusCleanupHandler'\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args - The arguments for creating the TUS server\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n s3Store: S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { s3Store, pluginOptions } = args\n\n return new TusServer({\n datastore: s3Store,\n namingFunction: async (_req, metadata) => {\n return metadata?.filename ?? generateUniqueFilename('')\n },\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3.respectForwardedHeaders ?? true,\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param tusServer - The TUS server instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n tusServer: TusServer\n s3Store: S3Store\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { tusServer, s3Store } = args\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return tusServer.handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusFileExistsHandler({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/exists',\n },\n {\n handler: getTusPostProcessorHandler({ s3Store }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n {\n handler: getTusCleanupHandler({ s3Store }),\n method: 'post' as const,\n path: '/uploads/cleanup',\n },\n ]\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,gBAAgB,MAAsC;CACpE,MAAM,EAAE,SAAS,kBAAkB;AAEnC,QAAO,IAAIA,OAAU;EACnB,WAAW;EACX,gBAAgB,OAAO,MAAM,aAAa;AACxC,UAAO,UAAU,YAAY,uBAAuB,GAAG;;EAEzD,MAAM;EACN,yBAAyB,cAAc,GAAG,2BAA2B;EACtE,CAAC;;AAaJ,SAAgB,mBAAmB,MAA8B;CAC/D,MAAM,EAAE,WAAW,YAAY;;;;;;CAO/B,SAAS,WAAW,KAAqB;AACvC,SAAO,UAAU,UAAU,IAAe;;AAG5C,QAAO;EACL;GAAE,SAAS;GAAY,QAAQ;GAAoB,MAAM;GAAY;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAiB,MAAM;GAAY;EAClE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAkB,MAAM;GAAgB;EACvE;GAAE,SAAS;GAAY,QAAQ;GAAmB,MAAM;GAAgB;EACxE;GACE,SAAS,wBAAwB,EAAE,SAAS,CAAC;GAC7C,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,2BAA2B,EAAE,SAAS,CAAC;GAChD,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,qBAAqB,EAAE,SAAS,CAAC;GAC1C,QAAQ;GACR,MAAM;GACP;EACF"}
|