@maas/payload-plugin-media-cloud 0.0.29 → 0.0.30
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/adapter/handleUpload.d.mts +0 -1
- package/dist/adapter/handleUpload.mjs +7 -12
- package/dist/adapter/handleUpload.mjs.map +1 -1
- package/dist/collections/mediaCollection.mjs +69 -2
- package/dist/collections/mediaCollection.mjs.map +1 -1
- package/dist/components/upload-handler/upload-handler.d.mts +3 -3
- package/dist/components/upload-handler/upload-handler.mjs +6 -3
- package/dist/components/upload-handler/upload-handler.mjs.map +1 -1
- package/dist/endpoints/tusCleanupHandler.mjs +1 -1
- package/dist/endpoints/tusCleanupHandler.mjs.map +1 -1
- package/dist/utils/tus.d.mts +3 -3
- package/package.json +2 -2
|
@@ -4,7 +4,6 @@ import { HandleUpload } from "@payloadcms/plugin-cloud-storage/types";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a handle upload function for processing file uploads to both Mux and S3 storage
|
|
7
|
-
* Since PayloadCMS 3.70.0, this runs in afterChange hook, so we need to update the document
|
|
8
7
|
* @returns A HandleUpload function that processes client upload context and updates document data
|
|
9
8
|
*/
|
|
10
9
|
declare function getHandleUpload(): HandleUpload;
|
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
//#region src/adapter/handleUpload.ts
|
|
2
2
|
/**
|
|
3
3
|
* Creates a handle upload function for processing file uploads to both Mux and S3 storage
|
|
4
|
-
* Since PayloadCMS 3.70.0, this runs in afterChange hook, so we need to update the document
|
|
5
4
|
* @returns A HandleUpload function that processes client upload context and updates document data
|
|
6
5
|
*/
|
|
7
6
|
function getHandleUpload() {
|
|
8
|
-
return async function({ clientUploadContext, data
|
|
9
|
-
if (!clientUploadContext) return;
|
|
7
|
+
return async function({ clientUploadContext, data }) {
|
|
8
|
+
if (!clientUploadContext) return data;
|
|
10
9
|
const ctx = clientUploadContext;
|
|
11
|
-
|
|
10
|
+
data.mimeType = ctx.mimeType;
|
|
12
11
|
switch (ctx.storage) {
|
|
13
12
|
case "mux":
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
data.storage = "mux";
|
|
14
|
+
data.mux = {
|
|
16
15
|
uploadId: ctx.uploadId ?? "",
|
|
17
16
|
status: "preparing"
|
|
18
17
|
};
|
|
19
18
|
break;
|
|
20
19
|
default:
|
|
21
|
-
|
|
20
|
+
data.storage = "s3";
|
|
22
21
|
break;
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
collection: collection.slug,
|
|
26
|
-
id: data.id,
|
|
27
|
-
data: updateData
|
|
28
|
-
});
|
|
23
|
+
return data;
|
|
29
24
|
};
|
|
30
25
|
}
|
|
31
26
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleUpload.mjs","names":[
|
|
1
|
+
{"version":3,"file":"handleUpload.mjs","names":[],"sources":["../../src/adapter/handleUpload.ts"],"sourcesContent":["import type { HandleUpload } from '@payloadcms/plugin-cloud-storage/types'\n\ninterface ClientUploadContext {\n storage: 'mux' | 's3'\n uploadId?: string\n mimeType?: string\n}\n\n/**\n * Creates a handle upload function for processing file uploads to both Mux and S3 storage\n * @returns A HandleUpload function that processes client upload context and updates document data\n */\nexport function getHandleUpload(): HandleUpload {\n return async function ({ clientUploadContext, data }) {\n if (!clientUploadContext) {\n return data\n }\n\n const ctx = clientUploadContext as ClientUploadContext\n data.mimeType = ctx.mimeType\n\n switch (ctx.storage) {\n case 'mux':\n data.storage = 'mux'\n data.mux = {\n uploadId: ctx.uploadId ?? '',\n status: 'preparing',\n }\n break\n default:\n data.storage = 's3'\n break\n }\n\n return data\n }\n}\n"],"mappings":";;;;;AAYA,SAAgB,kBAAgC;AAC9C,QAAO,eAAgB,EAAE,qBAAqB,QAAQ;AACpD,MAAI,CAAC,oBACH,QAAO;EAGT,MAAM,MAAM;AACZ,OAAK,WAAW,IAAI;AAEpB,UAAQ,IAAI,SAAZ;GACE,KAAK;AACH,SAAK,UAAU;AACf,SAAK,MAAM;KACT,UAAU,IAAI,YAAY;KAC1B,QAAQ;KACT;AACD;GACF;AACE,SAAK,UAAU;AACf;;AAGJ,SAAO"}
|
|
@@ -135,7 +135,39 @@ function getMediaCollection(args) {
|
|
|
135
135
|
{
|
|
136
136
|
name: "tracks",
|
|
137
137
|
label: "Tracks",
|
|
138
|
-
type: "json"
|
|
138
|
+
type: "json",
|
|
139
|
+
typescriptSchema: [({ jsonSchema }) => ({
|
|
140
|
+
...jsonSchema,
|
|
141
|
+
type: "array",
|
|
142
|
+
items: [{
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
type: {
|
|
146
|
+
type: "string",
|
|
147
|
+
enum: ["audio", "video"]
|
|
148
|
+
},
|
|
149
|
+
id: { type: "string" },
|
|
150
|
+
duration: { type: "number" },
|
|
151
|
+
primary: { type: "boolean" },
|
|
152
|
+
max_channels: { type: "number" },
|
|
153
|
+
max_channel_layout: { type: "string" },
|
|
154
|
+
max_width: { type: "number" },
|
|
155
|
+
max_height: { type: "number" },
|
|
156
|
+
max_frame_rate: { type: "number" }
|
|
157
|
+
},
|
|
158
|
+
required: [
|
|
159
|
+
"type",
|
|
160
|
+
"id",
|
|
161
|
+
"duration",
|
|
162
|
+
"primary",
|
|
163
|
+
"max_channels",
|
|
164
|
+
"max_channel_layout",
|
|
165
|
+
"max_width",
|
|
166
|
+
"max_height",
|
|
167
|
+
"max_frame_rate"
|
|
168
|
+
]
|
|
169
|
+
}]
|
|
170
|
+
})]
|
|
139
171
|
},
|
|
140
172
|
{
|
|
141
173
|
name: "maxResolutionTier",
|
|
@@ -150,7 +182,42 @@ function getMediaCollection(args) {
|
|
|
150
182
|
{
|
|
151
183
|
name: "staticRenditions",
|
|
152
184
|
label: "Static Renditions",
|
|
153
|
-
type: "json"
|
|
185
|
+
type: "json",
|
|
186
|
+
typescriptSchema: [({ jsonSchema }) => ({
|
|
187
|
+
...jsonSchema,
|
|
188
|
+
type: "object",
|
|
189
|
+
properties: { files: {
|
|
190
|
+
type: "array",
|
|
191
|
+
items: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
type: { type: "string" },
|
|
195
|
+
status: { type: "string" },
|
|
196
|
+
resolution: { type: "string" },
|
|
197
|
+
name: { type: "string" },
|
|
198
|
+
id: { type: "string" },
|
|
199
|
+
ext: { type: "string" },
|
|
200
|
+
width: { type: "number" },
|
|
201
|
+
height: { type: "number" },
|
|
202
|
+
filesize: { type: "string" },
|
|
203
|
+
bitrate: { type: "number" }
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
required: [
|
|
207
|
+
"type",
|
|
208
|
+
"status",
|
|
209
|
+
"resolution",
|
|
210
|
+
"name",
|
|
211
|
+
"id",
|
|
212
|
+
"ext",
|
|
213
|
+
"width",
|
|
214
|
+
"height",
|
|
215
|
+
"filesize",
|
|
216
|
+
"bitrate"
|
|
217
|
+
]
|
|
218
|
+
} },
|
|
219
|
+
required: ["files"]
|
|
220
|
+
})]
|
|
154
221
|
}
|
|
155
222
|
]
|
|
156
223
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { S3Store } from './../tus/stores/s3/s3-store'\n\ninterface GetMediaCollectionArgs {\n s3Store: S3Store\n baseCollection?: CollectionConfig\n}\n\n/**\n * Creates a media collection configuration for Payload CMS\n * @param args - Arguments including the S3Store instance\n * @returns A configured Payload collection for media files\n */\nexport function getMediaCollection(\n args: GetMediaCollectionArgs\n): CollectionConfig {\n const { s3Store, baseCollection } = args\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\n group: 'Media Cloud',\n pagination: {\n defaultLimit: 50,\n },\n },\n upload: {\n crop: false,\n displayPreview: true,\n hideRemoveFile: true,\n adminThumbnail({ doc }: { doc: Document }) {\n if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=200&height=200&fit_mode=pad`\n } else if (doc?.storage === 's3') {\n // @TODO: Make configurable\n const url = s3Store.getUrl(doc.filename)\n return `https://wsrv.nl/?url=${url}?width=200&height=200&default=1`\n }\n return null\n },\n },\n fields: [\n {\n name: 'alt',\n label: 'Alternative Text',\n type: 'text',\n },\n {\n name: 'caption',\n label: 'Caption',\n type: 'text',\n },\n {\n name: 'copyright',\n label: 'Copyright',\n type: 'text',\n },\n {\n type: 'row',\n fields: [\n {\n name: 'width',\n label: 'Width',\n type: 'text',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n {\n name: 'height',\n label: 'Height',\n type: 'number',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n ],\n },\n {\n name: 'storage',\n label: 'Storage',\n type: 'select',\n options: [\n {\n label: 'Mux',\n value: 'mux',\n },\n {\n label: 'S3',\n value: 's3',\n },\n ],\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n condition: (data) => {\n return data.storage === 'mux'\n },\n disableListColumn: true,\n components: {\n Field: '@maas/payload-plugin-media-cloud/components#MuxPreview',\n },\n },\n },\n {\n name: 'status',\n label: 'Status',\n type: 'text',\n },\n {\n name: 'uploadId',\n label: 'Upload ID',\n type: 'text',\n },\n {\n name: 'assetId',\n label: 'Asset ID',\n type: 'text',\n },\n {\n name: 'playbackId',\n label: 'Playback ID',\n type: 'text',\n },\n {\n name: 'aspectRatio',\n label: 'Aspect Ratio',\n type: 'text',\n },\n {\n name: 'duration',\n label: 'Duration',\n type: 'number',\n },\n {\n name: 'tracks',\n label: 'Tracks',\n type: 'json',\n },\n {\n name: 'maxResolutionTier',\n label: 'Max Resolution Tier',\n type: 'text',\n },\n {\n name: 'videoQuality',\n label: 'Video Quality',\n type: 'text',\n },\n {\n name: 'staticRenditions',\n label: 'Static Renditions',\n type: 'json',\n },\n ],\n },\n ],\n }\n\n // Simple merge: if baseCollection exists, spread it first, then our config overrides\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n ...config,\n fields: [...(baseCollection.fields || []), ...config.fields],\n }\n}\n"],"mappings":";;;;;;AAcA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,SAAS,mBAAmB;CAEpC,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,QAAI,KAAK,YAAY,SAAS,KAAK,KAAK,WACtC,QAAO,yBAAyB,IAAI,IAAI,WAAW;aAC1C,KAAK,YAAY,KAG1B,QAAO,wBADK,QAAQ,OAAO,IAAI,SAAS,CACL;AAErC,WAAO;;GAEV;EACD,QAAQ;GACN;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,QAAQ,CACN;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,EACD;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,CACF;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,SAAS,CACP;KACE,OAAO;KACP,OAAO;KACR,EACD;KACE,OAAO;KACP,OAAO;KACR,CACF;IACD,OAAO,EACL,UAAU,MACX;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;KACL,mBAAmB;KACnB,iBAAiB;KACjB,mBAAmB;KACnB,UAAU;KACV,YAAY,SAAS;AACnB,aAAO,KAAK,YAAY;;KAE3B;IACD,QAAQ;KACN;MACE,MAAM;MACN,MAAM;MACN,OAAO;OACL,YAAY,SAAS;AACnB,eAAO,KAAK,YAAY;;OAE1B,mBAAmB;OACnB,YAAY,EACV,OAAO,0DACR;OACF;MACF;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACF;IACF;GACF;EACF;AAGD,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,GAAG;EACH,QAAQ,CAAC,GAAI,eAAe,UAAU,EAAE,EAAG,GAAG,OAAO,OAAO;EAC7D"}
|
|
1
|
+
{"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { S3Store } from './../tus/stores/s3/s3-store'\n\ninterface GetMediaCollectionArgs {\n s3Store: S3Store\n baseCollection?: CollectionConfig\n}\n\n/**\n * Creates a media collection configuration for Payload CMS\n * @param args - Arguments including the S3Store instance\n * @returns A configured Payload collection for media files\n */\nexport function getMediaCollection(\n args: GetMediaCollectionArgs\n): CollectionConfig {\n const { s3Store, baseCollection } = args\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\n group: 'Media Cloud',\n pagination: {\n defaultLimit: 50,\n },\n },\n upload: {\n crop: false,\n displayPreview: true,\n hideRemoveFile: true,\n adminThumbnail({ doc }: { doc: Document }) {\n if (doc?.storage === 'mux' && doc?.mux?.playbackId) {\n return `https://image.mux.com/${doc.mux.playbackId}/thumbnail.jpg?width=200&height=200&fit_mode=pad`\n } else if (doc?.storage === 's3') {\n // @TODO: Make configurable\n const url = s3Store.getUrl(doc.filename)\n return `https://wsrv.nl/?url=${url}?width=200&height=200&default=1`\n }\n return null\n },\n },\n fields: [\n {\n name: 'alt',\n label: 'Alternative Text',\n type: 'text',\n },\n {\n name: 'caption',\n label: 'Caption',\n type: 'text',\n },\n {\n name: 'copyright',\n label: 'Copyright',\n type: 'text',\n },\n {\n type: 'row',\n fields: [\n {\n name: 'width',\n label: 'Width',\n type: 'text',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n {\n name: 'height',\n label: 'Height',\n type: 'number',\n admin: {\n readOnly: true,\n hidden: false,\n width: '50%',\n },\n },\n ],\n },\n {\n name: 'storage',\n label: 'Storage',\n type: 'select',\n options: [\n {\n label: 'Mux',\n value: 'mux',\n },\n {\n label: 'S3',\n value: 's3',\n },\n ],\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'mux',\n label: 'Mux',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableBulkEdit: true,\n disableListFilter: true,\n readOnly: true,\n condition: (data) => {\n return data.storage === 'mux'\n },\n },\n fields: [\n {\n name: 'preview',\n type: 'ui',\n admin: {\n condition: (data) => {\n return data.storage === 'mux'\n },\n disableListColumn: true,\n components: {\n Field: '@maas/payload-plugin-media-cloud/components#MuxPreview',\n },\n },\n },\n {\n name: 'status',\n label: 'Status',\n type: 'text',\n },\n {\n name: 'uploadId',\n label: 'Upload ID',\n type: 'text',\n },\n {\n name: 'assetId',\n label: 'Asset ID',\n type: 'text',\n },\n {\n name: 'playbackId',\n label: 'Playback ID',\n type: 'text',\n },\n {\n name: 'aspectRatio',\n label: 'Aspect Ratio',\n type: 'text',\n },\n {\n name: 'duration',\n label: 'Duration',\n type: 'number',\n },\n {\n name: 'tracks',\n label: 'Tracks',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'array',\n items: [\n {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n enum: ['audio', 'video'],\n },\n id: {\n type: 'string',\n },\n duration: {\n type: 'number',\n },\n primary: {\n type: 'boolean',\n },\n max_channels: {\n type: 'number',\n },\n max_channel_layout: {\n type: 'string',\n },\n max_width: {\n type: 'number',\n },\n max_height: {\n type: 'number',\n },\n max_frame_rate: {\n type: 'number',\n },\n },\n required: [\n 'type',\n 'id',\n 'duration',\n 'primary',\n 'max_channels',\n 'max_channel_layout',\n 'max_width',\n 'max_height',\n 'max_frame_rate',\n ],\n },\n ],\n }),\n ],\n },\n {\n name: 'maxResolutionTier',\n label: 'Max Resolution Tier',\n type: 'text',\n },\n {\n name: 'videoQuality',\n label: 'Video Quality',\n type: 'text',\n },\n {\n name: 'staticRenditions',\n label: 'Static Renditions',\n type: 'json',\n typescriptSchema: [\n ({ jsonSchema }) => ({\n ...jsonSchema,\n type: 'object',\n properties: {\n files: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n type: {\n type: 'string',\n },\n status: {\n type: 'string',\n },\n resolution: {\n type: 'string',\n },\n name: {\n type: 'string',\n },\n id: {\n type: 'string',\n },\n ext: {\n type: 'string',\n },\n width: {\n type: 'number',\n },\n height: {\n type: 'number',\n },\n filesize: {\n type: 'string',\n },\n bitrate: {\n type: 'number',\n },\n },\n },\n required: [\n 'type',\n 'status',\n 'resolution',\n 'name',\n 'id',\n 'ext',\n 'width',\n 'height',\n 'filesize',\n 'bitrate',\n ],\n },\n },\n required: ['files'],\n }),\n ],\n },\n ],\n },\n ],\n }\n\n // Simple merge: if baseCollection exists, spread it first, then our config overrides\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n ...config,\n fields: [...(baseCollection.fields || []), ...config.fields],\n }\n}\n"],"mappings":";;;;;;AAcA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,SAAS,mBAAmB;CAEpC,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,QAAI,KAAK,YAAY,SAAS,KAAK,KAAK,WACtC,QAAO,yBAAyB,IAAI,IAAI,WAAW;aAC1C,KAAK,YAAY,KAG1B,QAAO,wBADK,QAAQ,OAAO,IAAI,SAAS,CACL;AAErC,WAAO;;GAEV;EACD,QAAQ;GACN;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACP;GACD;IACE,MAAM;IACN,QAAQ,CACN;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,EACD;KACE,MAAM;KACN,OAAO;KACP,MAAM;KACN,OAAO;MACL,UAAU;MACV,QAAQ;MACR,OAAO;MACR;KACF,CACF;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,SAAS,CACP;KACE,OAAO;KACP,OAAO;KACR,EACD;KACE,OAAO;KACP,OAAO;KACR,CACF;IACD,OAAO,EACL,UAAU,MACX;IACF;GACD;IACE,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;KACL,mBAAmB;KACnB,iBAAiB;KACjB,mBAAmB;KACnB,UAAU;KACV,YAAY,SAAS;AACnB,aAAO,KAAK,YAAY;;KAE3B;IACD,QAAQ;KACN;MACE,MAAM;MACN,MAAM;MACN,OAAO;OACL,YAAY,SAAS;AACnB,eAAO,KAAK,YAAY;;OAE1B,mBAAmB;OACnB,YAAY,EACV,OAAO,0DACR;OACF;MACF;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACN,kBAAkB,EACf,EAAE,kBAAkB;OACnB,GAAG;OACH,MAAM;OACN,OAAO,CACL;QACE,MAAM;QACN,YAAY;SACV,MAAM;UACJ,MAAM;UACN,MAAM,CAAC,SAAS,QAAQ;UACzB;SACD,IAAI,EACF,MAAM,UACP;SACD,UAAU,EACR,MAAM,UACP;SACD,SAAS,EACP,MAAM,WACP;SACD,cAAc,EACZ,MAAM,UACP;SACD,oBAAoB,EAClB,MAAM,UACP;SACD,WAAW,EACT,MAAM,UACP;SACD,YAAY,EACV,MAAM,UACP;SACD,gBAAgB,EACd,MAAM,UACP;SACF;QACD,UAAU;SACR;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACD;QACF,CACF;OACF,EACF;MACF;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACP;KACD;MACE,MAAM;MACN,OAAO;MACP,MAAM;MACN,kBAAkB,EACf,EAAE,kBAAkB;OACnB,GAAG;OACH,MAAM;OACN,YAAY,EACV,OAAO;QACL,MAAM;QACN,OAAO;SACL,MAAM;SACN,YAAY;UACV,MAAM,EACJ,MAAM,UACP;UACD,QAAQ,EACN,MAAM,UACP;UACD,YAAY,EACV,MAAM,UACP;UACD,MAAM,EACJ,MAAM,UACP;UACD,IAAI,EACF,MAAM,UACP;UACD,KAAK,EACH,MAAM,UACP;UACD,OAAO,EACL,MAAM,UACP;UACD,QAAQ,EACN,MAAM,UACP;UACD,UAAU,EACR,MAAM,UACP;UACD,SAAS,EACP,MAAM,UACP;UACF;SACF;QACD,UAAU;SACR;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACA;SACD;QACF,EACF;OACD,UAAU,CAAC,QAAQ;OACpB,EACF;MACF;KACF;IACF;GACF;EACF;AAGD,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,GAAG;EACH,QAAQ,CAAC,GAAI,eAAe,UAAU,EAAE,EAAG,GAAG,OAAO,OAAO;EAC7D"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react0 from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as payload0 from "payload";
|
|
3
3
|
|
|
4
4
|
//#region src/components/upload-handler/upload-handler.d.ts
|
|
5
5
|
declare const UploadHandler: ({
|
|
@@ -11,11 +11,11 @@ declare const UploadHandler: ({
|
|
|
11
11
|
serverHandlerPath
|
|
12
12
|
}: {
|
|
13
13
|
children: react0.ReactNode;
|
|
14
|
-
collectionSlug:
|
|
14
|
+
collectionSlug: payload0.UploadCollectionSlug;
|
|
15
15
|
enabled?: boolean;
|
|
16
16
|
extra: Record<string, unknown>;
|
|
17
17
|
prefix?: string;
|
|
18
|
-
serverHandlerPath:
|
|
18
|
+
serverHandlerPath: string;
|
|
19
19
|
}) => react0.JSX.Element;
|
|
20
20
|
//#endregion
|
|
21
21
|
export { UploadHandler };
|
|
@@ -87,6 +87,7 @@ async function tusUpload(args) {
|
|
|
87
87
|
const filesize = file.size.toString();
|
|
88
88
|
Object.keys(localStorage).filter((key) => key.startsWith("tus::")).forEach((key) => localStorage.removeItem(key));
|
|
89
89
|
let uploadUrlAvailable = false;
|
|
90
|
+
let eventListenerAdded = false;
|
|
90
91
|
function onBeforeUnload(e) {
|
|
91
92
|
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
92
93
|
e.preventDefault();
|
|
@@ -118,8 +119,11 @@ async function tusUpload(args) {
|
|
|
118
119
|
resolve(null);
|
|
119
120
|
},
|
|
120
121
|
onBeforeRequest() {
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
if (!eventListenerAdded) {
|
|
123
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
124
|
+
window.addEventListener("unload", onPageUnload);
|
|
125
|
+
eventListenerAdded = true;
|
|
126
|
+
}
|
|
123
127
|
},
|
|
124
128
|
onProgress: function(bytesUploaded, bytesTotal) {
|
|
125
129
|
const percentage = Math.round(bytesUploaded / bytesTotal * 100);
|
|
@@ -150,7 +154,6 @@ async function tusUpload(args) {
|
|
|
150
154
|
const { log } = useErrorHandler();
|
|
151
155
|
const UploadHandler = createClientUploadHandler({ handler: async function(args) {
|
|
152
156
|
const { serverURL, apiRoute, file, updateFilename } = args;
|
|
153
|
-
console.log("upload handler called", file);
|
|
154
157
|
try {
|
|
155
158
|
const mimeType = await getFileType(file) || file.type || "application/octet-stream";
|
|
156
159
|
if (!mimeType) {
|
|
@@ -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 // 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 emitter.emit('removeUpload', { filename })\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 ).catch(() => {})\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 emitter.emit('removeUpload', { filename })\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 eventListenerAdded = true\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 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 ).catch(() => {})\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":";;;;;;;;;;;;AAiCA,MAAM,EAAE,UAAU,eAAe,iBAAiB;AAClD,MAAM,UAAU,sBAAsB;AAEtC,MAAM,iBAAiB;AACvB,MAAM,iBAAiB,OAAO;AAC9B,MAAM,mBAAmB;CAAC;CAAG;CAAM;CAAM;CAAK;;;;;;AAO9C,eAAe,UAAU,MAAgD;CACvE,MAAM,EAAE,MAAM,WAAW,UAAU,aAAa;CAEhD,MAAM,WAAW,KAAK;AACtB,KAAI;EAWF,MAAM,EAAE,KAAK,aAAc,OATV,MAAM,MAAM,GAAG,YAAY,SAAS,cAAc;GACjE,MAAM,KAAK,UAAU;IAAE;IAAU;IAAU,CAAC;GAC5C,aAAa;GACb,QAAQ;GACR,SAAS,EACP,gBAAgB,oBACjB;GACF,CAAC,EAEwC,MAAM;EAGhD,MAAM,WAAW,MAAM,QAAQ,aAAa;GAC1C,UAAU;GACV;GACA,WAAW;GACZ,CAAC;AAGF,UAAQ,KAAK,aAAa;GACxB;GACA;GACA,SAAS;GACT,YAAY,GAAG,YAAY,SAAS;GACrC,CAAC;AAGF,WAAS,GAAG,SAAS,WAAY;AAC/B,YAAS,iBAAiB,iBAAiB,QAAQ;AACnD,SAAM,MAAM,sBAAsB;AAClC,WAAQ,KAAK,gBAAgB,EAAE,UAAU,CAAC;IAC1C;AAEF,WAAS,GAAG,YAAY,SAAU,UAAU;AAC1C,WAAQ,KAAK,gBAAgB;IAC3B;IACA,UAAU,SAAS;IACpB,CAAC;IACF;AAEF,WAAS,GAAG,WAAW,WAAY;AACjC,WAAQ,KAAK,kBAAkB,EAAE,UAAU,CAAC;IAC5C;AAIF,SAAO;GACL;GACA;GACA;GACA,SAAS;GACV;UACM,QAAQ;AACf,WAAS,iBAAiB,wBAAwB,QAAQ;AAC1D,QAAM,MAAM,sBAAsB;AAClC,SAAO;;;;;;;;AASX,eAAe,UAAU,MAAgD;CACvE,MAAM,EAAE,UAAU,WAAW,MAAM,aAAa;CAEhD,MAAM,WAAW,KAAK;CACtB,MAAM,WAAW,KAAK;CACtB,MAAM,WAAW,KAAK,KAAK,UAAU;AAGrC,QAAO,KAAK,aAAa,CACtB,QAAQ,QAAQ,IAAI,WAAW,QAAQ,CAAC,CACxC,SAAS,QAAQ,aAAa,WAAW,IAAI,CAAC;CAGjD,IAAI,qBAAqB;CACzB,IAAI,qBAAqB;CAEzB,SAAS,eAAe,GAAsB;AAC5C,SAAO,oBAAoB,gBAAgB,eAAe;AAC1D,IAAE,gBAAgB;;CAGpB,SAAS,eAAe;AACtB,SAAO,oBAAoB,UAAU,aAAa;AAClD,YAAU,WACR,GAAG,YAAY,SAAS,mBACxB,KAAK,UAAU,EAAE,UAAU,CAAC,CAC7B;;AAGH,QAAO,IAAI,SAAS,YAAY;AA4E9B,EA3Ee,IAAI,IAAI,OAAO,MAAM;GAClC,UAAU,GAAG,YAAY,SAAS;GAClC,aAAa;GACb,WAAW;GACX,6BAA6B;GAC7B,UAAU;IACR;IACA;IACA;IACA,aAAa;IACb,eAAe;IACf,oBAAoB;IACrB;GACD,QAAQ,OAAO;AACb,aAAS,iBAAiB,iBAAiB,QAAQ;AACnD,aAAS,MAAM,QAAQ;AAGvB,cAAU,WACR,GAAG,YAAY,SAAS,mBACxB,KAAK,UAAU,EAAE,UAAU,CAAC,CAC7B;AAGD,YAAQ,KAAK,gBAAgB,EAAE,UAAU,CAAC;AAC1C,UAAM,MAAM,qBAAqB;AACjC,YAAQ,KAAK;;GAEf,kBAAkB;AAChB,QAAI,CAAC,oBAAoB;AACvB,YAAO,iBAAiB,gBAAgB,eAAe;AACvD,YAAO,iBAAiB,UAAU,aAAa;AAC/C,0BAAqB;;;GAGzB,YAAY,SAAU,eAAe,YAAY;IAC/C,MAAM,aAAa,KAAK,MAAO,gBAAgB,aAAc,IAAI;AACjE,YAAQ,KAAK,gBAAgB;KAC3B;KACA,UAAU;KACX,CAAC;;GAEJ,YAAY;AAEV,WAAO,oBAAoB,gBAAgB,eAAe;AAC1D,WAAO,oBAAoB,UAAU,aAAa;AAGlD,YAAQ,KAAK,kBAAkB,EAAE,UAAU,CAAC;AAG5C,UAAM,GAAG,YAAY,SAAS,WAAW,SAAS,UAAU;;GAE9D,sBAAsB,iBAAkB;AAEtC,QAAI,mBACF;AAIF,yBAAqB;AAGrB,YAAQ,KAAK,aAAa,EAAE,UAAU,CAAC;AAIvC,YAAQ;KACN;KACA;KACA,SAAS;KACV,CAAC;;GAEL,CAAC,CAEK,OAAO;GACd;;AAGJ,MAAM,EAAE,QAAQ,iBAAiB;AAEjC,MAAa,gBAAgB,0BAA0B,EACrD,SAAS,eAAgB,MAAM;CAC7B,MAAM,EAAE,WAAW,UAAU,MAAM,mBAAmB;AACtD,KAAI;EACF,MAAM,WACH,MAAM,YAAY,KAAK,IAAK,KAAK,QAAQ;AAC5C,MAAI,CAAC,UAAU;AACb,cAAW,iBAAiB,kBAAkB;AAC9C,UAAO;;EAGT,MAAM,cAAc,MAAM,QAAQ,KAAK;EAGvC,IAAI,aAAa;AAEjB,MAAI,CAAC,YACH,KAAI;AAOF,QALiB,MAAM,MACrB,GAAG,YAAY,SAAS,WAAW,KAAK,KAAK,SAC9C,CAAC,YAAY,GAAG,GAGH,WAAW,KAAK;AAC5B,QAAI,eAAe,oBAAoB;IAEvC,MAAM,cAAc,uBAAuB,KAAK,KAAK;AACrD,iBAAa,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,KAAK,MAAM,CAAC;AAC/D,UAAM,eAAe,YAAY;;WAE5B,QAAQ;AACf,YAAS,iBAAiB,sBAAsB,QAAQ;;EAI5D,MAAMA,aAAyB;GAC7B,MAAM;GACN;GACA;GACA;GACD;AAED,MAAI,YACF,QAAO,MAAM,UAAU,WAAW;MAElC,QAAO,MAAM,UAAU,WAAW;UAE7B,OAAO;AACd,UAAQ,MACN,uDACA,MACD;AACD,WAAS,iBAAiB,qBAAqB,QAAQ;AACvD,QAAM,MACJ,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,kBAC5D;AACD,SAAO;;GAGZ,CAAC"}
|
|
@@ -21,7 +21,7 @@ function getTusCleanupHandler(args) {
|
|
|
21
21
|
id: docs[0].id
|
|
22
22
|
});
|
|
23
23
|
return new Response("Cleanup successful", { status: 200 });
|
|
24
|
-
} catch
|
|
24
|
+
} catch {
|
|
25
25
|
logError(MediaCloudErrors.TUS_CLEANUP_ERROR.message);
|
|
26
26
|
return new Response("Cleanup failed", { status: 500 });
|
|
27
27
|
}
|
|
@@ -1 +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
|
|
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 {\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;UACpD;AACN,YAAS,iBAAiB,kBAAkB,QAAQ;AACpD,UAAO,IAAI,SAAS,kBAAkB,EAAE,QAAQ,KAAK,CAAC"}
|
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 payload2 from "payload";
|
|
5
5
|
import { PayloadRequest } from "payload";
|
|
6
6
|
|
|
7
7
|
//#region src/utils/tus.d.ts
|
|
@@ -42,11 +42,11 @@ declare function createTusEndpoints(args: CreateTusEndpointsArgs): ({
|
|
|
42
42
|
method: "delete";
|
|
43
43
|
path: string;
|
|
44
44
|
} | {
|
|
45
|
-
handler:
|
|
45
|
+
handler: payload2.PayloadHandler;
|
|
46
46
|
method: "get";
|
|
47
47
|
path: string;
|
|
48
48
|
} | {
|
|
49
|
-
handler:
|
|
49
|
+
handler: payload2.PayloadHandler;
|
|
50
50
|
method: "post";
|
|
51
51
|
path: string;
|
|
52
52
|
})[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maas/payload-plugin-media-cloud",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"contributors": [
|
|
6
6
|
{
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"dev": "tsdown --watch",
|
|
85
85
|
"lint": "eslint .",
|
|
86
86
|
"format": "prettier --write .",
|
|
87
|
-
"release": "release-it && pnpx tsx ../../scripts/publish.ts"
|
|
87
|
+
"release": "pnpm run build && release-it && pnpx tsx ../../scripts/publish.ts"
|
|
88
88
|
}
|
|
89
89
|
}
|