@maas/payload-plugin-media-cloud 0.0.0
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/LICENSE +8 -0
- package/dist/adapter/handleDelete.d.ts +20 -0
- package/dist/adapter/handleDelete.js +70 -0
- package/dist/adapter/handleDelete.js.map +1 -0
- package/dist/adapter/handleUpload.d.ts +12 -0
- package/dist/adapter/handleUpload.js +29 -0
- package/dist/adapter/handleUpload.js.map +1 -0
- package/dist/adapter/staticHandler.d.ts +17 -0
- package/dist/adapter/staticHandler.js +64 -0
- package/dist/adapter/staticHandler.js.map +1 -0
- package/dist/adapter/storageAdapter.d.ts +23 -0
- package/dist/adapter/storageAdapter.js +30 -0
- package/dist/adapter/storageAdapter.js.map +1 -0
- package/dist/collections/mediaCollection.d.ts +16 -0
- package/dist/collections/mediaCollection.js +139 -0
- package/dist/collections/mediaCollection.js.map +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +5 -0
- package/dist/components/mux-preview/index.d.ts +2 -0
- package/dist/components/mux-preview/index.js +3 -0
- package/dist/components/mux-preview/mux-preview.d.ts +14 -0
- package/dist/components/mux-preview/mux-preview.js +38 -0
- package/dist/components/mux-preview/mux-preview.js.map +1 -0
- package/dist/components/upload-handler/index.d.ts +2 -0
- package/dist/components/upload-handler/index.js +3 -0
- package/dist/components/upload-handler/upload-handler.d.ts +22 -0
- package/dist/components/upload-handler/upload-handler.js +178 -0
- package/dist/components/upload-handler/upload-handler.js.map +1 -0
- package/dist/components/upload-manager/index.d.ts +2 -0
- package/dist/components/upload-manager/index.js +3 -0
- package/dist/components/upload-manager/upload-manager-DN4RrmYB.css +204 -0
- package/dist/components/upload-manager/upload-manager-DN4RrmYB.css.map +1 -0
- package/dist/components/upload-manager/upload-manager.css +201 -0
- package/dist/components/upload-manager/upload-manager.d.ts +42 -0
- package/dist/components/upload-manager/upload-manager.js +315 -0
- package/dist/components/upload-manager/upload-manager.js.map +1 -0
- package/dist/components/upload-manager/upload-manager2.js +0 -0
- package/dist/endpoints/muxAssetHandler.d.ts +11 -0
- package/dist/endpoints/muxAssetHandler.js +59 -0
- package/dist/endpoints/muxAssetHandler.js.map +1 -0
- package/dist/endpoints/muxCreateUploadHandler.d.ts +13 -0
- package/dist/endpoints/muxCreateUploadHandler.js +40 -0
- package/dist/endpoints/muxCreateUploadHandler.js.map +1 -0
- package/dist/endpoints/muxWebhookHandler.d.ts +11 -0
- package/dist/endpoints/muxWebhookHandler.js +49 -0
- package/dist/endpoints/muxWebhookHandler.js.map +1 -0
- package/dist/hooks/useEmitter.d.ts +48 -0
- package/dist/hooks/useEmitter.js +19 -0
- package/dist/hooks/useEmitter.js.map +1 -0
- package/dist/hooks/useErrorHandler.d.ts +11 -0
- package/dist/hooks/useErrorHandler.js +19 -0
- package/dist/hooks/useErrorHandler.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.js +242 -0
- package/dist/plugin.js.map +1 -0
- package/dist/tus/stores/s3/expiration-manager.d.ts +36 -0
- package/dist/tus/stores/s3/expiration-manager.js +76 -0
- package/dist/tus/stores/s3/expiration-manager.js.map +1 -0
- package/dist/tus/stores/s3/file-operations.d.ts +66 -0
- package/dist/tus/stores/s3/file-operations.js +90 -0
- package/dist/tus/stores/s3/file-operations.js.map +1 -0
- package/dist/tus/stores/s3/log.d.ts +5 -0
- package/dist/tus/stores/s3/log.js +8 -0
- package/dist/tus/stores/s3/log.js.map +1 -0
- package/dist/tus/stores/s3/metadata-manager.d.ts +85 -0
- package/dist/tus/stores/s3/metadata-manager.js +135 -0
- package/dist/tus/stores/s3/metadata-manager.js.map +1 -0
- package/dist/tus/stores/s3/parts-manager.d.ts +130 -0
- package/dist/tus/stores/s3/parts-manager.js +328 -0
- package/dist/tus/stores/s3/parts-manager.js.map +1 -0
- package/dist/tus/stores/s3/s3-store.d.ts +110 -0
- package/dist/tus/stores/s3/s3-store.js +342 -0
- package/dist/tus/stores/s3/s3-store.js.map +1 -0
- package/dist/tus/stores/s3/semaphore.d.ts +16 -0
- package/dist/tus/stores/s3/semaphore.js +32 -0
- package/dist/tus/stores/s3/semaphore.js.map +1 -0
- package/dist/types/errors.d.ts +26 -0
- package/dist/types/errors.js +28 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +73 -0
- package/dist/types/index.js +0 -0
- package/dist/utils/file.d.ts +30 -0
- package/dist/utils/file.js +84 -0
- package/dist/utils/file.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MediaCloudError } from "../types/errors.js";
|
|
2
|
+
import { ErrorLevel } from "@maas/error-handler";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/useErrorHandler.d.ts
|
|
5
|
+
declare function useErrorHandler(): {
|
|
6
|
+
logError: (errorKey: MediaCloudError, level?: ErrorLevel) => void;
|
|
7
|
+
throwError: (errorKey: MediaCloudError) => never;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { useErrorHandler };
|
|
11
|
+
//# sourceMappingURL=useErrorHandler.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MediaCloudError } from "../types/errors.js";
|
|
2
|
+
import { ErrorLevel, createErrorHandler } from "@maas/error-handler";
|
|
3
|
+
|
|
4
|
+
//#region src/hooks/useErrorHandler.ts
|
|
5
|
+
function useErrorHandler() {
|
|
6
|
+
const { logError, throwError } = createErrorHandler({
|
|
7
|
+
prefix: "PLUGIN-MEDIA-CLOUD",
|
|
8
|
+
level: ErrorLevel.ERROR,
|
|
9
|
+
errors: MediaCloudError
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
logError,
|
|
13
|
+
throwError
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { useErrorHandler };
|
|
19
|
+
//# sourceMappingURL=useErrorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useErrorHandler.js","names":[],"sources":["../../src/hooks/useErrorHandler.ts"],"sourcesContent":["import { createErrorHandler, ErrorLevel } from '@maas/error-handler'\nimport { MediaCloudError } from '../types/errors'\n\nexport function useErrorHandler() {\n const { logError, throwError } = createErrorHandler({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n level: ErrorLevel.ERROR,\n errors: MediaCloudError,\n })\n\n return {\n logError,\n throwError,\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,kBAAkB;CAChC,MAAM,EAAE,UAAU,YAAY,GAAG,mBAAmB;EAClD,QAAQ;EACR,OAAO,WAAW;EAClB,QAAQ;CACT,EAAC;AAEF,QAAO;EACL;EACA;CACD;AACF"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MediaCloudPluginOptions } from "./types/index.js";
|
|
2
|
+
import { Config } from "payload";
|
|
3
|
+
|
|
4
|
+
//#region src/plugin.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Media Cloud Plugin for Payload CMS
|
|
8
|
+
* Provides file upload capabilities using S3 storage and Mux video processing
|
|
9
|
+
* @param pluginOptions - Configuration options for the plugin
|
|
10
|
+
* @returns A function that configures the Payload config
|
|
11
|
+
*/
|
|
12
|
+
declare function mediaCloudPlugin(pluginOptions: MediaCloudPluginOptions): (config: Config) => Config;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { mediaCloudPlugin };
|
|
15
|
+
//# sourceMappingURL=plugin.d.ts.map
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { MediaCloudError } from "./types/errors.js";
|
|
2
|
+
import { useErrorHandler } from "./hooks/useErrorHandler.js";
|
|
3
|
+
import { getStorageAdapter } from "./adapter/storageAdapter.js";
|
|
4
|
+
import { getMediaCollection } from "./collections/mediaCollection.js";
|
|
5
|
+
import { getMuxAssetHandler } from "./endpoints/muxAssetHandler.js";
|
|
6
|
+
import { getMuxCreateUploadHandler } from "./endpoints/muxCreateUploadHandler.js";
|
|
7
|
+
import { getMuxWebhookHandler } from "./endpoints/muxWebhookHandler.js";
|
|
8
|
+
import { S3Store } from "./tus/stores/s3/s3-store.js";
|
|
9
|
+
import { generateUniqueFilename } from "./utils/file.js";
|
|
10
|
+
import Mux from "@mux/mux-node";
|
|
11
|
+
import { cloudStoragePlugin } from "@payloadcms/plugin-cloud-storage";
|
|
12
|
+
import { initClientUploads } from "@payloadcms/plugin-cloud-storage/utilities";
|
|
13
|
+
import { getPayload, sanitizeConfig } from "payload";
|
|
14
|
+
import { Server } from "@tus/server";
|
|
15
|
+
|
|
16
|
+
//#region src/plugin.ts
|
|
17
|
+
const { logError, throwError } = useErrorHandler();
|
|
18
|
+
let muxClient = null;
|
|
19
|
+
/**
|
|
20
|
+
* Validates Mux configuration options
|
|
21
|
+
* @param muxConfig - The Mux configuration to validate
|
|
22
|
+
* @throws {Error} When required Mux configuration is missing
|
|
23
|
+
*/
|
|
24
|
+
function validateMuxConfig(muxConfig) {
|
|
25
|
+
if (!muxConfig?.tokenId || !muxConfig?.tokenSecret) throwError(MediaCloudError.MUX_CONFIG_MISSING);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new Mux client instance
|
|
29
|
+
* @param muxConfig - The Mux configuration options
|
|
30
|
+
* @returns A configured Mux client
|
|
31
|
+
*/
|
|
32
|
+
function createMuxClient(muxConfig) {
|
|
33
|
+
return new Mux({
|
|
34
|
+
tokenId: muxConfig.tokenId,
|
|
35
|
+
tokenSecret: muxConfig.tokenSecret,
|
|
36
|
+
webhookSecret: muxConfig.webhookSecret
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates and configures an S3Store instance
|
|
41
|
+
* @param s3Config - The S3 configuration options
|
|
42
|
+
* @returns A configured S3Store instance
|
|
43
|
+
* @throws {Error} When required S3 configuration is missing
|
|
44
|
+
*/
|
|
45
|
+
function createS3Store(s3Config) {
|
|
46
|
+
if (!s3Config?.bucket || !s3Config?.region || !s3Config?.accessKeyId || !s3Config?.secretAccessKey) throwError(MediaCloudError.S3_CONFIG_MISSING);
|
|
47
|
+
return new S3Store({ s3ClientConfig: {
|
|
48
|
+
acl: "public-read",
|
|
49
|
+
bucket: s3Config.bucket,
|
|
50
|
+
credentials: {
|
|
51
|
+
accessKeyId: s3Config.accessKeyId,
|
|
52
|
+
secretAccessKey: s3Config.secretAccessKey
|
|
53
|
+
},
|
|
54
|
+
endpoint: s3Config.endpoint,
|
|
55
|
+
forcePathStyle: true,
|
|
56
|
+
region: s3Config.region
|
|
57
|
+
} });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Creates a TUS server instance with S3 storage
|
|
61
|
+
* @param args - The arguments for creating the TUS server
|
|
62
|
+
* @returns A configured TusServer instance
|
|
63
|
+
*/
|
|
64
|
+
function createTusServer(args) {
|
|
65
|
+
const { config, s3Store } = args;
|
|
66
|
+
return new Server({
|
|
67
|
+
datastore: s3Store,
|
|
68
|
+
namingFunction: async (req, metadata) => {
|
|
69
|
+
try {
|
|
70
|
+
const payload = await getPayload({ config: sanitizeConfig(config) });
|
|
71
|
+
const { docs } = await payload.find({
|
|
72
|
+
collection: "media",
|
|
73
|
+
where: { filename: { equals: metadata?.filename || "" } }
|
|
74
|
+
});
|
|
75
|
+
if (docs.length > 0) return generateUniqueFilename(metadata?.filename ?? "");
|
|
76
|
+
else return metadata?.filename || generateUniqueFilename("");
|
|
77
|
+
} catch (_error) {
|
|
78
|
+
logError(MediaCloudError.NAMING_FUNCTION_ERROR);
|
|
79
|
+
return metadata?.filename || generateUniqueFilename("");
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
path: "/api/uploads"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Creates TUS upload endpoints for file handling
|
|
87
|
+
* @param tusServer - The TUS server instance
|
|
88
|
+
* @returns An array of endpoint configurations
|
|
89
|
+
*/
|
|
90
|
+
function createTusEndpoints(tusServer) {
|
|
91
|
+
/**
|
|
92
|
+
* Handles TUS requests through the server
|
|
93
|
+
* @param req - The payload request object
|
|
94
|
+
* @returns The server response
|
|
95
|
+
*/
|
|
96
|
+
function tusHandler(req) {
|
|
97
|
+
return tusServer.handleWeb(req);
|
|
98
|
+
}
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
handler: tusHandler,
|
|
102
|
+
method: "options",
|
|
103
|
+
path: "/uploads"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
handler: tusHandler,
|
|
107
|
+
method: "post",
|
|
108
|
+
path: "/uploads"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
handler: tusHandler,
|
|
112
|
+
method: "get",
|
|
113
|
+
path: "/uploads/:id"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
handler: tusHandler,
|
|
117
|
+
method: "put",
|
|
118
|
+
path: "/uploads/:id"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
handler: tusHandler,
|
|
122
|
+
method: "patch",
|
|
123
|
+
path: "/uploads/:id"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
handler: tusHandler,
|
|
127
|
+
method: "delete",
|
|
128
|
+
path: "/uploads/:id"
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Creates Mux-related endpoints for asset handling
|
|
134
|
+
* @param args - The arguments for creating Mux endpoints
|
|
135
|
+
* @returns An array of Mux endpoint configurations
|
|
136
|
+
*/
|
|
137
|
+
function createMuxEndpoints(args) {
|
|
138
|
+
const { getMuxClient, pluginOptions } = args;
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
handler: getMuxWebhookHandler({ getMuxClient }),
|
|
142
|
+
method: "get",
|
|
143
|
+
path: "/mux/webhook"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
handler: getMuxCreateUploadHandler({
|
|
147
|
+
getMuxClient,
|
|
148
|
+
pluginOptions
|
|
149
|
+
}),
|
|
150
|
+
method: "post",
|
|
151
|
+
path: "/mux/upload"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
handler: getMuxAssetHandler({ getMuxClient }),
|
|
155
|
+
method: "get",
|
|
156
|
+
path: "/mux/asset"
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Media Cloud Plugin for Payload CMS
|
|
162
|
+
* Provides file upload capabilities using S3 storage and Mux video processing
|
|
163
|
+
* @param pluginOptions - Configuration options for the plugin
|
|
164
|
+
* @returns A function that configures the Payload config
|
|
165
|
+
*/
|
|
166
|
+
function mediaCloudPlugin(pluginOptions) {
|
|
167
|
+
return function(config) {
|
|
168
|
+
if (!pluginOptions) {
|
|
169
|
+
logError(MediaCloudError.PLUGIN_NOT_CONFIGURED);
|
|
170
|
+
return config;
|
|
171
|
+
}
|
|
172
|
+
const isPluginDisabled = pluginOptions.enabled === false;
|
|
173
|
+
/**
|
|
174
|
+
* Gets or creates a Mux client instance
|
|
175
|
+
* @returns The Mux client instance
|
|
176
|
+
*/
|
|
177
|
+
function getMuxClient() {
|
|
178
|
+
if (muxClient) return muxClient;
|
|
179
|
+
return createMuxClient(pluginOptions.mux);
|
|
180
|
+
}
|
|
181
|
+
if (pluginOptions.mux) {
|
|
182
|
+
validateMuxConfig(pluginOptions.mux);
|
|
183
|
+
muxClient = getMuxClient();
|
|
184
|
+
} else {
|
|
185
|
+
logError(MediaCloudError.MUX_CONFIG_INCOMPLETE);
|
|
186
|
+
muxClient = null;
|
|
187
|
+
}
|
|
188
|
+
const s3Store = createS3Store(pluginOptions.s3);
|
|
189
|
+
const tusServer = createTusServer({
|
|
190
|
+
config,
|
|
191
|
+
s3Store
|
|
192
|
+
});
|
|
193
|
+
const mediaCollection = getMediaCollection({ s3Store });
|
|
194
|
+
if (isPluginDisabled) return config;
|
|
195
|
+
initClientUploads({
|
|
196
|
+
clientHandler: "@maas/payload-plugin-media-cloud/components#UploadHandler",
|
|
197
|
+
collections: { media: {
|
|
198
|
+
clientUploads: true,
|
|
199
|
+
disableLocalStorage: true
|
|
200
|
+
} },
|
|
201
|
+
config,
|
|
202
|
+
enabled: true,
|
|
203
|
+
serverHandler: () => {
|
|
204
|
+
return Response.json({ message: "Server handler is not implemented" }, { status: 501 });
|
|
205
|
+
},
|
|
206
|
+
serverHandlerPath: "/media-cloud/upload"
|
|
207
|
+
});
|
|
208
|
+
const cloudStorageConfig = { collections: { media: {
|
|
209
|
+
adapter: getStorageAdapter({
|
|
210
|
+
getMuxClient,
|
|
211
|
+
pluginOptions,
|
|
212
|
+
s3Store
|
|
213
|
+
}),
|
|
214
|
+
clientUploads: true,
|
|
215
|
+
disableLocalStorage: true
|
|
216
|
+
} } };
|
|
217
|
+
const mappedConfig = {
|
|
218
|
+
...config,
|
|
219
|
+
admin: {
|
|
220
|
+
...config.admin ?? {},
|
|
221
|
+
components: {
|
|
222
|
+
...config.admin?.components ?? {},
|
|
223
|
+
providers: ["@maas/payload-plugin-media-cloud/components#UploadManagerProvider", ...config.admin?.components?.providers ?? []]
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
collections: [...config.collections ?? [], mediaCollection],
|
|
227
|
+
endpoints: [
|
|
228
|
+
...config.endpoints ?? [],
|
|
229
|
+
...createTusEndpoints(tusServer),
|
|
230
|
+
...createMuxEndpoints({
|
|
231
|
+
getMuxClient,
|
|
232
|
+
pluginOptions
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
};
|
|
236
|
+
return cloudStoragePlugin(cloudStorageConfig)(mappedConfig);
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
//#endregion
|
|
241
|
+
export { mediaCloudPlugin };
|
|
242
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["muxClient: Mux | null","muxConfig: MediaCloudPluginOptions['mux']","s3Config: MediaCloudPluginOptions['s3']","args: CreateTusServerArgs","TusServer","tusServer: TusServer","req: PayloadRequest","args: CreateMuxEndpointsArgs","pluginOptions: MediaCloudPluginOptions","config: Config","mappedConfig: Config"],"sources":["../src/plugin.ts"],"sourcesContent":["import Mux from '@mux/mux-node'\n\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { getPayload, sanitizeConfig } from 'payload'\nimport { Server as TusServer, type DataStore } from '@tus/server'\n\nimport { MediaCloudError } from './types/errors'\nimport { useErrorHandler } from './hooks/useErrorHandler'\nimport { getStorageAdapter } from './adapter/storageAdapter'\nimport { getMediaCollection } from './collections/mediaCollection'\nimport { getMuxAssetHandler } from './endpoints/muxAssetHandler'\nimport { getMuxCreateUploadHandler } from './endpoints/muxCreateUploadHandler'\nimport { getMuxWebhookHandler } from './endpoints/muxWebhookHandler'\nimport { S3Store } from './tus/stores/s3/s3-store'\nimport { generateUniqueFilename } from './utils/file'\n\nimport type { Config, PayloadRequest } from 'payload'\nimport type { MediaCloudPluginOptions } from './types'\n\ninterface CreateTusServerArgs {\n config: Config\n s3Store: DataStore\n}\n\ninterface CreateMuxEndpointsArgs {\n getMuxClient: () => Mux\n pluginOptions: MediaCloudPluginOptions\n}\n\nconst { logError, throwError } = useErrorHandler()\n\nlet muxClient: Mux | null = null\n\n/**\n * Validates Mux configuration options\n * @param muxConfig - The Mux configuration to validate\n * @throws {Error} When required Mux configuration is missing\n */\nfunction validateMuxConfig(muxConfig: MediaCloudPluginOptions['mux']): void {\n if (!muxConfig?.tokenId || !muxConfig?.tokenSecret) {\n throwError(MediaCloudError.MUX_CONFIG_MISSING)\n }\n}\n\n/**\n * Creates a new Mux client instance\n * @param muxConfig - The Mux configuration options\n * @returns A configured Mux client\n */\nfunction createMuxClient(muxConfig: MediaCloudPluginOptions['mux']): Mux {\n // Create and return Mux client instance\n return new Mux({\n tokenId: muxConfig.tokenId,\n tokenSecret: muxConfig.tokenSecret,\n webhookSecret: muxConfig.webhookSecret,\n })\n}\n\n/**\n * Creates and configures an S3Store instance\n * @param s3Config - The S3 configuration options\n * @returns A configured S3Store instance\n * @throws {Error} When required S3 configuration is missing\n */\nfunction createS3Store(s3Config: MediaCloudPluginOptions['s3']): S3Store {\n // Validate S3 configuration\n if (\n !s3Config?.bucket ||\n !s3Config?.region ||\n !s3Config?.accessKeyId ||\n !s3Config?.secretAccessKey\n ) {\n throwError(MediaCloudError.S3_CONFIG_MISSING)\n }\n\n // Create and return S3Store instance\n return new S3Store({\n s3ClientConfig: {\n acl: 'public-read',\n bucket: s3Config.bucket,\n credentials: {\n accessKeyId: s3Config.accessKeyId,\n secretAccessKey: s3Config.secretAccessKey,\n },\n endpoint: s3Config.endpoint,\n forcePathStyle: true,\n region: s3Config.region,\n },\n })\n}\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 */\nfunction createTusServer(args: CreateTusServerArgs): TusServer {\n const { config, s3Store } = args\n\n return new TusServer({\n datastore: s3Store,\n namingFunction: async (req, metadata) => {\n try {\n // Get Payload instance\n const payload = await getPayload({\n config: sanitizeConfig(config),\n })\n\n // Check if a file with the same name already exists in the media collection\n const { docs } = await payload.find({\n collection: 'media',\n where: {\n filename: {\n equals: metadata?.filename || '',\n },\n },\n })\n\n // If a file with the same name exists, generate a unique filename\n if (docs.length > 0) {\n return generateUniqueFilename(metadata?.filename ?? '')\n } else {\n // If no file with the same name exists, return the original filename\n return metadata?.filename || generateUniqueFilename('')\n }\n } catch (_error) {\n // If an error occurs, log it and return the original filename\n logError(MediaCloudError.NAMING_FUNCTION_ERROR)\n return metadata?.filename || generateUniqueFilename('')\n }\n },\n path: '/api/uploads',\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 */\nfunction createTusEndpoints(tusServer: TusServer) {\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}\n\n/**\n * Creates Mux-related endpoints for asset handling\n * @param args - The arguments for creating Mux endpoints\n * @returns An array of Mux endpoint configurations\n */\nfunction createMuxEndpoints(args: CreateMuxEndpointsArgs) {\n const { getMuxClient, pluginOptions } = args\n return [\n {\n handler: getMuxWebhookHandler({ getMuxClient }),\n method: 'get' as const,\n path: '/mux/webhook',\n },\n {\n handler: getMuxCreateUploadHandler({ getMuxClient, pluginOptions }),\n method: 'post' as const,\n path: '/mux/upload',\n },\n {\n handler: getMuxAssetHandler({ getMuxClient }),\n method: 'get' as const,\n path: '/mux/asset',\n },\n ]\n}\n\n/**\n * Media Cloud Plugin for Payload CMS\n * Provides file upload capabilities using S3 storage and Mux video processing\n * @param pluginOptions - Configuration options for the plugin\n * @returns A function that configures the Payload config\n */\nexport function mediaCloudPlugin(pluginOptions: MediaCloudPluginOptions) {\n return function (config: Config): Config {\n if (!pluginOptions) {\n logError(MediaCloudError.PLUGIN_NOT_CONFIGURED)\n return config\n }\n\n const isPluginDisabled = pluginOptions.enabled === false\n\n /**\n * Gets or creates a Mux client instance\n * @returns The Mux client instance\n */\n function getMuxClient(): Mux {\n // Return existing Mux client if already created\n if (muxClient) {\n return muxClient\n }\n // Create and return a new Mux client\n return createMuxClient(pluginOptions.mux)\n }\n\n // Validate Mux configuration\n if (pluginOptions.mux) {\n validateMuxConfig(pluginOptions.mux)\n muxClient = getMuxClient()\n } else {\n logError(MediaCloudError.MUX_CONFIG_INCOMPLETE)\n muxClient = null\n }\n\n const s3Store = createS3Store(pluginOptions.s3)\n const tusServer = createTusServer({ config, s3Store })\n const mediaCollection = getMediaCollection({ s3Store })\n\n if (isPluginDisabled) {\n return config\n }\n\n // Initialize client uploads\n initClientUploads({\n clientHandler:\n '@maas/payload-plugin-media-cloud/components#UploadHandler',\n collections: {\n media: {\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n config,\n enabled: true,\n serverHandler: () => {\n return Response.json(\n { message: 'Server handler is not implemented' },\n { status: 501 }\n )\n },\n serverHandlerPath: '/media-cloud/upload',\n })\n\n const cloudStorageConfig = {\n collections: {\n media: {\n adapter: getStorageAdapter({ getMuxClient, pluginOptions, s3Store }),\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n }\n\n const mappedConfig: Config = {\n ...config,\n admin: {\n ...(config.admin ?? {}),\n components: {\n ...(config.admin?.components ?? {}),\n providers: [\n '@maas/payload-plugin-media-cloud/components#UploadManagerProvider',\n ...(config.admin?.components?.providers ?? []),\n ],\n },\n },\n collections: [...(config.collections ?? []), mediaCollection],\n endpoints: [\n ...(config.endpoints ?? []),\n ...createTusEndpoints(tusServer),\n ...createMuxEndpoints({ getMuxClient, pluginOptions }),\n ],\n }\n\n return cloudStoragePlugin(cloudStorageConfig)(mappedConfig)\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { S3 } from "@aws-sdk/client-s3";
|
|
2
|
+
|
|
3
|
+
//#region src/tus/stores/s3/expiration-manager.d.ts
|
|
4
|
+
type GetExpirationDateArgs = {
|
|
5
|
+
createdAt: string;
|
|
6
|
+
};
|
|
7
|
+
declare class S3ExpirationManager {
|
|
8
|
+
private client;
|
|
9
|
+
private bucket;
|
|
10
|
+
private expirationPeriodInMilliseconds;
|
|
11
|
+
private generateInfoKey;
|
|
12
|
+
private generatePartKey;
|
|
13
|
+
constructor(client: S3, bucket: string, expirationPeriodInMilliseconds: number, generateInfoKey: (id: string) => string, generatePartKey: (id: string, isIncompletePart: boolean) => string);
|
|
14
|
+
/**
|
|
15
|
+
* Calculates the expiration date for a file based on a creation date
|
|
16
|
+
* @param args - The function arguments
|
|
17
|
+
* @param args.createdAt - The creation date as a string
|
|
18
|
+
* @returns The expiration date
|
|
19
|
+
*/
|
|
20
|
+
getExpirationDate(args: GetExpirationDateArgs): Date;
|
|
21
|
+
/**
|
|
22
|
+
* Gets the expiration period in milliseconds
|
|
23
|
+
* @returns The expiration period in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
getExpiration(): number;
|
|
26
|
+
/**
|
|
27
|
+
* Deletes expired incomplete uploads based on their initialization date.
|
|
28
|
+
* Returns the number of deleted uploads.
|
|
29
|
+
* @param args - The function arguments (empty object)
|
|
30
|
+
* @returns Promise that resolves to the number of deleted uploads
|
|
31
|
+
*/
|
|
32
|
+
deleteExpired(): Promise<number>;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { S3ExpirationManager };
|
|
36
|
+
//# sourceMappingURL=expiration-manager.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//#region src/tus/stores/s3/expiration-manager.ts
|
|
2
|
+
var S3ExpirationManager = class {
|
|
3
|
+
constructor(client, bucket, expirationPeriodInMilliseconds, generateInfoKey, generatePartKey) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
this.bucket = bucket;
|
|
6
|
+
this.expirationPeriodInMilliseconds = expirationPeriodInMilliseconds;
|
|
7
|
+
this.generateInfoKey = generateInfoKey;
|
|
8
|
+
this.generatePartKey = generatePartKey;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Calculates the expiration date for a file based on a creation date
|
|
12
|
+
* @param args - The function arguments
|
|
13
|
+
* @param args.createdAt - The creation date as a string
|
|
14
|
+
* @returns The expiration date
|
|
15
|
+
*/
|
|
16
|
+
getExpirationDate(args) {
|
|
17
|
+
const { createdAt } = args;
|
|
18
|
+
const date = new Date(createdAt);
|
|
19
|
+
return new Date(date.getTime() + this.expirationPeriodInMilliseconds);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Gets the expiration period in milliseconds
|
|
23
|
+
* @returns The expiration period in milliseconds
|
|
24
|
+
*/
|
|
25
|
+
getExpiration() {
|
|
26
|
+
return this.expirationPeriodInMilliseconds;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Deletes expired incomplete uploads based on their initialization date.
|
|
30
|
+
* Returns the number of deleted uploads.
|
|
31
|
+
* @param args - The function arguments (empty object)
|
|
32
|
+
* @returns Promise that resolves to the number of deleted uploads
|
|
33
|
+
*/
|
|
34
|
+
async deleteExpired() {
|
|
35
|
+
if (this.getExpiration() === 0) return 0;
|
|
36
|
+
let keyMarker = void 0;
|
|
37
|
+
let uploadIdMarker = void 0;
|
|
38
|
+
let isTruncated = true;
|
|
39
|
+
let deleted = 0;
|
|
40
|
+
while (isTruncated) {
|
|
41
|
+
const listResponse = await this.client.listMultipartUploads({
|
|
42
|
+
Bucket: this.bucket,
|
|
43
|
+
KeyMarker: keyMarker,
|
|
44
|
+
UploadIdMarker: uploadIdMarker
|
|
45
|
+
});
|
|
46
|
+
const expiredUploads = listResponse.Uploads?.filter((multiPartUpload) => {
|
|
47
|
+
const initiatedDate = multiPartUpload.Initiated;
|
|
48
|
+
return initiatedDate && (/* @__PURE__ */ new Date()).getTime() > this.getExpirationDate({ createdAt: initiatedDate.toISOString() }).getTime();
|
|
49
|
+
}) || [];
|
|
50
|
+
const objectsToDelete = expiredUploads.reduce((all, expiredUpload) => {
|
|
51
|
+
all.push({ Key: this.generateInfoKey(expiredUpload.Key) }, { Key: this.generatePartKey(expiredUpload.Key, true) });
|
|
52
|
+
return all;
|
|
53
|
+
}, []);
|
|
54
|
+
const deletions = [];
|
|
55
|
+
if (objectsToDelete.length > 0) deletions.push(this.client.deleteObjects({
|
|
56
|
+
Bucket: this.bucket,
|
|
57
|
+
Delete: { Objects: objectsToDelete }
|
|
58
|
+
}));
|
|
59
|
+
const abortions = expiredUploads.map((expiredUpload) => this.client.abortMultipartUpload({
|
|
60
|
+
Bucket: this.bucket,
|
|
61
|
+
Key: expiredUpload.Key,
|
|
62
|
+
UploadId: expiredUpload.UploadId
|
|
63
|
+
}));
|
|
64
|
+
await Promise.all([...deletions, ...abortions]);
|
|
65
|
+
deleted += expiredUploads.length;
|
|
66
|
+
isTruncated = listResponse.IsTruncated || false;
|
|
67
|
+
keyMarker = listResponse.NextKeyMarker;
|
|
68
|
+
uploadIdMarker = listResponse.NextUploadIdMarker;
|
|
69
|
+
}
|
|
70
|
+
return deleted;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { S3ExpirationManager };
|
|
76
|
+
//# sourceMappingURL=expiration-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiration-manager.js","names":["client: S3","bucket: string","expirationPeriodInMilliseconds: number","generateInfoKey: (id: string) => string","generatePartKey: (id: string, isIncompletePart: boolean) => string","args: GetExpirationDateArgs","keyMarker: string | undefined","uploadIdMarker: string | undefined","listResponse: AWS.ListMultipartUploadsCommandOutput","multiPartUpload: AWS.MultipartUpload","all: { Key: string }[]","expiredUpload: AWS.MultipartUpload","deletions: Promise<AWS.DeleteObjectsCommandOutput>[]"],"sources":["../../../../src/tus/stores/s3/expiration-manager.ts"],"sourcesContent":["import type AWS from '@aws-sdk/client-s3'\nimport type { S3 } from '@aws-sdk/client-s3'\n\ntype GetExpirationDateArgs = {\n createdAt: string\n}\n\nexport class S3ExpirationManager {\n constructor(\n private client: S3,\n private bucket: string,\n private expirationPeriodInMilliseconds: number,\n private generateInfoKey: (id: string) => string,\n private generatePartKey: (id: string, isIncompletePart: boolean) => string\n ) {}\n\n /**\n * Calculates the expiration date for a file based on a creation date\n * @param args - The function arguments\n * @param args.createdAt - The creation date as a string\n * @returns The expiration date\n */\n getExpirationDate(args: GetExpirationDateArgs): Date {\n const { createdAt } = args\n const date = new Date(createdAt)\n return new Date(date.getTime() + this.expirationPeriodInMilliseconds)\n }\n\n /**\n * Gets the expiration period in milliseconds\n * @returns The expiration period in milliseconds\n */\n getExpiration(): number {\n return this.expirationPeriodInMilliseconds\n }\n\n /**\n * Deletes expired incomplete uploads based on their initialization date.\n * Returns the number of deleted uploads.\n * @param args - The function arguments (empty object)\n * @returns Promise that resolves to the number of deleted uploads\n */\n async deleteExpired(): Promise<number> {\n // No arguments to destructure\n if (this.getExpiration() === 0) {\n return 0\n }\n\n let keyMarker: string | undefined = undefined\n let uploadIdMarker: string | undefined = undefined\n let isTruncated = true\n let deleted = 0\n\n while (isTruncated) {\n const listResponse: AWS.ListMultipartUploadsCommandOutput =\n await this.client.listMultipartUploads({\n Bucket: this.bucket,\n KeyMarker: keyMarker,\n UploadIdMarker: uploadIdMarker,\n })\n\n const expiredUploads =\n listResponse.Uploads?.filter((multiPartUpload: AWS.MultipartUpload) => {\n const initiatedDate = multiPartUpload.Initiated\n return (\n initiatedDate &&\n new Date().getTime() >\n this.getExpirationDate({\n createdAt: initiatedDate.toISOString(),\n }).getTime()\n )\n }) || []\n\n const objectsToDelete = expiredUploads.reduce(\n (all: { Key: string }[], expiredUpload: AWS.MultipartUpload) => {\n all.push(\n {\n Key: this.generateInfoKey(expiredUpload.Key as string),\n },\n {\n Key: this.generatePartKey(expiredUpload.Key as string, true),\n }\n )\n return all\n },\n [] as { Key: string }[]\n )\n\n const deletions: Promise<AWS.DeleteObjectsCommandOutput>[] = []\n\n if (objectsToDelete.length > 0) {\n deletions.push(\n this.client.deleteObjects({\n Bucket: this.bucket,\n Delete: {\n Objects: objectsToDelete,\n },\n })\n )\n }\n\n const abortions = expiredUploads.map(\n (expiredUpload: AWS.MultipartUpload) =>\n this.client.abortMultipartUpload({\n Bucket: this.bucket,\n Key: expiredUpload.Key,\n UploadId: expiredUpload.UploadId,\n })\n )\n\n await Promise.all([...deletions, ...abortions])\n\n deleted += expiredUploads.length\n\n isTruncated = listResponse.IsTruncated || false\n keyMarker = listResponse.NextKeyMarker\n uploadIdMarker = listResponse.NextUploadIdMarker\n }\n\n return deleted\n }\n}\n"],"mappings":";AAOA,IAAa,sBAAb,MAAiC;CAC/B,YACUA,QACAC,QACAC,gCACAC,iBACAC,iBACR;EALQ;EACA;EACA;EACA;EACA;CACN;;;;;;;CAQJ,kBAAkBC,MAAmC;EACnD,MAAM,EAAE,WAAW,GAAG;EACtB,MAAM,OAAO,IAAI,KAAK;AACtB,SAAO,IAAI,KAAK,KAAK,SAAS,GAAG,KAAK;CACvC;;;;;CAMD,gBAAwB;AACtB,SAAO,KAAK;CACb;;;;;;;CAQD,MAAM,gBAAiC;AAErC,MAAI,KAAK,eAAe,KAAK,EAC3B,QAAO;EAGT,IAAIC,YAAgC;EACpC,IAAIC,iBAAqC;EACzC,IAAI,cAAc;EAClB,IAAI,UAAU;AAEd,SAAO,aAAa;GAClB,MAAMC,eACJ,MAAM,KAAK,OAAO,qBAAqB;IACrC,QAAQ,KAAK;IACb,WAAW;IACX,gBAAgB;GACjB,EAAC;GAEJ,MAAM,iBACJ,aAAa,SAAS,OAAO,CAACC,oBAAyC;IACrE,MAAM,gBAAgB,gBAAgB;AACtC,WACE,kCACA,IAAI,QAAO,SAAS,GAClB,KAAK,kBAAkB,EACrB,WAAW,cAAc,aAAa,CACvC,EAAC,CAAC,SAAS;GAEjB,EAAC,IAAI,CAAE;GAEV,MAAM,kBAAkB,eAAe,OACrC,CAACC,KAAwBC,kBAAuC;IAC9D,IAAI,KACF,EACE,KAAK,KAAK,gBAAgB,cAAc,IAAc,CACvD,GACD,EACE,KAAK,KAAK,gBAAgB,cAAc,KAAe,KAAK,CAC7D,EACF;AACD,WAAO;GACR,GACD,CAAE,EACH;GAED,MAAMC,YAAuD,CAAE;AAE/D,OAAI,gBAAgB,SAAS,GAC3B,UAAU,KACR,KAAK,OAAO,cAAc;IACxB,QAAQ,KAAK;IACb,QAAQ,EACN,SAAS,gBACV;GACF,EAAC,CACH;GAGH,MAAM,YAAY,eAAe,IAC/B,CAACD,kBACC,KAAK,OAAO,qBAAqB;IAC/B,QAAQ,KAAK;IACb,KAAK,cAAc;IACnB,UAAU,cAAc;GACzB,EAAC,CACL;GAED,MAAM,QAAQ,IAAI,CAAC,GAAG,WAAW,GAAG,SAAU,EAAC;GAE/C,WAAW,eAAe;GAE1B,cAAc,aAAa,eAAe;GAC1C,YAAY,aAAa;GACzB,iBAAiB,aAAa;EAC/B;AAED,SAAO;CACR;AACF"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import AWS from "@aws-sdk/client-s3";
|
|
2
|
+
|
|
3
|
+
//#region src/tus/stores/s3/file-operations.d.ts
|
|
4
|
+
type CalculateOptimalPartSizeArgs = {
|
|
5
|
+
size?: number;
|
|
6
|
+
};
|
|
7
|
+
type CalculateOffsetFromPartsArgs = {
|
|
8
|
+
parts?: Array<AWS.Part>;
|
|
9
|
+
};
|
|
10
|
+
type CalculatePartNumberArgs = {
|
|
11
|
+
parts: Array<AWS.Part>;
|
|
12
|
+
};
|
|
13
|
+
type GenerateUniqueTmpFileNameArgs = {
|
|
14
|
+
template: string;
|
|
15
|
+
};
|
|
16
|
+
type CalculateOffsetFromPartsExportArgs = {
|
|
17
|
+
parts?: Array<{
|
|
18
|
+
Size?: number;
|
|
19
|
+
}>;
|
|
20
|
+
};
|
|
21
|
+
declare class S3FileOperations {
|
|
22
|
+
private maxMultipartParts;
|
|
23
|
+
private maxUploadSize;
|
|
24
|
+
private minPartSize;
|
|
25
|
+
private preferredPartSize;
|
|
26
|
+
constructor(maxMultipartParts: number, maxUploadSize: number, minPartSize: number, preferredPartSize: number);
|
|
27
|
+
/**
|
|
28
|
+
* Calculates the optimal part size for S3 multipart upload
|
|
29
|
+
* @param args - The function arguments
|
|
30
|
+
* @param args.size - The upload size in bytes (optional)
|
|
31
|
+
* @returns The optimal part size in bytes
|
|
32
|
+
*/
|
|
33
|
+
calculateOptimalPartSize(args: CalculateOptimalPartSizeArgs): number;
|
|
34
|
+
/**
|
|
35
|
+
* Calculates the offset based on uploaded parts
|
|
36
|
+
* @param args - The function arguments
|
|
37
|
+
* @param args.parts - Array of uploaded parts (optional)
|
|
38
|
+
* @returns The total offset in bytes
|
|
39
|
+
*/
|
|
40
|
+
calculateOffsetFromParts(args: CalculateOffsetFromPartsArgs): number;
|
|
41
|
+
/**
|
|
42
|
+
* Calculates the next part number based on existing parts
|
|
43
|
+
* @param args - The function arguments
|
|
44
|
+
* @param args.parts - Array of uploaded parts
|
|
45
|
+
* @returns The next part number to use
|
|
46
|
+
*/
|
|
47
|
+
calculatePartNumber(args: CalculatePartNumberArgs): number;
|
|
48
|
+
/**
|
|
49
|
+
* Generates a unique temporary file name
|
|
50
|
+
* @param args - The function arguments
|
|
51
|
+
* @param args.template - The template string for the file name
|
|
52
|
+
* @returns Promise that resolves to the unique file path
|
|
53
|
+
* @throws Error if unable to find unique name after max tries
|
|
54
|
+
*/
|
|
55
|
+
generateUniqueTmpFileName(args: GenerateUniqueTmpFileNameArgs): Promise<string>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Calculates the total offset from uploaded parts
|
|
59
|
+
* @param args - The function arguments
|
|
60
|
+
* @param args.parts - Array of parts with size information (optional)
|
|
61
|
+
* @returns The total offset in bytes
|
|
62
|
+
*/
|
|
63
|
+
declare function calculateOffsetFromParts(args: CalculateOffsetFromPartsExportArgs): number;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { S3FileOperations, calculateOffsetFromParts };
|
|
66
|
+
//# sourceMappingURL=file-operations.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { MediaCloudError } from "../../../types/errors.js";
|
|
2
|
+
import { useErrorHandler } from "../../../hooks/useErrorHandler.js";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/tus/stores/s3/file-operations.ts
|
|
9
|
+
const { throwError } = useErrorHandler();
|
|
10
|
+
var S3FileOperations = class {
|
|
11
|
+
constructor(maxMultipartParts, maxUploadSize, minPartSize, preferredPartSize) {
|
|
12
|
+
this.maxMultipartParts = maxMultipartParts;
|
|
13
|
+
this.maxUploadSize = maxUploadSize;
|
|
14
|
+
this.minPartSize = minPartSize;
|
|
15
|
+
this.preferredPartSize = preferredPartSize;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Calculates the optimal part size for S3 multipart upload
|
|
19
|
+
* @param args - The function arguments
|
|
20
|
+
* @param args.size - The upload size in bytes (optional)
|
|
21
|
+
* @returns The optimal part size in bytes
|
|
22
|
+
*/
|
|
23
|
+
calculateOptimalPartSize(args) {
|
|
24
|
+
const { size } = args;
|
|
25
|
+
let uploadSize = size;
|
|
26
|
+
if (uploadSize === void 0) uploadSize = this.maxUploadSize;
|
|
27
|
+
let optimalPartSize;
|
|
28
|
+
if (uploadSize <= this.preferredPartSize) optimalPartSize = uploadSize;
|
|
29
|
+
else if (uploadSize <= this.preferredPartSize * this.maxMultipartParts) optimalPartSize = this.preferredPartSize;
|
|
30
|
+
else optimalPartSize = Math.ceil(uploadSize / this.maxMultipartParts);
|
|
31
|
+
return Math.max(optimalPartSize, this.minPartSize);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Calculates the offset based on uploaded parts
|
|
35
|
+
* @param args - The function arguments
|
|
36
|
+
* @param args.parts - Array of uploaded parts (optional)
|
|
37
|
+
* @returns The total offset in bytes
|
|
38
|
+
*/
|
|
39
|
+
calculateOffsetFromParts(args) {
|
|
40
|
+
const { parts } = args;
|
|
41
|
+
return parts && parts.length > 0 ? parts.reduce((a, b) => a + (b.Size ?? 0), 0) : 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Calculates the next part number based on existing parts
|
|
45
|
+
* @param args - The function arguments
|
|
46
|
+
* @param args.parts - Array of uploaded parts
|
|
47
|
+
* @returns The next part number to use
|
|
48
|
+
*/
|
|
49
|
+
calculatePartNumber(args) {
|
|
50
|
+
const { parts } = args;
|
|
51
|
+
return parts.length > 0 ? parts[parts.length - 1].PartNumber + 1 : 1;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generates a unique temporary file name
|
|
55
|
+
* @param args - The function arguments
|
|
56
|
+
* @param args.template - The template string for the file name
|
|
57
|
+
* @returns Promise that resolves to the unique file path
|
|
58
|
+
* @throws Error if unable to find unique name after max tries
|
|
59
|
+
*/
|
|
60
|
+
async generateUniqueTmpFileName(args) {
|
|
61
|
+
const { template } = args;
|
|
62
|
+
const tries = 5;
|
|
63
|
+
for (let i = 0; i < tries; i++) {
|
|
64
|
+
const randomId = crypto.randomBytes(16).toString("hex");
|
|
65
|
+
const filePath = path.join(os.tmpdir(), `${template}${randomId}`);
|
|
66
|
+
try {
|
|
67
|
+
await fs.promises.access(filePath, fs.constants.F_OK);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const nodeError = error;
|
|
70
|
+
if (nodeError.code === "ENOENT") return filePath;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throwError(MediaCloudError.S3_UNIQUE_NAME_ERROR);
|
|
74
|
+
throw new Error();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Calculates the total offset from uploaded parts
|
|
79
|
+
* @param args - The function arguments
|
|
80
|
+
* @param args.parts - Array of parts with size information (optional)
|
|
81
|
+
* @returns The total offset in bytes
|
|
82
|
+
*/
|
|
83
|
+
function calculateOffsetFromParts(args) {
|
|
84
|
+
const { parts } = args;
|
|
85
|
+
return parts && parts.length > 0 ? parts.reduce((a, b) => a + (b.Size ?? 0), 0) : 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { S3FileOperations, calculateOffsetFromParts };
|
|
90
|
+
//# sourceMappingURL=file-operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-operations.js","names":["maxMultipartParts: number","maxUploadSize: number","minPartSize: number","preferredPartSize: number","args: CalculateOptimalPartSizeArgs","optimalPartSize: number","args: CalculateOffsetFromPartsArgs","args: CalculatePartNumberArgs","args: GenerateUniqueTmpFileNameArgs","args: CalculateOffsetFromPartsExportArgs"],"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 { MediaCloudError } 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 we assume largest possible value (`maxUploadSize`)\n let uploadSize = size\n if (uploadSize === undefined) {\n uploadSize = this.maxUploadSize\n }\n\n let optimalPartSize: number\n\n // When upload is smaller or equal to PreferredPartSize, we 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(MediaCloudError.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,YAAY,GAAG,iBAAiB;AAExC,IAAa,mBAAb,MAA8B;CAC5B,YACUA,mBACAC,eACAC,aACAC,mBACR;EAJQ;EACA;EACA;EACA;CACN;;;;;;;CAQJ,yBAAyBC,MAA4C;EACnE,MAAM,EAAE,MAAM,GAAG;EAEjB,IAAI,aAAa;AACjB,MAAI,eAAe,QACjB,aAAa,KAAK;EAGpB,IAAIC;AAGJ,MAAI,cAAc,KAAK,mBACrB,kBAAkB;WAGX,cAAc,KAAK,oBAAoB,KAAK,mBACnD,kBAAkB,KAAK;OAIvB,kBAAkB,KAAK,KAAK,aAAa,KAAK,kBAAkB;AAIlE,SAAO,KAAK,IAAI,iBAAiB,KAAK,YAAY;CACnD;;;;;;;CAQD,yBAAyBC,MAA4C;EACnE,MAAM,EAAE,OAAO,GAAG;AAClB,SAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C;CACL;;;;;;;CAQD,oBAAoBC,MAAuC;EACzD,MAAM,EAAE,OAAO,GAAG;AAClB,SAAO,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,GAAG,aAAc,IAAI;CACrE;;;;;;;;CASD,MAAM,0BACJC,MACiB;EACjB,MAAM,EAAE,UAAU,GAAG;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,UAAU,CAAC;AACjE,OAAI;IACF,MAAM,GAAG,SAAS,OAAO,UAAU,GAAG,UAAU,KAAK;GACtD,SAAQ,OAAO;IACd,MAAM,YAAY;AAClB,QAAI,UAAU,SAAS,SACrB,QAAO;GAEV;EACF;EACD,WAAW,gBAAgB,qBAAqB;AAChD,QAAM,IAAI;CACX;AACF;;;;;;;AAQD,SAAgB,yBACdC,MACA;CACA,MAAM,EAAE,OAAO,GAAG;AAClB,QAAO,SAAS,MAAM,SAAS,IAC3B,MAAM,OAAO,CAAC,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,EAAE,GAC5C;AACL"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","names":["message: string"],"sources":["../../../../src/tus/stores/s3/log.ts"],"sourcesContent":["export function log(message: string, ...args: unknown[]) {\n console.log(`tus:stores:s3store - ${message}`, ...args)\n}\n"],"mappings":";AAAA,SAAgB,IAAIA,SAAiB,GAAG,MAAiB;CACvD,QAAQ,IAAI,CAAC,qBAAqB,EAAE,SAAS,EAAE,GAAG,KAAK;AACxD"}
|