@maas/payload-plugin-media-cloud 0.0.44 → 0.0.46

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.
Files changed (32) hide show
  1. package/dist/collectionHooks/afterChange.mjs +2 -1
  2. package/dist/collectionHooks/afterChange.mjs.map +1 -1
  3. package/dist/collectionHooks/beforeChange.mjs +27 -18
  4. package/dist/collectionHooks/beforeChange.mjs.map +1 -1
  5. package/dist/collectionHooks/folderAfterChange.d.mts +7 -0
  6. package/dist/collectionHooks/folderAfterChange.mjs +80 -0
  7. package/dist/collectionHooks/folderAfterChange.mjs.map +1 -0
  8. package/dist/collectionHooks/thumbnailAfterChange.d.mts +7 -0
  9. package/dist/collectionHooks/{thumbnail.mjs → thumbnailAfterChange.mjs} +4 -4
  10. package/dist/collectionHooks/thumbnailAfterChange.mjs.map +1 -0
  11. package/dist/collections/mediaCollection.d.mts +0 -1
  12. package/dist/collections/mediaCollection.mjs +6 -11
  13. package/dist/collections/mediaCollection.mjs.map +1 -1
  14. package/dist/endpoints/fileExistsHandler.mjs +3 -3
  15. package/dist/endpoints/fileExistsHandler.mjs.map +1 -1
  16. package/dist/endpoints/tusFolderHandler.d.mts +3 -2
  17. package/dist/endpoints/tusFolderHandler.mjs +3 -3
  18. package/dist/endpoints/tusFolderHandler.mjs.map +1 -1
  19. package/dist/plugin.mjs +31 -4
  20. package/dist/plugin.mjs.map +1 -1
  21. package/dist/tus/stores/s3/partsManager.mjs.map +1 -1
  22. package/dist/tus/stores/s3/semaphore.d.mts +1 -2
  23. package/dist/tus/stores/s3/semaphore.mjs.map +1 -1
  24. package/dist/types/errors.d.mts +4 -0
  25. package/dist/types/errors.mjs +4 -0
  26. package/dist/types/errors.mjs.map +1 -1
  27. package/dist/utils/file.d.mts +2 -2
  28. package/dist/utils/tus.mjs +3 -1
  29. package/dist/utils/tus.mjs.map +1 -1
  30. package/package.json +1 -1
  31. package/dist/collectionHooks/thumbnail.d.mts +0 -7
  32. package/dist/collectionHooks/thumbnail.mjs.map +0 -1
@@ -6,9 +6,10 @@ import { s3Store } from "../plugin.mjs";
6
6
  const afterChangeHook = async ({ doc, previousDoc, req }) => {
7
7
  const { throwError } = useErrorHandler();
8
8
  if (req.context?._mediaCloudPluginInternal) return doc;
9
+ if (!previousDoc?.path) return doc;
9
10
  if (doc.path !== previousDoc?.path) {
10
11
  if (doc.storage === "s3" && s3Store) try {
11
- const oldKey = previousDoc?.path ?? previousDoc?.filename;
12
+ const oldKey = previousDoc?.path;
12
13
  const newKey = doc.path;
13
14
  await s3Store.copy(oldKey, newKey);
14
15
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"afterChange.mjs","names":["afterChangeHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/afterChange.ts"],"sourcesContent":["import { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const afterChangeHook: CollectionAfterChangeHook = async ({\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?._mediaCloudPluginInternal) {\n return doc\n }\n\n // Move asset in S3 if path has changed\n if (doc.path !== previousDoc?.path) {\n if (doc.storage === 's3' && s3Store) {\n try {\n const oldKey = previousDoc?.path ?? previousDoc?.filename\n const newKey = doc.path\n\n await s3Store.copy(oldKey, newKey)\n } catch (error) {\n throwError({ ...MediaCloudErrors.S3_MOVE_ERROR, cause: error })\n }\n }\n }\n\n return doc\n}\n"],"mappings":";;;;;AAMA,MAAaA,kBAA6C,OAAO,EAC/D,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,0BACf,QAAO;AAIT,KAAI,IAAI,SAAS,aAAa,MAC5B;MAAI,IAAI,YAAY,QAAQ,QAC1B,KAAI;GACF,MAAM,SAAS,aAAa,QAAQ,aAAa;GACjD,MAAM,SAAS,IAAI;AAEnB,SAAM,QAAQ,KAAK,QAAQ,OAAO;WAC3B,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAe,OAAO;IAAO,CAAC;;;AAKrE,QAAO"}
1
+ {"version":3,"file":"afterChange.mjs","names":["afterChangeHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/afterChange.ts"],"sourcesContent":["import { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const afterChangeHook: CollectionAfterChangeHook = async ({\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?._mediaCloudPluginInternal) {\n return doc\n }\n\n // Skip if this is a new document, only move files on updates\n if (!previousDoc?.path) {\n return doc\n }\n\n // Move asset in S3 if path has changed\n // Path is updated in `beforeChange` if filename or folder changes\n if (doc.path !== previousDoc?.path) {\n if (doc.storage === 's3' && s3Store) {\n try {\n const oldKey = previousDoc?.path\n const newKey = doc.path\n\n await s3Store.copy(oldKey, newKey)\n } catch (error) {\n throwError({ ...MediaCloudErrors.S3_MOVE_ERROR, cause: error })\n }\n }\n }\n\n return doc\n}\n"],"mappings":";;;;;AAMA,MAAaA,kBAA6C,OAAO,EAC/D,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,0BACf,QAAO;AAIT,KAAI,CAAC,aAAa,KAChB,QAAO;AAKT,KAAI,IAAI,SAAS,aAAa,MAC5B;MAAI,IAAI,YAAY,QAAQ,QAC1B,KAAI;GACF,MAAM,SAAS,aAAa;GAC5B,MAAM,SAAS,IAAI;AAEnB,SAAM,QAAQ,KAAK,QAAQ,OAAO;WAC3B,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAe,OAAO;IAAO,CAAC;;;AAKrE,QAAO"}
@@ -5,25 +5,34 @@ import { buildS3Path } from "../utils/buildS3Path.mjs";
5
5
  //#region src/collectionHooks/beforeChange.ts
6
6
  const beforeChangeHook = async ({ data, originalDoc, req }) => {
7
7
  const { throwError } = useErrorHandler();
8
- if (data.filename && data.filename !== originalDoc?.filename || data.folder && data.folder !== originalDoc?.folder || !data.folder) {
9
- let prefix = "";
10
- if (data.folder) try {
11
- const folder = await req.payload.findByID({
12
- id: data.folder,
13
- collection: "payload-folders",
14
- select: {
15
- name: true,
16
- folder: true
17
- }
18
- });
19
- if (folder) prefix = buildS3Path(folder);
20
- } catch (error) {
21
- throwError({
22
- ...MediaCloudErrors.FOLDER_FETCH_ERROR,
23
- cause: error
24
- });
8
+ const { custom } = req?.payload?.config ?? {};
9
+ const folders = custom?.mediaCloud?.options?.folders;
10
+ if (data.filename && data.filename !== originalDoc?.filename || data.folder && data.folder !== originalDoc?.folder || !data.path) switch (folders) {
11
+ case true: {
12
+ let prefix = "";
13
+ if (data.folder) try {
14
+ const folder = await req.payload.findByID({
15
+ id: data.folder,
16
+ collection: "payload-folders",
17
+ select: {
18
+ name: true,
19
+ folder: true
20
+ },
21
+ depth: 20
22
+ });
23
+ if (folder) prefix = buildS3Path(folder);
24
+ } catch (error) {
25
+ throwError({
26
+ ...MediaCloudErrors.FOLDER_FETCH_ERROR,
27
+ cause: error
28
+ });
29
+ }
30
+ data.path = prefix ? `${prefix}/${data.filename}` : data.filename;
31
+ break;
25
32
  }
26
- data.path = prefix ? `${prefix}/${data.filename}` : data.filename;
33
+ case false:
34
+ data.path = data.filename;
35
+ break;
27
36
  }
28
37
  return data;
29
38
  };
@@ -1 +1 @@
1
- {"version":3,"file":"beforeChange.mjs","names":["beforeChangeHook: CollectionBeforeChangeHook"],"sources":["../../src/collectionHooks/beforeChange.ts"],"sourcesContent":["import { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\nimport { buildS3Path } from '../utils/buildS3Path'\nimport type { CollectionBeforeChangeHook } from 'payload'\n\nexport const beforeChangeHook: CollectionBeforeChangeHook = async ({\n data,\n originalDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n // Update path\n if (\n (data.filename && data.filename !== originalDoc?.filename) ||\n (data.folder && data.folder !== originalDoc?.folder) ||\n !data.folder\n ) {\n let prefix = ''\n\n if (data.folder) {\n try {\n const folder = await req.payload.findByID({\n id: data.folder,\n collection: 'payload-folders',\n select: { name: true, folder: true },\n })\n\n if (folder) {\n prefix = buildS3Path(folder)\n }\n } catch (error) {\n throwError({ ...MediaCloudErrors.FOLDER_FETCH_ERROR, cause: error })\n }\n }\n\n // Save path\n data.path = prefix ? `${prefix}/${data.filename}` : data.filename\n }\n\n return data\n}\n"],"mappings":";;;;;AAKA,MAAaA,mBAA+C,OAAO,EACjE,MACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAExC,KACG,KAAK,YAAY,KAAK,aAAa,aAAa,YAChD,KAAK,UAAU,KAAK,WAAW,aAAa,UAC7C,CAAC,KAAK,QACN;EACA,IAAI,SAAS;AAEb,MAAI,KAAK,OACP,KAAI;GACF,MAAM,SAAS,MAAM,IAAI,QAAQ,SAAS;IACxC,IAAI,KAAK;IACT,YAAY;IACZ,QAAQ;KAAE,MAAM;KAAM,QAAQ;KAAM;IACrC,CAAC;AAEF,OAAI,OACF,UAAS,YAAY,OAAO;WAEvB,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAoB,OAAO;IAAO,CAAC;;AAKxE,OAAK,OAAO,SAAS,GAAG,OAAO,GAAG,KAAK,aAAa,KAAK;;AAG3D,QAAO"}
1
+ {"version":3,"file":"beforeChange.mjs","names":["beforeChangeHook: CollectionBeforeChangeHook"],"sources":["../../src/collectionHooks/beforeChange.ts"],"sourcesContent":["import { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\nimport { buildS3Path } from '../utils/buildS3Path'\nimport type { CollectionBeforeChangeHook } from 'payload'\n\nexport const beforeChangeHook: CollectionBeforeChangeHook = async ({\n data,\n originalDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n const { custom } = req?.payload?.config ?? {}\n\n // Check if folders are enabled in plugin options\n const folders = custom?.mediaCloud?.options?.folders\n\n // Update path if missing or filename or folder have changed\n if (\n (data.filename && data.filename !== originalDoc?.filename) ||\n (data.folder && data.folder !== originalDoc?.folder) ||\n !data.path\n ) {\n switch (folders) {\n case true: {\n let prefix = ''\n\n if (data.folder) {\n try {\n const folder = await req.payload.findByID({\n id: data.folder,\n collection: 'payload-folders',\n select: { name: true, folder: true },\n depth: 20,\n })\n\n if (folder) {\n prefix = buildS3Path(folder)\n }\n } catch (error) {\n throwError({\n ...MediaCloudErrors.FOLDER_FETCH_ERROR,\n cause: error,\n })\n }\n }\n\n // Save path\n data.path = prefix ? `${prefix}/${data.filename}` : data.filename\n break\n }\n case false: {\n // For flat structures, use filename as path\n data.path = data.filename\n break\n }\n }\n }\n\n return data\n}\n"],"mappings":";;;;;AAKA,MAAaA,mBAA+C,OAAO,EACjE,MACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;CACxC,MAAM,EAAE,WAAW,KAAK,SAAS,UAAU,EAAE;CAG7C,MAAM,UAAU,QAAQ,YAAY,SAAS;AAG7C,KACG,KAAK,YAAY,KAAK,aAAa,aAAa,YAChD,KAAK,UAAU,KAAK,WAAW,aAAa,UAC7C,CAAC,KAAK,KAEN,SAAQ,SAAR;EACE,KAAK,MAAM;GACT,IAAI,SAAS;AAEb,OAAI,KAAK,OACP,KAAI;IACF,MAAM,SAAS,MAAM,IAAI,QAAQ,SAAS;KACxC,IAAI,KAAK;KACT,YAAY;KACZ,QAAQ;MAAE,MAAM;MAAM,QAAQ;MAAM;KACpC,OAAO;KACR,CAAC;AAEF,QAAI,OACF,UAAS,YAAY,OAAO;YAEvB,OAAO;AACd,eAAW;KACT,GAAG,iBAAiB;KACpB,OAAO;KACR,CAAC;;AAKN,QAAK,OAAO,SAAS,GAAG,OAAO,GAAG,KAAK,aAAa,KAAK;AACzD;;EAEF,KAAK;AAEH,QAAK,OAAO,KAAK;AACjB;;AAKN,QAAO"}
@@ -0,0 +1,7 @@
1
+ import { CollectionAfterChangeHook } from "payload";
2
+
3
+ //#region src/collectionHooks/folderAfterChange.d.ts
4
+ declare const folderAfterChangeHook: CollectionAfterChangeHook;
5
+ //#endregion
6
+ export { folderAfterChangeHook };
7
+ //# sourceMappingURL=folderAfterChange.d.mts.map
@@ -0,0 +1,80 @@
1
+ import { MediaCloudErrors } from "../types/errors.mjs";
2
+ import { useErrorHandler } from "../hooks/useErrorHandler.mjs";
3
+ import { buildS3Path } from "../utils/buildS3Path.mjs";
4
+
5
+ //#region src/collectionHooks/folderAfterChange.ts
6
+ const folderAfterChangeHook = async ({ doc, previousDoc, req }) => {
7
+ const { throwError } = useErrorHandler();
8
+ if (doc.name === previousDoc.name) return doc;
9
+ const { custom } = req?.payload?.config ?? {};
10
+ const collectionSlug = custom?.mediaCloud?.options?.collection ?? "media";
11
+ const foldersSlug = typeof req.payload.config.folders === "object" ? req.payload.config.folders.slug : "payload-folders";
12
+ let populatedFolder;
13
+ try {
14
+ populatedFolder = await req.payload.findByID({
15
+ id: doc.id,
16
+ collection: foldersSlug,
17
+ select: {
18
+ name: true,
19
+ folder: true
20
+ },
21
+ depth: 20
22
+ });
23
+ } catch (error) {
24
+ throwError({
25
+ ...MediaCloudErrors.FOLDER_FETCH_ERROR,
26
+ cause: error
27
+ });
28
+ return doc;
29
+ }
30
+ if (!populatedFolder) return doc;
31
+ const newPrefix = buildS3Path(populatedFolder);
32
+ const oldPrefix = buildS3Path({
33
+ ...populatedFolder,
34
+ name: previousDoc.name
35
+ });
36
+ if (!oldPrefix || !newPrefix || oldPrefix === newPrefix) return doc;
37
+ let page = 1;
38
+ let hasNextPage = true;
39
+ while (hasNextPage) {
40
+ let mediaItems;
41
+ try {
42
+ mediaItems = await req.payload.find({
43
+ collection: collectionSlug,
44
+ where: { path: { contains: oldPrefix } },
45
+ limit: 100,
46
+ page,
47
+ depth: 0
48
+ });
49
+ } catch (error) {
50
+ throwError({
51
+ ...MediaCloudErrors.FOLDER_FETCH_ERROR,
52
+ cause: error
53
+ });
54
+ break;
55
+ }
56
+ const itemsToUpdate = mediaItems.docs.filter((item) => item.path?.startsWith(`${oldPrefix}/`));
57
+ await Promise.all(itemsToUpdate.map(async (item) => {
58
+ const newPath = item.path.replace(oldPrefix, newPrefix);
59
+ try {
60
+ await req.payload.update({
61
+ collection: collectionSlug,
62
+ id: item.id,
63
+ data: { path: newPath }
64
+ });
65
+ } catch (error) {
66
+ throwError({
67
+ ...MediaCloudErrors.PATH_UPDATE_ERROR,
68
+ cause: error
69
+ });
70
+ }
71
+ }));
72
+ hasNextPage = mediaItems.hasNextPage;
73
+ page++;
74
+ }
75
+ return doc;
76
+ };
77
+
78
+ //#endregion
79
+ export { folderAfterChangeHook };
80
+ //# sourceMappingURL=folderAfterChange.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderAfterChange.mjs","names":["folderAfterChangeHook: CollectionAfterChangeHook","collectionSlug: string"],"sources":["../../src/collectionHooks/folderAfterChange.ts"],"sourcesContent":["import { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { buildS3Path } from '../utils/buildS3Path'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const folderAfterChangeHook: CollectionAfterChangeHook = async ({\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Only proceed if the folder name has actually changed\n if (doc.name === previousDoc.name) {\n return doc\n }\n\n const { custom } = req?.payload?.config ?? {}\n const collectionSlug: string =\n custom?.mediaCloud?.options?.collection ?? 'media'\n\n const foldersSlug =\n typeof req.payload.config.folders === 'object'\n ? req.payload.config.folders.slug\n : 'payload-folders'\n\n // Fetch the renamed folder with fully populated parent folders so we can\n // reconstruct the full path prefix (e.g. \"grandparent/parent/thisFolder\")\n let populatedFolder\n try {\n populatedFolder = await req.payload.findByID({\n id: doc.id,\n collection: foldersSlug,\n select: { name: true, folder: true },\n depth: 20,\n })\n } catch (error) {\n throwError({ ...MediaCloudErrors.FOLDER_FETCH_ERROR, cause: error })\n return doc\n }\n\n if (!populatedFolder) {\n return doc\n }\n\n // Build the new path prefix using the current (already renamed) folder document\n const newPrefix = buildS3Path(populatedFolder)\n\n // Build the old path prefix by substituting the previous name into the same\n // folder structure – the parent chain is identical, only the leaf name changed\n const oldPrefix = buildS3Path({ ...populatedFolder, name: previousDoc.name })\n\n if (!oldPrefix || !newPrefix || oldPrefix === newPrefix) {\n return doc\n }\n\n // Find and update all media items whose path starts with the old folder prefix.\n // Pagination ensures every item is processed regardless of collection size.\n let page = 1\n let hasNextPage = true\n\n while (hasNextPage) {\n let mediaItems\n try {\n mediaItems = await req.payload.find({\n collection: collectionSlug,\n where: {\n path: {\n contains: oldPrefix,\n },\n },\n limit: 100,\n page,\n depth: 0,\n })\n } catch (error) {\n throwError({ ...MediaCloudErrors.FOLDER_FETCH_ERROR, cause: error })\n break\n }\n\n // Only update items whose path starts with `oldPrefix` exactly\n const itemsToUpdate = mediaItems.docs.filter((item) =>\n item.path?.startsWith(`${oldPrefix}/`)\n )\n\n await Promise.all(\n itemsToUpdate.map(async (item) => {\n const newPath = (item.path as string).replace(oldPrefix, newPrefix)\n\n try {\n await req.payload.update({\n collection: collectionSlug,\n id: item.id,\n data: { path: newPath },\n })\n } catch (error) {\n throwError({ ...MediaCloudErrors.PATH_UPDATE_ERROR, cause: error })\n }\n })\n )\n\n hasNextPage = mediaItems.hasNextPage\n page++\n }\n\n return doc\n}\n"],"mappings":";;;;;AAMA,MAAaA,wBAAmD,OAAO,EACrE,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,YAAY,KAC3B,QAAO;CAGT,MAAM,EAAE,WAAW,KAAK,SAAS,UAAU,EAAE;CAC7C,MAAMC,iBACJ,QAAQ,YAAY,SAAS,cAAc;CAE7C,MAAM,cACJ,OAAO,IAAI,QAAQ,OAAO,YAAY,WAClC,IAAI,QAAQ,OAAO,QAAQ,OAC3B;CAIN,IAAI;AACJ,KAAI;AACF,oBAAkB,MAAM,IAAI,QAAQ,SAAS;GAC3C,IAAI,IAAI;GACR,YAAY;GACZ,QAAQ;IAAE,MAAM;IAAM,QAAQ;IAAM;GACpC,OAAO;GACR,CAAC;UACK,OAAO;AACd,aAAW;GAAE,GAAG,iBAAiB;GAAoB,OAAO;GAAO,CAAC;AACpE,SAAO;;AAGT,KAAI,CAAC,gBACH,QAAO;CAIT,MAAM,YAAY,YAAY,gBAAgB;CAI9C,MAAM,YAAY,YAAY;EAAE,GAAG;EAAiB,MAAM,YAAY;EAAM,CAAC;AAE7E,KAAI,CAAC,aAAa,CAAC,aAAa,cAAc,UAC5C,QAAO;CAKT,IAAI,OAAO;CACX,IAAI,cAAc;AAElB,QAAO,aAAa;EAClB,IAAI;AACJ,MAAI;AACF,gBAAa,MAAM,IAAI,QAAQ,KAAK;IAClC,YAAY;IACZ,OAAO,EACL,MAAM,EACJ,UAAU,WACX,EACF;IACD,OAAO;IACP;IACA,OAAO;IACR,CAAC;WACK,OAAO;AACd,cAAW;IAAE,GAAG,iBAAiB;IAAoB,OAAO;IAAO,CAAC;AACpE;;EAIF,MAAM,gBAAgB,WAAW,KAAK,QAAQ,SAC5C,KAAK,MAAM,WAAW,GAAG,UAAU,GAAG,CACvC;AAED,QAAM,QAAQ,IACZ,cAAc,IAAI,OAAO,SAAS;GAChC,MAAM,UAAW,KAAK,KAAgB,QAAQ,WAAW,UAAU;AAEnE,OAAI;AACF,UAAM,IAAI,QAAQ,OAAO;KACvB,YAAY;KACZ,IAAI,KAAK;KACT,MAAM,EAAE,MAAM,SAAS;KACxB,CAAC;YACK,OAAO;AACd,eAAW;KAAE,GAAG,iBAAiB;KAAmB,OAAO;KAAO,CAAC;;IAErE,CACH;AAED,gBAAc,WAAW;AACzB;;AAGF,QAAO"}
@@ -0,0 +1,7 @@
1
+ import { CollectionAfterChangeHook } from "payload";
2
+
3
+ //#region src/collectionHooks/thumbnailAfterChange.d.ts
4
+ declare const thumbnailAfterChangeHook: CollectionAfterChangeHook;
5
+ //#endregion
6
+ export { thumbnailAfterChangeHook };
7
+ //# sourceMappingURL=thumbnailAfterChange.d.mts.map
@@ -3,8 +3,8 @@ import { useErrorHandler } from "../hooks/useErrorHandler.mjs";
3
3
  import { buildThumbnailURL } from "../utils/buildThumbnailURL.mjs";
4
4
  import { s3Store } from "../plugin.mjs";
5
5
 
6
- //#region src/collectionHooks/thumbnail.ts
7
- const thumbnailHook = async ({ collection, doc, previousDoc, req }) => {
6
+ //#region src/collectionHooks/thumbnailAfterChange.ts
7
+ const thumbnailAfterChangeHook = async ({ collection, doc, previousDoc, req }) => {
8
8
  const { throwError } = useErrorHandler();
9
9
  if (req.context?._mediaCloudPluginInternal) return doc;
10
10
  if (!doc.thumbnail || previousDoc?.path !== doc.path) try {
@@ -34,5 +34,5 @@ const thumbnailHook = async ({ collection, doc, previousDoc, req }) => {
34
34
  };
35
35
 
36
36
  //#endregion
37
- export { thumbnailHook };
38
- //# sourceMappingURL=thumbnail.mjs.map
37
+ export { thumbnailAfterChangeHook };
38
+ //# sourceMappingURL=thumbnailAfterChange.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thumbnailAfterChange.mjs","names":["thumbnailAfterChangeHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/thumbnailAfterChange.ts"],"sourcesContent":["import { buildThumbnailURL } from '../utils/buildThumbnailURL'\nimport { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const thumbnailAfterChangeHook: CollectionAfterChangeHook = async ({\n collection,\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?._mediaCloudPluginInternal) {\n return doc\n }\n\n // Handle thumbnail\n if (!doc.thumbnail || previousDoc?.path !== doc.path) {\n try {\n const thumbnail = buildThumbnailURL({\n storage: doc.storage,\n playbackId: doc.storage === 'mux' ? doc.mux.playbackId : undefined,\n s3Key: doc.storage === 's3' ? (doc.path ?? doc.filename) : undefined,\n s3Store,\n })\n\n req.context = { ...req.context }\n req.context._mediaCloudPluginInternal = true\n\n const update = await req.payload.update({\n collection: collection.slug,\n id: doc.id,\n data: { thumbnail },\n req,\n })\n\n delete req.context._mediaCloudPluginInternal\n\n return update\n } catch (error) {\n throwError({\n ...MediaCloudErrors.THUMBNAIL_GENERATION_ERROR,\n cause: error,\n })\n }\n }\n\n return doc\n}\n"],"mappings":";;;;;;AAOA,MAAaA,2BAAsD,OAAO,EACxE,YACA,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,0BACf,QAAO;AAIT,KAAI,CAAC,IAAI,aAAa,aAAa,SAAS,IAAI,KAC9C,KAAI;EACF,MAAM,YAAY,kBAAkB;GAClC,SAAS,IAAI;GACb,YAAY,IAAI,YAAY,QAAQ,IAAI,IAAI,aAAa;GACzD,OAAO,IAAI,YAAY,OAAQ,IAAI,QAAQ,IAAI,WAAY;GAC3D;GACD,CAAC;AAEF,MAAI,UAAU,EAAE,GAAG,IAAI,SAAS;AAChC,MAAI,QAAQ,4BAA4B;EAExC,MAAM,SAAS,MAAM,IAAI,QAAQ,OAAO;GACtC,YAAY,WAAW;GACvB,IAAI,IAAI;GACR,MAAM,EAAE,WAAW;GACnB;GACD,CAAC;AAEF,SAAO,IAAI,QAAQ;AAEnB,SAAO;UACA,OAAO;AACd,aAAW;GACT,GAAG,iBAAiB;GACpB,OAAO;GACR,CAAC;;AAIN,QAAO"}
@@ -4,7 +4,6 @@ import { CollectionConfig } from "payload";
4
4
  //#region src/collections/mediaCollection.d.ts
5
5
  interface GetMediaCollectionArgs {
6
6
  view: MediaCloudPluginOptions['view'];
7
- folders: MediaCloudPluginOptions['folders'];
8
7
  baseCollection?: CollectionConfig;
9
8
  }
10
9
  /**
@@ -8,7 +8,7 @@ import { storageField } from "../fields/storage.mjs";
8
8
  import { muxField } from "../fields/mux.mjs";
9
9
  import { beforeChangeHook } from "../collectionHooks/beforeChange.mjs";
10
10
  import { afterChangeHook } from "../collectionHooks/afterChange.mjs";
11
- import { thumbnailHook } from "../collectionHooks/thumbnail.mjs";
11
+ import { thumbnailAfterChangeHook } from "../collectionHooks/thumbnailAfterChange.mjs";
12
12
 
13
13
  //#region src/collections/mediaCollection.ts
14
14
  /**
@@ -17,15 +17,7 @@ import { thumbnailHook } from "../collectionHooks/thumbnail.mjs";
17
17
  * @returns A configured Payload collection for media files
18
18
  */
19
19
  function getMediaCollection(args) {
20
- const { baseCollection, view, folders } = args;
21
- const hooks = {
22
- beforeChange: [],
23
- afterChange: [thumbnailHook]
24
- };
25
- if (folders) {
26
- hooks.beforeChange = [beforeChangeHook, ...hooks.beforeChange ?? []];
27
- hooks.afterChange = [afterChangeHook, ...hooks.afterChange ?? []];
28
- }
20
+ const { baseCollection, view } = args;
29
21
  const config = {
30
22
  slug: "media",
31
23
  access: {
@@ -56,7 +48,10 @@ function getMediaCollection(args) {
56
48
  storageField,
57
49
  muxField
58
50
  ],
59
- hooks
51
+ hooks: {
52
+ beforeChange: [beforeChangeHook],
53
+ afterChange: [afterChangeHook, thumbnailAfterChangeHook]
54
+ }
60
55
  };
61
56
  if (!baseCollection) return config;
62
57
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"mediaCollection.mjs","names":["hooks: CollectionConfig['hooks']","config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import { thumbnailField } from '../fields/thumbnail'\nimport { pathField } from '../fields/path'\nimport { filenameField } from '../fields/filename'\nimport { altField } from '../fields/alt'\nimport { widthField } from '../fields/width'\nimport { heightField } from '../fields/height'\nimport { storageField } from '../fields/storage'\nimport { muxField } from '../fields/mux'\n\nimport { beforeChangeHook } from '../collectionHooks/beforeChange'\nimport { afterChangeHook } from '../collectionHooks/afterChange'\n\nimport type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types/index'\nimport { before } from 'node:test'\nimport { thumbnailHook } from '../collectionHooks/thumbnail'\n\ninterface GetMediaCollectionArgs {\n view: MediaCloudPluginOptions['view']\n folders: MediaCloudPluginOptions['folders']\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 { baseCollection, view, folders } = args\n\n const hooks: CollectionConfig['hooks'] = {\n beforeChange: [],\n afterChange: [thumbnailHook],\n }\n\n // Add hooks for folder support if enabled\n if (folders) {\n hooks.beforeChange = [beforeChangeHook, ...(hooks.beforeChange ?? [])]\n hooks.afterChange = [afterChangeHook, ...(hooks.afterChange ?? [])]\n }\n\n // Override list view to use grid view if specified\n const components =\n view === 'grid'\n ? {\n views: {\n list: {\n Component: '@maas/payload-plugin-media-cloud/components#GridView',\n },\n },\n }\n : undefined\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\n components,\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 return doc.thumbnail ?? null\n },\n },\n fields: [\n thumbnailField,\n pathField,\n altField,\n {\n type: 'row',\n fields: [widthField, heightField],\n },\n storageField,\n muxField,\n ],\n hooks: hooks,\n }\n\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n slug: baseCollection.slug ?? config.slug,\n admin: {\n ...(config.admin ?? {}),\n ...(baseCollection.admin ?? {}),\n },\n access: {\n ...(config.access ?? {}),\n ...(baseCollection.access ?? {}),\n },\n upload: config.upload,\n fields: [...(baseCollection.fields ?? []), ...config.fields, filenameField],\n hooks: {\n ...baseCollection.hooks,\n beforeChange: [\n ...(baseCollection.hooks?.beforeChange ?? []),\n ...(config.hooks?.beforeChange ?? []),\n ],\n afterChange: [\n ...(baseCollection.hooks?.afterChange ?? []),\n ...(config.hooks?.afterChange ?? []),\n ],\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,gBAAgB,MAAM,YAAY;CAE1C,MAAMA,QAAmC;EACvC,cAAc,EAAE;EAChB,aAAa,CAAC,cAAc;EAC7B;AAGD,KAAI,SAAS;AACX,QAAM,eAAe,CAAC,kBAAkB,GAAI,MAAM,gBAAgB,EAAE,CAAE;AACtE,QAAM,cAAc,CAAC,iBAAiB,GAAI,MAAM,eAAe,EAAE,CAAE;;CAerE,MAAMC,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,YAjBF,SAAS,SACL,EACE,OAAO,EACL,MAAM,EACJ,WAAW,wDACZ,EACF,EACF,GACD;GAUF,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,WAAO,IAAI,aAAa;;GAE3B;EACD,QAAQ;GACN;GACA;GACA;GACA;IACE,MAAM;IACN,QAAQ,CAAC,YAAY,YAAY;IAClC;GACD;GACA;GACD;EACM;EACR;AAED,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,MAAM,eAAe,QAAQ,OAAO;EACpC,OAAO;GACL,GAAI,OAAO,SAAS,EAAE;GACtB,GAAI,eAAe,SAAS,EAAE;GAC/B;EACD,QAAQ;GACN,GAAI,OAAO,UAAU,EAAE;GACvB,GAAI,eAAe,UAAU,EAAE;GAChC;EACD,QAAQ,OAAO;EACf,QAAQ;GAAC,GAAI,eAAe,UAAU,EAAE;GAAG,GAAG,OAAO;GAAQ;GAAc;EAC3E,OAAO;GACL,GAAG,eAAe;GAClB,cAAc,CACZ,GAAI,eAAe,OAAO,gBAAgB,EAAE,EAC5C,GAAI,OAAO,OAAO,gBAAgB,EAAE,CACrC;GACD,aAAa,CACX,GAAI,eAAe,OAAO,eAAe,EAAE,EAC3C,GAAI,OAAO,OAAO,eAAe,EAAE,CACpC;GACF;EACF"}
1
+ {"version":3,"file":"mediaCollection.mjs","names":["config: CollectionConfig"],"sources":["../../src/collections/mediaCollection.ts"],"sourcesContent":["import { thumbnailField } from '../fields/thumbnail'\nimport { pathField } from '../fields/path'\nimport { filenameField } from '../fields/filename'\nimport { altField } from '../fields/alt'\nimport { widthField } from '../fields/width'\nimport { heightField } from '../fields/height'\nimport { storageField } from '../fields/storage'\nimport { muxField } from '../fields/mux'\n\nimport { beforeChangeHook } from '../collectionHooks/beforeChange'\nimport { afterChangeHook } from '../collectionHooks/afterChange'\nimport { thumbnailAfterChangeHook } from '../collectionHooks/thumbnailAfterChange'\n\nimport type { CollectionConfig } from 'payload'\nimport type { Document } from 'payload'\nimport type { MediaCloudPluginOptions } from '../types/index'\n\ninterface GetMediaCollectionArgs {\n view: MediaCloudPluginOptions['view']\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 { baseCollection, view } = args\n\n const hooks: CollectionConfig['hooks'] = {\n beforeChange: [beforeChangeHook],\n afterChange: [afterChangeHook, thumbnailAfterChangeHook],\n }\n\n // Override list view to use grid view if specified\n const components =\n view === 'grid'\n ? {\n views: {\n list: {\n Component: '@maas/payload-plugin-media-cloud/components#GridView',\n },\n },\n }\n : undefined\n\n const config: CollectionConfig = {\n slug: 'media',\n access: {\n read: () => true,\n delete: () => true,\n },\n admin: {\n components,\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 return doc.thumbnail ?? null\n },\n },\n fields: [\n thumbnailField,\n pathField,\n altField,\n {\n type: 'row',\n fields: [widthField, heightField],\n },\n storageField,\n muxField,\n ],\n hooks: hooks,\n }\n\n if (!baseCollection) {\n return config\n }\n\n return {\n ...baseCollection,\n slug: baseCollection.slug ?? config.slug,\n admin: {\n ...(config.admin ?? {}),\n ...(baseCollection.admin ?? {}),\n },\n access: {\n ...(config.access ?? {}),\n ...(baseCollection.access ?? {}),\n },\n upload: config.upload,\n fields: [...(baseCollection.fields ?? []), ...config.fields, filenameField],\n hooks: {\n ...baseCollection.hooks,\n beforeChange: [\n ...(baseCollection.hooks?.beforeChange ?? []),\n ...(config.hooks?.beforeChange ?? []),\n ],\n afterChange: [\n ...(baseCollection.hooks?.afterChange ?? []),\n ...(config.hooks?.afterChange ?? []),\n ],\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,SAAgB,mBACd,MACkB;CAClB,MAAM,EAAE,gBAAgB,SAAS;CAmBjC,MAAMA,SAA2B;EAC/B,MAAM;EACN,QAAQ;GACN,YAAY;GACZ,cAAc;GACf;EACD,OAAO;GACL,YAjBF,SAAS,SACL,EACE,OAAO,EACL,MAAM,EACJ,WAAW,wDACZ,EACF,EACF,GACD;GAUF,OAAO;GACP,YAAY,EACV,cAAc,IACf;GACF;EACD,QAAQ;GACN,MAAM;GACN,gBAAgB;GAChB,gBAAgB;GAChB,eAAe,EAAE,OAA0B;AACzC,WAAO,IAAI,aAAa;;GAE3B;EACD,QAAQ;GACN;GACA;GACA;GACA;IACE,MAAM;IACN,QAAQ,CAAC,YAAY,YAAY;IAClC;GACD;GACA;GACD;EACD,OAjDuC;GACvC,cAAc,CAAC,iBAAiB;GAChC,aAAa,CAAC,iBAAiB,yBAAyB;GACzD;EA+CA;AAED,KAAI,CAAC,eACH,QAAO;AAGT,QAAO;EACL,GAAG;EACH,MAAM,eAAe,QAAQ,OAAO;EACpC,OAAO;GACL,GAAI,OAAO,SAAS,EAAE;GACtB,GAAI,eAAe,SAAS,EAAE;GAC/B;EACD,QAAQ;GACN,GAAI,OAAO,UAAU,EAAE;GACvB,GAAI,eAAe,UAAU,EAAE;GAChC;EACD,QAAQ,OAAO;EACf,QAAQ;GAAC,GAAI,eAAe,UAAU,EAAE;GAAG,GAAG,OAAO;GAAQ;GAAc;EAC3E,OAAO;GACL,GAAG,eAAe;GAClB,cAAc,CACZ,GAAI,eAAe,OAAO,gBAAgB,EAAE,EAC5C,GAAI,OAAO,OAAO,gBAAgB,EAAE,CACrC;GACD,aAAa,CACX,GAAI,eAAe,OAAO,eAAe,EAAE,EAC3C,GAAI,OAAO,OAAO,eAAe,EAAE,CACpC;GACF;EACF"}
@@ -20,9 +20,9 @@ function getFileExistsHandler(args) {
20
20
  const url = getS3Store().getUrl(filename);
21
21
  if ((await fetch(url, { method: "HEAD" })).status === 200) return Response.json({ message: "File found [S3]" }, { status: 200 });
22
22
  return new Response(null, { status: 204 });
23
- } catch (error) {
24
- logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message);
25
- return Response.json({ message: "Failed to process asset" }, { status: 500 });
23
+ } catch (_error) {
24
+ logError(MediaCloudErrors.FILE_NOT_FOUND.message);
25
+ return Response.json({ message: "Server error" }, { status: 500 });
26
26
  }
27
27
  };
28
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n // Check if file exists in Payload database\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n return Response.json(\n { message: 'File found [Payload]' },\n { status: 200 }\n )\n }\n\n // Check if completed file exists in S3\n const url = getS3Store().getUrl(filename)\n const s3Response = await fetch(url, { method: 'HEAD' })\n\n if (s3Response.status === 200) {\n return Response.json({ message: 'File found [S3]' }, { status: 200 })\n }\n\n return new Response(null, {\n status: 204,\n })\n } catch (error) {\n logError(MediaCloudErrors.FILE_DIMENSIONS_ERROR.message)\n return Response.json(\n { message: 'Failed to process asset' },\n { status: 500 }\n )\n }\n }\n}\n"],"mappings":";;;;AAWA,SAAgB,qBACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAIhD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,UACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;AAEF,OAAI,KAAK,SAAS,EAChB,QAAO,SAAS,KACd,EAAE,SAAS,wBAAwB,EACnC,EAAE,QAAQ,KAAK,CAChB;GAIH,MAAM,MAAM,YAAY,CAAC,OAAO,SAAS;AAGzC,QAFmB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,EAExC,WAAW,IACxB,QAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGvE,UAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,KACT,CAAC;WACK,OAAO;AACd,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO,SAAS,KACd,EAAE,SAAS,2BAA2B,EACtC,EAAE,QAAQ,KAAK,CAChB"}
1
+ {"version":3,"file":"fileExistsHandler.mjs","names":[],"sources":["../../src/endpoints/fileExistsHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\n\ninterface GetFileExistsHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getFileExistsHandler(\n args: GetFileExistsHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n // Check if file exists in Payload database\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: filename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n if (docs.length > 0) {\n return Response.json(\n { message: 'File found [Payload]' },\n { status: 200 }\n )\n }\n\n // Check if completed file exists in S3\n const url = getS3Store().getUrl(filename)\n const s3Response = await fetch(url, { method: 'HEAD' })\n\n if (s3Response.status === 200) {\n return Response.json({ message: 'File found [S3]' }, { status: 200 })\n }\n\n return new Response(null, {\n status: 204,\n })\n } catch (_error) {\n logError(MediaCloudErrors.FILE_NOT_FOUND.message)\n return Response.json({ message: 'Server error' }, { status: 500 })\n }\n }\n}\n"],"mappings":";;;;AAWA,SAAgB,qBACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAIhD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,UACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;AAEF,OAAI,KAAK,SAAS,EAChB,QAAO,SAAS,KACd,EAAE,SAAS,wBAAwB,EACnC,EAAE,QAAQ,KAAK,CAChB;GAIH,MAAM,MAAM,YAAY,CAAC,OAAO,SAAS;AAGzC,QAFmB,MAAM,MAAM,KAAK,EAAE,QAAQ,QAAQ,CAAC,EAExC,WAAW,IACxB,QAAO,SAAS,KAAK,EAAE,SAAS,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGvE,UAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,KACT,CAAC;WACK,QAAQ;AACf,YAAS,iBAAiB,eAAe,QAAQ;AACjD,UAAO,SAAS,KAAK,EAAE,SAAS,gBAAgB,EAAE,EAAE,QAAQ,KAAK,CAAC"}
@@ -2,11 +2,12 @@ import { S3Store } from "../tus/stores/s3/s3Store.mjs";
2
2
  import { PayloadHandler } from "payload";
3
3
 
4
4
  //#region src/endpoints/tusFolderHandler.d.ts
5
- interface GetTusPostProcessorHandlerArgs {
5
+ interface GetTusFolderHandlerArgs {
6
6
  getS3Store: () => S3Store;
7
7
  collection: string;
8
+ folders: boolean;
8
9
  }
9
- declare function getTusFolderHandler(args: GetTusPostProcessorHandlerArgs): PayloadHandler;
10
+ declare function getTusFolderHandler(args: GetTusFolderHandlerArgs): PayloadHandler;
10
11
  //#endregion
11
12
  export { getTusFolderHandler };
12
13
  //# sourceMappingURL=tusFolderHandler.d.mts.map
@@ -4,9 +4,10 @@ import { sanitizeFilename } from "../utils/file.mjs";
4
4
 
5
5
  //#region src/endpoints/tusFolderHandler.ts
6
6
  function getTusFolderHandler(args) {
7
- const { getS3Store, collection } = args;
7
+ const { getS3Store, collection, folders } = args;
8
8
  const { throwError, logError } = useErrorHandler();
9
9
  return async function(req) {
10
+ if (!folders) return Response.json({ message: "Folders are disabled, skipping …" }, { status: 200 });
10
11
  try {
11
12
  const { routeParams, payload } = req;
12
13
  const filename = routeParams?.filename;
@@ -22,8 +23,7 @@ function getTusFolderHandler(args) {
22
23
  if (!media) throwError(MediaCloudErrors.FILE_NOT_FOUND);
23
24
  if (media.storage !== "s3") return Response.json({ message: "Asset not stored on S3, skipping …" }, { status: 200 });
24
25
  const s3Store = getS3Store();
25
- if (!media.path) return Response.json({ message: "No path found, skipping …" }, { status: 200 });
26
- if (await s3Store?.read(media.path)) return Response.json({ message: "Asset already in correct location, skipping …" }, { status: 200 });
26
+ if (await s3Store?.read(media.path ?? "").catch(() => null)) return Response.json({ message: "Asset already in correct location, skipping …" }, { status: 200 });
27
27
  const oldKey = media.filename;
28
28
  const newKey = media.path;
29
29
  await s3Store?.copy(oldKey, newKey);
@@ -1 +1 @@
1
- {"version":3,"file":"tusFolderHandler.mjs","names":[],"sources":["../../src/endpoints/tusFolderHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\nimport { sanitizeFilename } from '../utils/file'\n\ninterface GetTusPostProcessorHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n}\n\nexport function getTusFolderHandler(\n args: GetTusPostProcessorHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n const sanitizedFilename = sanitizeFilename(filename)\n\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: sanitizedFilename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n const media = docs?.[0]\n\n if (!media) {\n throwError(MediaCloudErrors.FILE_NOT_FOUND)\n }\n\n if (media.storage !== 's3') {\n return Response.json(\n { message: 'Asset not stored on S3, skipping …' },\n { status: 200 }\n )\n }\n\n const s3Store = getS3Store()\n\n if (!media.path) {\n return Response.json(\n { message: 'No path found, skipping …' },\n { status: 200 }\n )\n }\n\n const file = await s3Store?.read(media.path)\n\n if (file) {\n return Response.json(\n { message: 'Asset already in correct location, skipping …' },\n { status: 200 }\n )\n }\n\n const oldKey = media.filename\n const newKey = media.path\n\n await s3Store?.copy(oldKey, newKey)\n return Response.json({ message: 'Asset moved' }, { status: 200 })\n } catch (_error) {\n logError(MediaCloudErrors.S3_MOVE_ERROR.message)\n return Response.json({ message: 'Failed to move asset' }, { status: 500 })\n }\n }\n}\n"],"mappings":";;;;;AAYA,SAAgB,oBACd,MACgB;CAChB,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAGhD,MAAM,oBAAoB,iBAAiB,SAAS;GAEpD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,mBACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;GAEF,MAAM,QAAQ,OAAO;AAErB,OAAI,CAAC,MACH,YAAW,iBAAiB,eAAe;AAG7C,OAAI,MAAM,YAAY,KACpB,QAAO,SAAS,KACd,EAAE,SAAS,sCAAsC,EACjD,EAAE,QAAQ,KAAK,CAChB;GAGH,MAAM,UAAU,YAAY;AAE5B,OAAI,CAAC,MAAM,KACT,QAAO,SAAS,KACd,EAAE,SAAS,6BAA6B,EACxC,EAAE,QAAQ,KAAK,CAChB;AAKH,OAFa,MAAM,SAAS,KAAK,MAAM,KAAK,CAG1C,QAAO,SAAS,KACd,EAAE,SAAS,iDAAiD,EAC5D,EAAE,QAAQ,KAAK,CAChB;GAGH,MAAM,SAAS,MAAM;GACrB,MAAM,SAAS,MAAM;AAErB,SAAM,SAAS,KAAK,QAAQ,OAAO;AACnC,UAAO,SAAS,KAAK,EAAE,SAAS,eAAe,EAAE,EAAE,QAAQ,KAAK,CAAC;WAC1D,QAAQ;AACf,YAAS,iBAAiB,cAAc,QAAQ;AAChD,UAAO,SAAS,KAAK,EAAE,SAAS,wBAAwB,EAAE,EAAE,QAAQ,KAAK,CAAC"}
1
+ {"version":3,"file":"tusFolderHandler.mjs","names":[],"sources":["../../src/endpoints/tusFolderHandler.ts"],"sourcesContent":["import type { PayloadHandler } from 'payload'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport { S3Store } from '../tus/stores/s3/s3Store'\nimport { sanitizeFilename } from '../utils/file'\n\ninterface GetTusFolderHandlerArgs {\n getS3Store: () => S3Store\n collection: string\n folders: boolean\n}\n\nexport function getTusFolderHandler(\n args: GetTusFolderHandlerArgs\n): PayloadHandler {\n const { getS3Store, collection, folders } = args\n const { throwError, logError } = useErrorHandler()\n\n return async function (req) {\n if (!folders) {\n return Response.json(\n { message: 'Folders are disabled, skipping …' },\n { status: 200 }\n )\n }\n\n try {\n const { routeParams, payload } = req\n const filename = routeParams?.filename as string\n\n if (!filename) {\n throwError(MediaCloudErrors.FILE_MISSING_NAME)\n }\n\n const sanitizedFilename = sanitizeFilename(filename)\n\n const { docs } = await payload.find({\n collection,\n where: {\n filename: {\n equals: sanitizedFilename,\n },\n },\n limit: 1,\n pagination: false,\n })\n\n const media = docs?.[0]\n\n if (!media) {\n throwError(MediaCloudErrors.FILE_NOT_FOUND)\n }\n\n if (media.storage !== 's3') {\n return Response.json(\n { message: 'Asset not stored on S3, skipping …' },\n { status: 200 }\n )\n }\n\n const s3Store = getS3Store()\n const file = await s3Store?.read(media.path ?? '').catch(() => null)\n\n if (file) {\n return Response.json(\n { message: 'Asset already in correct location, skipping …' },\n { status: 200 }\n )\n }\n\n const oldKey = media.filename\n const newKey = media.path\n\n await s3Store?.copy(oldKey, newKey)\n return Response.json({ message: 'Asset moved' }, { status: 200 })\n } catch (_error) {\n logError(MediaCloudErrors.S3_MOVE_ERROR.message)\n return Response.json({ message: 'Failed to move asset' }, { status: 500 })\n }\n }\n}\n"],"mappings":";;;;;AAaA,SAAgB,oBACd,MACgB;CAChB,MAAM,EAAE,YAAY,YAAY,YAAY;CAC5C,MAAM,EAAE,YAAY,aAAa,iBAAiB;AAElD,QAAO,eAAgB,KAAK;AAC1B,MAAI,CAAC,QACH,QAAO,SAAS,KACd,EAAE,SAAS,oCAAoC,EAC/C,EAAE,QAAQ,KAAK,CAChB;AAGH,MAAI;GACF,MAAM,EAAE,aAAa,YAAY;GACjC,MAAM,WAAW,aAAa;AAE9B,OAAI,CAAC,SACH,YAAW,iBAAiB,kBAAkB;GAGhD,MAAM,oBAAoB,iBAAiB,SAAS;GAEpD,MAAM,EAAE,SAAS,MAAM,QAAQ,KAAK;IAClC;IACA,OAAO,EACL,UAAU,EACR,QAAQ,mBACT,EACF;IACD,OAAO;IACP,YAAY;IACb,CAAC;GAEF,MAAM,QAAQ,OAAO;AAErB,OAAI,CAAC,MACH,YAAW,iBAAiB,eAAe;AAG7C,OAAI,MAAM,YAAY,KACpB,QAAO,SAAS,KACd,EAAE,SAAS,sCAAsC,EACjD,EAAE,QAAQ,KAAK,CAChB;GAGH,MAAM,UAAU,YAAY;AAG5B,OAFa,MAAM,SAAS,KAAK,MAAM,QAAQ,GAAG,CAAC,YAAY,KAAK,CAGlE,QAAO,SAAS,KACd,EAAE,SAAS,iDAAiD,EAC5D,EAAE,QAAQ,KAAK,CAChB;GAGH,MAAM,SAAS,MAAM;GACrB,MAAM,SAAS,MAAM;AAErB,SAAM,SAAS,KAAK,QAAQ,OAAO;AACnC,UAAO,SAAS,KAAK,EAAE,SAAS,eAAe,EAAE,EAAE,QAAQ,KAAK,CAAC;WAC1D,QAAQ;AACf,YAAS,iBAAiB,cAAc,QAAQ;AAChD,UAAO,SAAS,KAAK,EAAE,SAAS,wBAAwB,EAAE,EAAE,QAAQ,KAAK,CAAC"}
package/dist/plugin.mjs CHANGED
@@ -2,6 +2,7 @@ import { MediaCloudErrors } from "./types/errors.mjs";
2
2
  import { useErrorHandler } from "./hooks/useErrorHandler.mjs";
3
3
  import { getStorageAdapter } from "./adapter/storageAdapter.mjs";
4
4
  import { getMediaCollection } from "./collections/mediaCollection.mjs";
5
+ import { folderAfterChangeHook } from "./collectionHooks/folderAfterChange.mjs";
5
6
  import { createS3Store } from "./tus/stores/s3/index.mjs";
6
7
  import { createMuxClient, createMuxEndpoints } from "./utils/mux.mjs";
7
8
  import { createFileEndpoints } from "./utils/file.mjs";
@@ -59,11 +60,10 @@ function mediaCloudPlugin(options) {
59
60
  tusServer = getTusServer();
60
61
  }
61
62
  const baseCollection = config.collections?.find(({ slug }) => slug === pluginOptions.collection);
62
- const { view, folders } = pluginOptions;
63
+ const { view } = pluginOptions;
63
64
  const mediaCollection = getMediaCollection({
64
65
  baseCollection,
65
- view,
66
- folders
66
+ view
67
67
  });
68
68
  if (baseCollection) config = {
69
69
  ...config,
@@ -93,6 +93,22 @@ function mediaCloudPlugin(options) {
93
93
  clientUploads: true,
94
94
  disableLocalStorage: true
95
95
  } } };
96
+ if (pluginOptions.folders) {
97
+ const existingFoldersConfig = typeof config.folders === "object" && config.folders !== null ? config.folders : {};
98
+ config = {
99
+ ...config,
100
+ folders: {
101
+ ...existingFoldersConfig,
102
+ collectionOverrides: [...existingFoldersConfig.collectionOverrides ?? [], ({ collection }) => ({
103
+ ...collection,
104
+ hooks: {
105
+ ...collection.hooks,
106
+ afterChange: [...collection.hooks?.afterChange ?? [], folderAfterChangeHook]
107
+ }
108
+ })]
109
+ }
110
+ };
111
+ }
96
112
  const mergedConfig = {
97
113
  ...config,
98
114
  admin: {
@@ -118,7 +134,18 @@ function mediaCloudPlugin(options) {
118
134
  getS3Store,
119
135
  pluginOptions
120
136
  })
121
- ]
137
+ ],
138
+ custom: {
139
+ ...config.custom,
140
+ mediaCloud: { options: {
141
+ enabled: pluginOptions.enabled,
142
+ collection: pluginOptions.collection,
143
+ view: pluginOptions.view,
144
+ storage: pluginOptions.storage,
145
+ folders: pluginOptions.folders,
146
+ limits: pluginOptions.limits
147
+ } }
148
+ }
122
149
  };
123
150
  return cloudStoragePlugin(cloudStorageConfig)(mergedConfig);
124
151
  };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":["muxClient: Mux | null","s3Store: S3Store | null","tusServer: Server | null","mergedConfig: Config"],"sources":["../src/plugin.ts"],"sourcesContent":["import Mux from '@mux/mux-node'\nimport { defu } from 'defu'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\n\nimport { MediaCloudErrors } from './types/errors'\nimport { getStorageAdapter } from './adapter/storageAdapter'\nimport { getMediaCollection } from './collections/mediaCollection'\nimport { useErrorHandler } from './hooks/useErrorHandler'\nimport { createS3Store } from './tus/stores/s3'\nimport { createMuxClient, createMuxEndpoints } from './utils/mux'\nimport { createTusEndpoints, createTusServer } from './utils/tus'\nimport { createFileEndpoints } from './utils/file'\nimport { defaultOptions } from './utils/defaultOptions'\n\nimport type { Config } from 'payload'\nimport type { Server } from '@tus/server'\nimport type { MediaCloudPluginOptions } from './types'\nimport type { S3Store } from './tus/stores/s3/s3Store'\n\nconst { logError } = useErrorHandler()\n\nlet muxClient: Mux | null = null\nlet s3Store: S3Store | null = null\nlet tusServer: Server | null = null\n\n/**\n * Media Cloud Plugin for Payload CMS\n * @param options Configuration options\n * @returns Payload config function\n */\nexport function mediaCloudPlugin(options: MediaCloudPluginOptions) {\n return function (config: Config): Config {\n // Check if config is invalid or disabled\n if (!options) {\n logError(MediaCloudErrors.PLUGIN_NOT_CONFIGURED.message)\n return config\n }\n\n // Merge user options with default options\n const pluginOptions = defu(options, defaultOptions)\n\n // Check if the plugin is disabled\n if (pluginOptions.enabled === false) {\n return config\n }\n\n /**\n * Helper function to get or create Mux client instance\n * @returns Mux client instance\n */\n function getMuxClient(): Mux {\n return muxClient ?? createMuxClient(pluginOptions.mux)\n }\n\n /**\n * Helper function to get or create S3 store instance\n * @returns S3 store instance\n */\n function getS3Store(): S3Store {\n return s3Store ?? createS3Store(pluginOptions.s3)\n }\n\n /**\n * Helper function to get or create tus server instance\n * @returns TUS server instance\n */\n function getTusServer(): Server {\n return tusServer ?? createTusServer({ getS3Store, pluginOptions })\n }\n\n // Initialize Mux client if configuration is provided\n if (pluginOptions.mux) {\n muxClient = getMuxClient()\n }\n\n // Initialize S3 store and TUS server if configuration is provided\n if (pluginOptions.s3) {\n s3Store = getS3Store()\n tusServer = getTusServer()\n }\n\n // Check if base collection exists\n const baseCollection = config.collections?.find(\n ({ slug }) => slug === pluginOptions.collection\n )\n\n const { view, folders } = pluginOptions\n\n const mediaCollection = getMediaCollection({\n baseCollection,\n view,\n folders,\n })\n\n // Remove base collection\n // It’ll be replaced with the merged media collection\n if (baseCollection) {\n config = {\n ...config,\n collections:\n config.collections?.filter(\n ({ slug }) => slug !== baseCollection?.slug\n ) ?? [],\n }\n }\n\n initClientUploads({\n config,\n enabled: true,\n clientHandler:\n '@maas/payload-plugin-media-cloud/components#UploadHandler',\n collections: {\n [mediaCollection.slug]: {\n clientUploads: true,\n disableLocalStorage: true,\n prefix: pluginOptions.s3?.prefix ?? '',\n },\n },\n extraClientHandlerProps: () => ({\n pluginOptions,\n }),\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 [mediaCollection.slug]: {\n adapter: getStorageAdapter({\n getMuxClient,\n pluginOptions,\n getS3Store,\n }),\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n }\n\n const mergedConfig: Config = {\n ...config,\n admin: {\n ...config.admin,\n components: {\n ...config.admin?.components,\n providers: [\n ...(config.admin?.components?.providers ?? []),\n '@maas/payload-plugin-media-cloud/components#UploadManagerProvider',\n ],\n },\n },\n collections: [...(config.collections ?? []), mediaCollection],\n endpoints: [\n ...(config.endpoints ?? []),\n ...createTusEndpoints({ getTusServer, getS3Store, pluginOptions }),\n ...createMuxEndpoints({ getMuxClient, pluginOptions }),\n ...createFileEndpoints({ getS3Store, pluginOptions }),\n ],\n }\n\n return cloudStoragePlugin(cloudStorageConfig)(mergedConfig)\n }\n}\n\nexport { s3Store }\n"],"mappings":";;;;;;;;;;;;;;AAoBA,MAAM,EAAE,aAAa,iBAAiB;AAEtC,IAAIA,YAAwB;AAC5B,IAAIC,UAA0B;AAC9B,IAAIC,YAA2B;;;;;;AAO/B,SAAgB,iBAAiB,SAAkC;AACjE,QAAO,SAAU,QAAwB;AAEvC,MAAI,CAAC,SAAS;AACZ,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO;;EAIT,MAAM,gBAAgB,KAAK,SAAS,eAAe;AAGnD,MAAI,cAAc,YAAY,MAC5B,QAAO;;;;;EAOT,SAAS,eAAoB;AAC3B,UAAO,aAAa,gBAAgB,cAAc,IAAI;;;;;;EAOxD,SAAS,aAAsB;AAC7B,UAAO,WAAW,cAAc,cAAc,GAAG;;;;;;EAOnD,SAAS,eAAuB;AAC9B,UAAO,aAAa,gBAAgB;IAAE;IAAY;IAAe,CAAC;;AAIpE,MAAI,cAAc,IAChB,aAAY,cAAc;AAI5B,MAAI,cAAc,IAAI;AACpB,aAAU,YAAY;AACtB,eAAY,cAAc;;EAI5B,MAAM,iBAAiB,OAAO,aAAa,MACxC,EAAE,WAAW,SAAS,cAAc,WACtC;EAED,MAAM,EAAE,MAAM,YAAY;EAE1B,MAAM,kBAAkB,mBAAmB;GACzC;GACA;GACA;GACD,CAAC;AAIF,MAAI,eACF,UAAS;GACP,GAAG;GACH,aACE,OAAO,aAAa,QACjB,EAAE,WAAW,SAAS,gBAAgB,KACxC,IAAI,EAAE;GACV;AAGH,oBAAkB;GAChB;GACA,SAAS;GACT,eACE;GACF,aAAa,GACV,gBAAgB,OAAO;IACtB,eAAe;IACf,qBAAqB;IACrB,QAAQ,cAAc,IAAI,UAAU;IACrC,EACF;GACD,gCAAgC,EAC9B,eACD;GACD,qBAAqB;AACnB,WAAO,SAAS,KACd,EAAE,SAAS,qCAAqC,EAChD,EAAE,QAAQ,KAAK,CAChB;;GAEH,mBAAmB;GACpB,CAAC;EAEF,MAAM,qBAAqB,EACzB,aAAa,GACV,gBAAgB,OAAO;GACtB,SAAS,kBAAkB;IACzB;IACA;IACA;IACD,CAAC;GACF,eAAe;GACf,qBAAqB;GACtB,EACF,EACF;EAED,MAAMC,eAAuB;GAC3B,GAAG;GACH,OAAO;IACL,GAAG,OAAO;IACV,YAAY;KACV,GAAG,OAAO,OAAO;KACjB,WAAW,CACT,GAAI,OAAO,OAAO,YAAY,aAAa,EAAE,EAC7C,oEACD;KACF;IACF;GACD,aAAa,CAAC,GAAI,OAAO,eAAe,EAAE,EAAG,gBAAgB;GAC7D,WAAW;IACT,GAAI,OAAO,aAAa,EAAE;IAC1B,GAAG,mBAAmB;KAAE;KAAc;KAAY;KAAe,CAAC;IAClE,GAAG,mBAAmB;KAAE;KAAc;KAAe,CAAC;IACtD,GAAG,oBAAoB;KAAE;KAAY;KAAe,CAAC;IACtD;GACF;AAED,SAAO,mBAAmB,mBAAmB,CAAC,aAAa"}
1
+ {"version":3,"file":"plugin.mjs","names":["muxClient: Mux | null","s3Store: S3Store | null","tusServer: Server | null","mergedConfig: Config"],"sources":["../src/plugin.ts"],"sourcesContent":["import Mux from '@mux/mux-node'\nimport { defu } from 'defu'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport { initClientUploads } from '@payloadcms/plugin-cloud-storage/utilities'\n\nimport { MediaCloudErrors } from './types/errors'\nimport { getStorageAdapter } from './adapter/storageAdapter'\nimport { getMediaCollection } from './collections/mediaCollection'\nimport { folderAfterChangeHook } from './collectionHooks/folderAfterChange'\nimport { useErrorHandler } from './hooks/useErrorHandler'\nimport { createS3Store } from './tus/stores/s3'\nimport { createMuxClient, createMuxEndpoints } from './utils/mux'\nimport { createTusEndpoints, createTusServer } from './utils/tus'\nimport { createFileEndpoints } from './utils/file'\nimport { defaultOptions } from './utils/defaultOptions'\n\nimport type { Config } from 'payload'\nimport type { Server } from '@tus/server'\nimport type { MediaCloudPluginOptions } from './types'\nimport type { S3Store } from './tus/stores/s3/s3Store'\n\nconst { logError } = useErrorHandler()\n\nlet muxClient: Mux | null = null\nlet s3Store: S3Store | null = null\nlet tusServer: Server | null = null\n\n/**\n * Media Cloud Plugin for Payload CMS\n * @param options Configuration options\n * @returns Payload config function\n */\nexport function mediaCloudPlugin(options: MediaCloudPluginOptions) {\n return function (config: Config): Config {\n // Check if config is invalid or disabled\n if (!options) {\n logError(MediaCloudErrors.PLUGIN_NOT_CONFIGURED.message)\n return config\n }\n\n // Merge user options with default options\n const pluginOptions = defu(options, defaultOptions)\n\n // Check if the plugin is disabled\n if (pluginOptions.enabled === false) {\n return config\n }\n\n /**\n * Helper function to get or create Mux client instance\n * @returns Mux client instance\n */\n function getMuxClient(): Mux {\n return muxClient ?? createMuxClient(pluginOptions.mux)\n }\n\n /**\n * Helper function to get or create S3 store instance\n * @returns S3 store instance\n */\n function getS3Store(): S3Store {\n return s3Store ?? createS3Store(pluginOptions.s3)\n }\n\n /**\n * Helper function to get or create tus server instance\n * @returns TUS server instance\n */\n function getTusServer(): Server {\n return tusServer ?? createTusServer({ getS3Store, pluginOptions })\n }\n\n // Initialize Mux client if configuration is provided\n if (pluginOptions.mux) {\n muxClient = getMuxClient()\n }\n\n // Initialize S3 store and TUS server if configuration is provided\n if (pluginOptions.s3) {\n s3Store = getS3Store()\n tusServer = getTusServer()\n }\n\n // Check if base media collection exists\n const baseCollection = config.collections?.find(\n ({ slug }) => slug === pluginOptions.collection\n )\n\n const { view } = pluginOptions\n const mediaCollection = getMediaCollection({\n baseCollection: baseCollection,\n view,\n })\n\n // Remove base media collection\n // It’ll be replaced with the merged media collection\n if (baseCollection) {\n config = {\n ...config,\n collections:\n config.collections?.filter(\n ({ slug }) => slug !== baseCollection?.slug\n ) ?? [],\n }\n }\n\n initClientUploads({\n config,\n enabled: true,\n clientHandler:\n '@maas/payload-plugin-media-cloud/components#UploadHandler',\n collections: {\n [mediaCollection.slug]: {\n clientUploads: true,\n disableLocalStorage: true,\n prefix: pluginOptions.s3?.prefix ?? '',\n },\n },\n extraClientHandlerProps: () => ({\n pluginOptions,\n }),\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 [mediaCollection.slug]: {\n adapter: getStorageAdapter({\n getMuxClient,\n pluginOptions,\n getS3Store,\n }),\n clientUploads: true,\n disableLocalStorage: true,\n },\n },\n }\n\n // Inject folderAfterChangeHook into folder collection\n if (pluginOptions.folders) {\n const existingFoldersConfig =\n typeof config.folders === 'object' && config.folders !== null\n ? config.folders\n : {}\n\n config = {\n ...config,\n folders: {\n ...existingFoldersConfig,\n collectionOverrides: [\n ...(existingFoldersConfig.collectionOverrides ?? []),\n ({ collection }) => ({\n ...collection,\n hooks: {\n ...collection.hooks,\n afterChange: [\n ...(collection.hooks?.afterChange ?? []),\n folderAfterChangeHook,\n ],\n },\n }),\n ],\n },\n }\n }\n\n const mergedConfig: Config = {\n ...config,\n admin: {\n ...config.admin,\n components: {\n ...config.admin?.components,\n providers: [\n ...(config.admin?.components?.providers ?? []),\n '@maas/payload-plugin-media-cloud/components#UploadManagerProvider',\n ],\n },\n },\n collections: [...(config.collections ?? []), mediaCollection],\n endpoints: [\n ...(config.endpoints ?? []),\n ...createTusEndpoints({ getTusServer, getS3Store, pluginOptions }),\n ...createMuxEndpoints({ getMuxClient, pluginOptions }),\n ...createFileEndpoints({ getS3Store, pluginOptions }),\n ],\n custom: {\n ...config.custom,\n mediaCloud: {\n options: {\n enabled: pluginOptions.enabled,\n collection: pluginOptions.collection,\n view: pluginOptions.view,\n storage: pluginOptions.storage,\n folders: pluginOptions.folders,\n limits: pluginOptions.limits,\n },\n },\n },\n }\n\n return cloudStoragePlugin(cloudStorageConfig)(mergedConfig)\n }\n}\n\nexport { s3Store }\n"],"mappings":";;;;;;;;;;;;;;;AAqBA,MAAM,EAAE,aAAa,iBAAiB;AAEtC,IAAIA,YAAwB;AAC5B,IAAIC,UAA0B;AAC9B,IAAIC,YAA2B;;;;;;AAO/B,SAAgB,iBAAiB,SAAkC;AACjE,QAAO,SAAU,QAAwB;AAEvC,MAAI,CAAC,SAAS;AACZ,YAAS,iBAAiB,sBAAsB,QAAQ;AACxD,UAAO;;EAIT,MAAM,gBAAgB,KAAK,SAAS,eAAe;AAGnD,MAAI,cAAc,YAAY,MAC5B,QAAO;;;;;EAOT,SAAS,eAAoB;AAC3B,UAAO,aAAa,gBAAgB,cAAc,IAAI;;;;;;EAOxD,SAAS,aAAsB;AAC7B,UAAO,WAAW,cAAc,cAAc,GAAG;;;;;;EAOnD,SAAS,eAAuB;AAC9B,UAAO,aAAa,gBAAgB;IAAE;IAAY;IAAe,CAAC;;AAIpE,MAAI,cAAc,IAChB,aAAY,cAAc;AAI5B,MAAI,cAAc,IAAI;AACpB,aAAU,YAAY;AACtB,eAAY,cAAc;;EAI5B,MAAM,iBAAiB,OAAO,aAAa,MACxC,EAAE,WAAW,SAAS,cAAc,WACtC;EAED,MAAM,EAAE,SAAS;EACjB,MAAM,kBAAkB,mBAAmB;GACzB;GAChB;GACD,CAAC;AAIF,MAAI,eACF,UAAS;GACP,GAAG;GACH,aACE,OAAO,aAAa,QACjB,EAAE,WAAW,SAAS,gBAAgB,KACxC,IAAI,EAAE;GACV;AAGH,oBAAkB;GAChB;GACA,SAAS;GACT,eACE;GACF,aAAa,GACV,gBAAgB,OAAO;IACtB,eAAe;IACf,qBAAqB;IACrB,QAAQ,cAAc,IAAI,UAAU;IACrC,EACF;GACD,gCAAgC,EAC9B,eACD;GACD,qBAAqB;AACnB,WAAO,SAAS,KACd,EAAE,SAAS,qCAAqC,EAChD,EAAE,QAAQ,KAAK,CAChB;;GAEH,mBAAmB;GACpB,CAAC;EAEF,MAAM,qBAAqB,EACzB,aAAa,GACV,gBAAgB,OAAO;GACtB,SAAS,kBAAkB;IACzB;IACA;IACA;IACD,CAAC;GACF,eAAe;GACf,qBAAqB;GACtB,EACF,EACF;AAGD,MAAI,cAAc,SAAS;GACzB,MAAM,wBACJ,OAAO,OAAO,YAAY,YAAY,OAAO,YAAY,OACrD,OAAO,UACP,EAAE;AAER,YAAS;IACP,GAAG;IACH,SAAS;KACP,GAAG;KACH,qBAAqB,CACnB,GAAI,sBAAsB,uBAAuB,EAAE,GAClD,EAAE,kBAAkB;MACnB,GAAG;MACH,OAAO;OACL,GAAG,WAAW;OACd,aAAa,CACX,GAAI,WAAW,OAAO,eAAe,EAAE,EACvC,sBACD;OACF;MACF,EACF;KACF;IACF;;EAGH,MAAMC,eAAuB;GAC3B,GAAG;GACH,OAAO;IACL,GAAG,OAAO;IACV,YAAY;KACV,GAAG,OAAO,OAAO;KACjB,WAAW,CACT,GAAI,OAAO,OAAO,YAAY,aAAa,EAAE,EAC7C,oEACD;KACF;IACF;GACD,aAAa,CAAC,GAAI,OAAO,eAAe,EAAE,EAAG,gBAAgB;GAC7D,WAAW;IACT,GAAI,OAAO,aAAa,EAAE;IAC1B,GAAG,mBAAmB;KAAE;KAAc;KAAY;KAAe,CAAC;IAClE,GAAG,mBAAmB;KAAE;KAAc;KAAe,CAAC;IACtD,GAAG,oBAAoB;KAAE;KAAY;KAAe,CAAC;IACtD;GACD,QAAQ;IACN,GAAG,OAAO;IACV,YAAY,EACV,SAAS;KACP,SAAS,cAAc;KACvB,YAAY,cAAc;KAC1B,MAAM,cAAc;KACpB,SAAS,cAAc;KACvB,SAAS,cAAc;KACvB,QAAQ,cAAc;KACvB,EACF;IACF;GACF;AAED,SAAO,mBAAmB,mBAAmB,CAAC,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore, SemaphorePermit } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n\n const params = {\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n Bucket: this.bucket,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n } catch (error) {\n throwError({\n ...MediaCloudErrors.S3_DELETE_ERROR,\n cause: error,\n })\n }\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (error) => {\n fileReader.destroy(error)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n }\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n readable.destroy(error)\n fs.unlink(path, () => {})\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"partsManager.mjs","names":["client: S3","bucket: string","minPartSize: number","partUploadSemaphore: Semaphore","metadataManager: S3MetadataManager","fileOperations: S3FileOperations","generateCompleteTag: (value: 'false' | 'true') => string | undefined","params: AWS.ListPartsCommandInput","params: AWS.UploadPartCommandInput","promises: Promise<void>[]","pendingChunkFilepath: null | string"],"sources":["../../../../src/tus/stores/s3/partsManager.ts"],"sourcesContent":["import fs from 'node:fs'\nimport os from 'node:os'\nimport stream from 'node:stream'\n\nimport { NoSuchKey, NotFound, type S3 } from '@aws-sdk/client-s3'\nimport { StreamSplitter } from '@tus/utils'\n\nimport { useErrorHandler } from '../../../hooks/useErrorHandler'\nimport { MediaCloudErrors, MediaCloudLogs } from '../../../types/errors'\n\nimport type AWS from '@aws-sdk/client-s3'\nimport type { Readable } from 'node:stream'\nimport type { IncompletePartInfo, TusUploadMetadata } from '../../../types'\nimport type { S3FileOperations } from './fileOperations'\nimport type { S3MetadataManager } from './metadataManager'\nimport type { Semaphore } from './semaphore'\n\ntype RetrievePartsArgs = {\n id: string\n partNumberMarker?: string\n}\n\ntype FinishMultipartUploadArgs = {\n metadata: TusUploadMetadata\n parts: Array<AWS.Part>\n}\n\ntype GetIncompletePartArgs = {\n id: string\n}\n\ntype GetIncompletePartSizeArgs = {\n id: string\n}\n\ntype DeleteIncompletePartArgs = {\n id: string\n}\n\ntype DownloadIncompletePartArgs = {\n id: string\n}\n\ntype UploadIncompletePartArgs = {\n id: string\n readStream: fs.ReadStream | Readable\n}\n\ntype UploadPartArgs = {\n metadata: TusUploadMetadata\n readStream: fs.ReadStream | Readable\n partNumber: number\n}\n\ntype UploadPartsArgs = {\n metadata: TusUploadMetadata\n readStream: stream.Readable\n currentPartNumber: number\n offset: number\n}\n\nconst { log, throwError } = useErrorHandler()\n\nexport class S3PartsManager {\n constructor(\n private client: S3,\n private bucket: string,\n private minPartSize: number,\n private partUploadSemaphore: Semaphore,\n private metadataManager: S3MetadataManager,\n private fileOperations: S3FileOperations,\n private generateCompleteTag: (value: 'false' | 'true') => string | undefined\n ) {}\n\n /**\n * Gets the number of complete parts/chunks already uploaded to S3.\n * Retrieves only consecutive parts.\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.partNumberMarker - Marker for pagination (optional)\n * @returns Promise that resolves to array of uploaded parts\n */\n async retrieveParts(args: RetrievePartsArgs): Promise<Array<AWS.Part>> {\n const { id, partNumberMarker } = args\n const metadata = await this.metadataManager.getMetadata({ id })\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.ListPartsCommandInput = {\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumberMarker: partNumberMarker,\n UploadId: metadata['upload-id'],\n }\n\n const data = await this.client.listParts(params)\n\n let parts = data.Parts ?? []\n\n if (data.IsTruncated) {\n const rest = await this.retrieveParts({\n id,\n partNumberMarker: data.NextPartNumberMarker,\n })\n parts = [...parts, ...rest]\n }\n\n if (!partNumberMarker) {\n parts.sort((a, b) => (a.PartNumber || 0) - (b.PartNumber || 0))\n }\n\n return parts\n }\n\n /**\n * Completes a multipart upload on S3.\n * This is where S3 concatenates all the uploaded parts.\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.parts - Array of uploaded parts to complete\n * @returns Promise that resolves to the location URL (optional)\n */\n async finishMultipartUpload(\n args: FinishMultipartUploadArgs\n ): Promise<string | undefined> {\n const { metadata, parts } = args\n\n const params = {\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n Bucket: this.bucket,\n MultipartUpload: {\n Parts: parts.map((part) => {\n return {\n ETag: part.ETag,\n PartNumber: part.PartNumber,\n }\n }),\n },\n UploadId: metadata['upload-id'],\n }\n\n try {\n const result = await this.client.completeMultipartUpload(params)\n return result.Location\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw new Error() // This will never execute but satisfies TypeScript\n }\n }\n\n /**\n * Gets incomplete part from S3\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to readable stream or undefined if not found\n */\n async getIncompletePart(\n args: GetIncompletePartArgs\n ): Promise<Readable | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.getObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n prefix: file?.metadata?.prefix ?? undefined,\n isIncomplete: true,\n }),\n })\n return data.Body as Readable\n } catch (error) {\n if (error instanceof NoSuchKey) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Gets the size of an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to part size or undefined if not found\n */\n async getIncompletePartSize(\n args: GetIncompletePartSizeArgs\n ): Promise<number | undefined> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.headObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n return data.ContentLength\n } catch (error) {\n if (error instanceof NotFound) {\n return undefined\n }\n throw error\n }\n }\n\n /**\n * Deletes an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves when deletion is complete\n */\n async deleteIncompletePart(args: DeleteIncompletePartArgs): Promise<void> {\n const { id } = args\n\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n await this.client.deleteObject({\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n })\n } catch (error) {\n throwError({\n ...MediaCloudErrors.S3_DELETE_ERROR,\n cause: error,\n })\n }\n }\n\n /**\n * Downloads incomplete part to temporary file\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @returns Promise that resolves to incomplete part info or undefined if not found\n */\n async downloadIncompletePart(\n args: DownloadIncompletePartArgs\n ): Promise<IncompletePartInfo | undefined> {\n const { id } = args\n const incompletePart = await this.getIncompletePart({ id })\n\n if (!incompletePart) {\n return\n }\n const filePath = await this.fileOperations.generateUniqueTmpFileName({\n template: 'tus-s3-incomplete-part-',\n })\n\n try {\n let incompletePartSize = 0\n\n const byteCounterTransform = new stream.Transform({\n transform(chunk, _, callback) {\n incompletePartSize += chunk.length\n callback(null, chunk)\n },\n })\n\n // Write to temporary file\n await stream.promises.pipeline(\n incompletePart,\n byteCounterTransform,\n fs.createWriteStream(filePath)\n )\n\n const createReadStream = (options: { cleanUpOnEnd: boolean }) => {\n const fileReader = fs.createReadStream(filePath)\n\n if (options.cleanUpOnEnd) {\n fileReader.on('end', () => {\n fs.unlink(filePath, () => {})\n })\n\n fileReader.on('error', (error) => {\n fileReader.destroy(error)\n fs.unlink(filePath, () => {})\n })\n }\n\n return fileReader\n }\n\n return {\n createReader: createReadStream,\n path: filePath,\n size: incompletePartSize,\n }\n } catch (err) {\n fs.promises.rm(filePath).catch(() => {})\n throw err\n }\n }\n\n /**\n * Uploads an incomplete part\n * @param args - The function arguments\n * @param args.id - The upload ID\n * @param args.readStream - The stream to read data from\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadIncompletePart(args: UploadIncompletePartArgs): Promise<string> {\n const { id, readStream } = args\n try {\n const { file } = await this.metadataManager.getMetadata({ id })\n\n const data = await this.client.putObject({\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id,\n isIncomplete: true,\n prefix: file?.metadata?.prefix ?? undefined,\n }),\n Tagging: this.generateCompleteTag('false'),\n })\n log(MediaCloudLogs.S3_STORE_INCOMPLETE_PART_UPLOADED)\n return data.ETag as string\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n }\n }\n\n /**\n * Uploads a single part\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.partNumber - The part number to upload\n * @returns Promise that resolves to the ETag of the uploaded part\n */\n async uploadPart(args: UploadPartArgs): Promise<AWS.Part> {\n const { metadata, readStream, partNumber } = args\n const permit = await this.partUploadSemaphore.acquire()\n\n if (!metadata['upload-id']) {\n throwError(MediaCloudErrors.MUX_UPLOAD_ID_MISSING)\n throw new Error() // This will never execute but satisfies TypeScript\n }\n\n const params: AWS.UploadPartCommandInput = {\n Body: readStream,\n Bucket: this.bucket,\n Key: this.metadataManager.generatePartKey({\n id: metadata.file.id,\n prefix: metadata.file.metadata?.prefix ?? undefined,\n }),\n PartNumber: partNumber,\n UploadId: metadata['upload-id'],\n }\n\n try {\n const data = await this.client.uploadPart(params)\n return { ETag: data.ETag, PartNumber: partNumber }\n } catch (error) {\n throwError({ ...MediaCloudErrors.TUS_UPLOAD_ERROR, cause: error })\n throw error\n } finally {\n permit()\n }\n }\n\n /**\n * Uploads a stream to s3 using multiple parts\n * @param args - The function arguments\n * @param args.metadata - The upload metadata\n * @param args.readStream - The stream to read data from\n * @param args.currentPartNumber - The current part number to start from\n * @param args.offset - The byte offset to start from\n * @returns Promise that resolves to the number of bytes uploaded\n */\n async uploadParts(args: UploadPartsArgs): Promise<number> {\n const { metadata, readStream, offset: initialOffset } = args\n let { currentPartNumber } = args\n let offset = initialOffset\n const size = metadata.file.size\n const promises: Promise<void>[] = []\n let pendingChunkFilepath: null | string = null\n let bytesUploaded = 0\n\n const splitterStream = new StreamSplitter({\n chunkSize: this.fileOperations.calculateOptimalPartSize({ size }),\n directory: os.tmpdir(),\n })\n .on('chunkStarted', (filepath) => {\n pendingChunkFilepath = filepath\n })\n .on('chunkFinished', ({ path, size: partSize }) => {\n pendingChunkFilepath = null\n\n const partNumber = currentPartNumber++\n\n offset += partSize\n\n const isFinalPart = size === offset\n\n const uploadChunk = async () => {\n try {\n // Only the first chunk of each PATCH request can prepend\n // an incomplete part (last chunk) from the previous request.\n const readable = fs.createReadStream(path)\n readable.on('error', function (error) {\n readable.destroy(error)\n fs.unlink(path, () => {})\n })\n\n switch (true) {\n case partSize >= this.minPartSize || isFinalPart:\n await this.uploadPart({\n metadata,\n readStream: readable,\n partNumber,\n })\n break\n default:\n await this.uploadIncompletePart({\n id: metadata.file.id,\n readStream: readable,\n })\n break\n }\n\n bytesUploaded += partSize\n } catch (error) {\n // Destroy the splitter to stop processing more chunks\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n splitterStream.destroy(mappedError)\n throw mappedError\n } finally {\n fs.promises.rm(path).catch(function () {})\n }\n }\n\n const deferred = uploadChunk()\n\n promises.push(deferred)\n })\n\n try {\n await stream.promises.pipeline(readStream, splitterStream)\n } catch (error) {\n if (pendingChunkFilepath !== null) {\n try {\n await fs.promises.rm(pendingChunkFilepath)\n } catch {\n log(MediaCloudLogs.S3_STORE_CHUNK_REMOVAL_FAILED)\n }\n }\n const mappedError =\n error instanceof Error ? error : new Error(String(error))\n promises.push(Promise.reject(mappedError))\n } finally {\n // Wait for all promises\n await Promise.allSettled(promises)\n // Reject the promise if any of the promises reject\n await Promise.all(promises)\n }\n\n return bytesUploaded\n }\n}\n"],"mappings":"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"}
@@ -10,7 +10,6 @@ declare class Semaphore {
10
10
  private release;
11
11
  acquire(): Promise<() => void>;
12
12
  }
13
- type SemaphorePermit = () => void;
14
13
  //#endregion
15
- export { Semaphore, SemaphorePermit };
14
+ export { Semaphore };
16
15
  //# sourceMappingURL=semaphore.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"semaphore.mjs","names":[],"sources":["../../../../src/tus/stores/s3/semaphore.ts"],"sourcesContent":["/**\n * A semaphore implementation for controlling concurrent operations.\n * Used to limit the number of simultaneous part uploads to S3.\n */\nexport class Semaphore {\n private permits: number\n private queue: (() => void)[] = []\n\n constructor(permits: number) {\n this.permits = permits\n }\n\n private release(): void {\n this.permits++\n const next = this.queue.shift()\n if (next) {\n next()\n }\n }\n\n async acquire(): Promise<() => void> {\n return new Promise((resolve) => {\n if (this.permits > 0) {\n this.permits--\n resolve(() => this.release())\n } else {\n this.queue.push(() => {\n this.permits--\n resolve(() => this.release())\n })\n }\n })\n }\n}\n\nexport type SemaphorePermit = () => void\n"],"mappings":";;;;;AAIA,IAAa,YAAb,MAAuB;CAIrB,YAAY,SAAiB;eAFG,EAAE;AAGhC,OAAK,UAAU;;CAGjB,AAAQ,UAAgB;AACtB,OAAK;EACL,MAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,MAAI,KACF,OAAM;;CAIV,MAAM,UAA+B;AACnC,SAAO,IAAI,SAAS,YAAY;AAC9B,OAAI,KAAK,UAAU,GAAG;AACpB,SAAK;AACL,kBAAc,KAAK,SAAS,CAAC;SAE7B,MAAK,MAAM,WAAW;AACpB,SAAK;AACL,kBAAc,KAAK,SAAS,CAAC;KAC7B;IAEJ"}
1
+ {"version":3,"file":"semaphore.mjs","names":[],"sources":["../../../../src/tus/stores/s3/semaphore.ts"],"sourcesContent":["/**\n * A semaphore implementation for controlling concurrent operations.\n * Used to limit the number of simultaneous part uploads to S3.\n */\nexport class Semaphore {\n private permits: number\n private queue: (() => void)[] = []\n\n constructor(permits: number) {\n this.permits = permits\n }\n\n private release(): void {\n this.permits++\n const next = this.queue.shift()\n if (next) {\n next()\n }\n }\n\n async acquire(): Promise<() => void> {\n return new Promise((resolve) => {\n if (this.permits > 0) {\n this.permits--\n resolve(() => this.release())\n } else {\n this.queue.push(() => {\n this.permits--\n resolve(() => this.release())\n })\n }\n })\n }\n}\n"],"mappings":";;;;;AAIA,IAAa,YAAb,MAAuB;CAIrB,YAAY,SAAiB;eAFG,EAAE;AAGhC,OAAK,UAAU;;CAGjB,AAAQ,UAAgB;AACtB,OAAK;EACL,MAAM,OAAO,KAAK,MAAM,OAAO;AAC/B,MAAI,KACF,OAAM;;CAIV,MAAM,UAA+B;AACnC,SAAO,IAAI,SAAS,YAAY;AAC9B,OAAI,KAAK,UAAU,GAAG;AACpB,SAAK;AACL,kBAAc,KAAK,SAAS,CAAC;SAE7B,MAAK,MAAM,WAAW;AACpB,SAAK;AACL,kBAAc,KAAK,SAAS,CAAC;KAC7B;IAEJ"}
@@ -84,6 +84,10 @@ declare const MediaCloudErrors: {
84
84
  readonly message: "Error generating thumbnail URL";
85
85
  readonly errorCode: 500;
86
86
  };
87
+ readonly PATH_UPDATE_ERROR: {
88
+ readonly message: "Error updating media path";
89
+ readonly errorCode: 500;
90
+ };
87
91
  readonly FILE_TYPE_UNKNOWN: {
88
92
  readonly message: "Unable to determine file type";
89
93
  readonly errorCode: 400;
@@ -84,6 +84,10 @@ const MediaCloudErrors = {
84
84
  message: "Error generating thumbnail URL",
85
85
  errorCode: 500
86
86
  },
87
+ PATH_UPDATE_ERROR: {
88
+ message: "Error updating media path",
89
+ errorCode: 500
90
+ },
87
91
  FILE_TYPE_UNKNOWN: {
88
92
  message: "Unable to determine file type",
89
93
  errorCode: 400
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\n },\n THUMBNAIL_GENERATION_ERROR: {\n message: 'Error generating thumbnail URL',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n UPLOAD_QUEUE_NOT_INITIALIZED: {\n message: 'Upload queue not initialized',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,4BAA4B;EAC1B,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,8BAA8B;EAC5B,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/types/errors.ts"],"sourcesContent":["export const MediaCloudErrors = {\n // Mux Errors\n MUX_CONFIG_MISSING: {\n message:\n 'Mux configuration (tokenId and tokenSecret) must be provided in pluginOptions to use Mux',\n errorCode: 400,\n },\n MUX_CONFIG_INCOMPLETE: {\n message: 'Mux configuration is missing. Mux features will not be available',\n errorCode: 400,\n },\n MUX_UPLOAD_ID_MISSING: {\n message: 'No upload-id found for upload',\n errorCode: 400,\n },\n MUX_ASSET_DELETE_ERROR: {\n message: 'Error deleting Mux asset',\n errorCode: 500,\n },\n MUX_UPLOAD_ERROR: {\n message: 'Mux video upload failed',\n errorCode: 500,\n },\n MUX_DIRECT_UPLOAD_ERROR: {\n message: 'Mux direct upload failed',\n errorCode: 500,\n },\n MUX_CREATE_UPLOAD_ERROR: {\n message: 'Error in Mux create upload handler',\n errorCode: 500,\n },\n MUX_REQUEST_NO_JSON: {\n message: 'Request does not support json() method',\n errorCode: 400,\n },\n MUX_WEBHOOK_BODY_INVALID: {\n message: 'Invalid Mux webhook body',\n errorCode: 400,\n },\n\n // S3 Errors\n S3_CONFIG_MISSING: {\n message:\n 'S3 configuration (bucket, region, accessKeyId, secretAccessKey) must be provided in pluginOptions',\n errorCode: 400,\n },\n S3_DELETE_ERROR: {\n message: 'Error deleting file from S3',\n errorCode: 500,\n },\n S3_RENAME_ERROR: {\n message: 'Error renaming file in S3',\n errorCode: 500,\n },\n S3_MOVE_ERROR: {\n message: 'Error moving file in S3',\n errorCode: 500,\n },\n S3_PREFIX_ERROR: {\n message: 'Error updating S3 prefix',\n errorCode: 500,\n },\n S3_GET_OBJECT_ERROR: {\n message: 'Error getting object from S3',\n errorCode: 500,\n },\n S3_UNIQUE_NAME_ERROR: {\n message: 'Could not find a unique file name after maximum tries',\n errorCode: 500,\n },\n\n // TUS Errors\n TUS_UPLOAD_ERROR: {\n message: 'TUS file upload error occurred',\n errorCode: 500,\n },\n TUS_CLEANUP_ERROR: {\n message: 'Error during cleanup',\n errorCode: 500,\n },\n\n // General Errors\n UNKNOWN_STORAGE_TYPE: {\n message: 'Unknown storage type for media',\n errorCode: 500,\n },\n COLLECTION_REQUIRED: {\n message: 'A collection must be specified in the plugin options',\n errorCode: 400,\n },\n THUMBNAIL_GENERATION_ERROR: {\n message: 'Error generating thumbnail URL',\n errorCode: 500,\n },\n PATH_UPDATE_ERROR: {\n message: 'Error updating media path',\n errorCode: 500,\n },\n\n // File Errors\n FILE_TYPE_UNKNOWN: {\n message: 'Unable to determine file type',\n errorCode: 400,\n },\n FILE_TYPE_ERROR: {\n message: 'Error determining file type',\n errorCode: 500,\n },\n FILE_TYPE_UNSUPPORTED_ERROR: {\n message: 'Unsupported file type',\n errorCode: 400,\n },\n FILE_TYPE_NOT_ALLOWED: {\n message: 'File type not allowed',\n errorCode: 400,\n },\n FILE_SIZE_EXCEEDED: {\n message: 'File size exceeds the maximum allowed limit',\n errorCode: 400,\n },\n FILENAME_SANITIZE_ERROR: {\n message: 'Error sanitizing filename',\n errorCode: 500,\n },\n FILE_MISSING_NAME: {\n message: 'Filename is missing, cannot parse file',\n errorCode: 500,\n },\n FILE_NOT_FOUND: {\n message: 'File not found',\n errorCode: 404,\n },\n FILE_DIMENSIONS_ERROR: {\n message: 'Error setting media dimensions',\n errorCode: 500,\n },\n\n // Upload Errors\n UPLOAD_NO_URL: {\n message: 'No upload URL provided, cannot parse upload ID',\n errorCode: 400,\n },\n UPLOAD_HANDLER_ERROR: {\n message: 'Upload handler error occurred',\n errorCode: 500,\n },\n UPLOAD_POLLING_ERROR: {\n message: 'Polling error for upload',\n errorCode: 500,\n },\n UPLOAD_QUEUE_NOT_INITIALIZED: {\n message: 'Upload queue not initialized',\n errorCode: 500,\n },\n\n // Plugin Errors\n PLUGIN_NOT_CONFIGURED: {\n message: 'Payload Media Cloud plugin is not configured',\n errorCode: 500,\n },\n NAMING_FUNCTION_ERROR: {\n message: 'Error in namingFunction',\n errorCode: 500,\n },\n\n // HOOK Errors\n FOLDER_FETCH_ERROR: {\n message: 'Error fetching folder data',\n errorCode: 500,\n },\n} as const\n\nexport enum MediaCloudLogs {\n S3_STORE_MULTIPART_INIT = 'Initializing multipart upload',\n S3_STORE_MULTIPART_CREATED = 'Multipart upload created',\n S3_STORE_UPLOAD_FAILED = 'Failed to finish upload',\n S3_STORE_READ_ATTEMPT = 'Attempting to read file from S3',\n S3_STORE_READ_SUCCESS = 'Successfully read file from S3',\n S3_STORE_READ_RETRY = 'Failed to read file, trying again',\n S3_STORE_READ_FAILED = 'Failed to read file',\n S3_STORE_FILE_NOT_FOUND = 'No file found',\n S3_STORE_FILE_FOUND = 'File found',\n S3_STORE_RETRIEVE_PARTS_ERROR = 'Error retrieving parts',\n S3_STORE_METADATA_SAVING = 'Saving metadata',\n S3_STORE_METADATA_SAVED = 'Metadata file saved',\n S3_STORE_INCOMPLETE_PART_UPLOADED = 'Finished uploading incomplete part',\n S3_STORE_CHUNK_REMOVAL_FAILED = 'Failed to remove chunk',\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;CAE9B,oBAAoB;EAClB,SACE;EACF,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,wBAAwB;EACtB,SAAS;EACT,WAAW;EACZ;CACD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,0BAA0B;EACxB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SACE;EACF,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CAGD,kBAAkB;EAChB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,qBAAqB;EACnB,SAAS;EACT,WAAW;EACZ;CACD,4BAA4B;EAC1B,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CAGD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,iBAAiB;EACf,SAAS;EACT,WAAW;EACZ;CACD,6BAA6B;EAC3B,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACD,yBAAyB;EACvB,SAAS;EACT,WAAW;EACZ;CACD,mBAAmB;EACjB,SAAS;EACT,WAAW;EACZ;CACD,gBAAgB;EACd,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,eAAe;EACb,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,sBAAsB;EACpB,SAAS;EACT,WAAW;EACZ;CACD,8BAA8B;EAC5B,SAAS;EACT,WAAW;EACZ;CAGD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CACD,uBAAuB;EACrB,SAAS;EACT,WAAW;EACZ;CAGD,oBAAoB;EAClB,SAAS;EACT,WAAW;EACZ;CACF;AAED,IAAY,4DAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -1,6 +1,6 @@
1
1
  import { MediaCloudPluginOptions, MimeType } from "../types/index.mjs";
2
2
  import { S3Store } from "../tus/stores/s3/s3Store.mjs";
3
- import * as payload1 from "payload";
3
+ import * as payload0 from "payload";
4
4
 
5
5
  //#region src/utils/file.d.ts
6
6
 
@@ -35,7 +35,7 @@ interface CreateFileEndpointsArgs {
35
35
  pluginOptions: MediaCloudPluginOptions;
36
36
  }
37
37
  declare function createFileEndpoints(args: CreateFileEndpointsArgs): {
38
- handler: payload1.PayloadHandler;
38
+ handler: payload0.PayloadHandler;
39
39
  method: "get";
40
40
  path: string;
41
41
  }[];
@@ -29,6 +29,7 @@ function createTusEndpoints(args) {
29
29
  const { getTusServer, getS3Store, pluginOptions } = args;
30
30
  const collection = pluginOptions.collection;
31
31
  magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED);
32
+ const folders = pluginOptions.folders ?? false;
32
33
  /**
33
34
  * Handles TUS requests through the server
34
35
  * @param req - The payload request object
@@ -79,7 +80,8 @@ function createTusEndpoints(args) {
79
80
  {
80
81
  handler: getTusFolderHandler({
81
82
  getS3Store,
82
- collection
83
+ collection,
84
+ folders
83
85
  }),
84
86
  method: "get",
85
87
  path: "/uploads/:filename/folder"
@@ -1 +1 @@
1
- {"version":3,"file":"tus.mjs","names":["magicError: UseMagicErrorReturn","TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport { getTusFolderHandler } from '../endpoints/tusFolderHandler'\nimport { getTusCleanupHandler } from '../endpoints/tusCleanupHandler'\nimport { generateUniqueFilename } from './file'\n\nimport { useMagicError, type UseMagicErrorReturn } from '@maas/error-handler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { PayloadRequest } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\nimport type { MediaCloudPluginOptions } from '../types'\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.pluginOptions - Media cloud plugin options\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { getS3Store, pluginOptions } = args\n\n const s3Store = getS3Store()\n\n return new TusServer({\n datastore: s3Store,\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3?.respectForwardedHeaders ?? true,\n namingFunction: async (_req, metadata) => {\n return metadata?.filename ?? generateUniqueFilename('')\n },\n onUploadFinish: async (_req, upload) => {\n // Clean up .part and .info files\n const filename = upload.metadata?.filename ?? ''\n await s3Store.cleanup(filename)\n\n // Prevent type error\n return Promise.resolve({})\n },\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param args.getTusServer - Function that returns a TUS server instance\n * @param args.getS3Store - Function that returns an S3 client instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n getTusServer: () => TusServer\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { getTusServer, getS3Store, pluginOptions } = args\n\n const collection = pluginOptions.collection\n magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED)\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return getTusServer().handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusPostProcessorHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n {\n handler: getTusFolderHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/folder',\n },\n {\n handler: getTusCleanupHandler({ getS3Store, collection }),\n method: 'post' as const,\n path: '/uploads/cleanup',\n },\n ]\n}\n"],"mappings":";;;;;;;;;AAcA,MAAMA,aAAkC,cAAc,EACpD,QAAQ,sBACT,CAAC;AAaF,SAAgB,gBAAgB,MAAsC;CACpE,MAAM,EAAE,YAAY,kBAAkB;CAEtC,MAAM,UAAU,YAAY;AAE5B,QAAO,IAAIC,OAAU;EACnB,WAAW;EACX,MAAM;EACN,yBAAyB,cAAc,IAAI,2BAA2B;EACtE,gBAAgB,OAAO,MAAM,aAAa;AACxC,UAAO,UAAU,YAAY,uBAAuB,GAAG;;EAEzD,gBAAgB,OAAO,MAAM,WAAW;GAEtC,MAAM,WAAW,OAAO,UAAU,YAAY;AAC9C,SAAM,QAAQ,QAAQ,SAAS;AAG/B,UAAO,QAAQ,QAAQ,EAAE,CAAC;;EAE7B,CAAC;;AAeJ,SAAgB,mBAAmB,MAA8B;CAC/D,MAAM,EAAE,cAAc,YAAY,kBAAkB;CAEpD,MAAM,aAAa,cAAc;AACjC,YAAW,OAAO,YAAY,iBAAiB,oBAAoB;;;;;;CAOnE,SAAS,WAAW,KAAqB;AACvC,SAAO,cAAc,CAAC,UAAU,IAAe;;AAGjD,QAAO;EACL;GAAE,SAAS;GAAY,QAAQ;GAAoB,MAAM;GAAY;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAiB,MAAM;GAAY;EAClE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAkB,MAAM;GAAgB;EACvE;GAAE,SAAS;GAAY,QAAQ;GAAmB,MAAM;GAAgB;EACxE;GACE,SAAS,2BAA2B;IAAE;IAAY;IAAY,CAAC;GAC/D,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,oBAAoB;IAAE;IAAY;IAAY,CAAC;GACxD,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,qBAAqB;IAAE;IAAY;IAAY,CAAC;GACzD,QAAQ;GACR,MAAM;GACP;EACF"}
1
+ {"version":3,"file":"tus.mjs","names":["magicError: UseMagicErrorReturn","TusServer"],"sources":["../../src/utils/tus.ts"],"sourcesContent":["import { Server as TusServer } from '@tus/server'\n\nimport { getTusPostProcessorHandler } from '../endpoints/tusPostProcessorHandler'\nimport { getTusFolderHandler } from '../endpoints/tusFolderHandler'\nimport { getTusCleanupHandler } from '../endpoints/tusCleanupHandler'\nimport { generateUniqueFilename } from './file'\n\nimport { useMagicError, type UseMagicErrorReturn } from '@maas/error-handler'\nimport { MediaCloudErrors } from '../types/errors'\n\nimport type { PayloadRequest } from 'payload'\nimport type { S3Store } from '../tus/stores/s3/s3Store'\nimport type { MediaCloudPluginOptions } from '../types'\n\nconst magicError: UseMagicErrorReturn = useMagicError({\n prefix: 'PLUGIN-MEDIA-CLOUD',\n})\n\n/**\n * Creates a TUS server instance with S3 storage\n * @param args.getS3Store - Function that returns an S3 client instance\n * @param args.pluginOptions - Media cloud plugin options\n * @returns A configured TusServer instance\n */\ninterface CreateTusServerArgs {\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusServer(args: CreateTusServerArgs): TusServer {\n const { getS3Store, pluginOptions } = args\n\n const s3Store = getS3Store()\n\n return new TusServer({\n datastore: s3Store,\n path: '/api/uploads',\n respectForwardedHeaders: pluginOptions.s3?.respectForwardedHeaders ?? true,\n namingFunction: async (_req, metadata) => {\n return metadata?.filename ?? generateUniqueFilename('')\n },\n onUploadFinish: async (_req, upload) => {\n // Clean up .part and .info files\n const filename = upload.metadata?.filename ?? ''\n await s3Store.cleanup(filename)\n\n // Prevent type error\n return Promise.resolve({})\n },\n })\n}\n\n/**\n * Creates TUS upload endpoints for file handling\n * @param args.getTusServer - Function that returns a TUS server instance\n * @param args.getS3Store - Function that returns an S3 client instance\n * @returns An array of endpoint configurations\n */\ninterface CreateTusEndpointsArgs {\n getTusServer: () => TusServer\n getS3Store: () => S3Store\n pluginOptions: MediaCloudPluginOptions\n}\n\nexport function createTusEndpoints(args: CreateTusEndpointsArgs) {\n const { getTusServer, getS3Store, pluginOptions } = args\n\n const collection = pluginOptions.collection\n magicError.assert(collection, MediaCloudErrors.COLLECTION_REQUIRED)\n\n const folders = pluginOptions.folders ?? false\n\n /**\n * Handles TUS requests through the server\n * @param req - The payload request object\n * @returns The server response\n */\n function tusHandler(req: PayloadRequest) {\n return getTusServer().handleWeb(req as Request)\n }\n\n return [\n { handler: tusHandler, method: 'options' as const, path: '/uploads' },\n { handler: tusHandler, method: 'post' as const, path: '/uploads' },\n { handler: tusHandler, method: 'get' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'put' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'patch' as const, path: '/uploads/:id' },\n { handler: tusHandler, method: 'delete' as const, path: '/uploads/:id' },\n {\n handler: getTusPostProcessorHandler({ getS3Store, collection }),\n method: 'get' as const,\n path: '/uploads/:filename/process',\n },\n {\n handler: getTusFolderHandler({ getS3Store, collection, folders }),\n method: 'get' as const,\n path: '/uploads/:filename/folder',\n },\n {\n handler: getTusCleanupHandler({ getS3Store, collection }),\n method: 'post' as const,\n path: '/uploads/cleanup',\n },\n ]\n}\n"],"mappings":";;;;;;;;;AAcA,MAAMA,aAAkC,cAAc,EACpD,QAAQ,sBACT,CAAC;AAaF,SAAgB,gBAAgB,MAAsC;CACpE,MAAM,EAAE,YAAY,kBAAkB;CAEtC,MAAM,UAAU,YAAY;AAE5B,QAAO,IAAIC,OAAU;EACnB,WAAW;EACX,MAAM;EACN,yBAAyB,cAAc,IAAI,2BAA2B;EACtE,gBAAgB,OAAO,MAAM,aAAa;AACxC,UAAO,UAAU,YAAY,uBAAuB,GAAG;;EAEzD,gBAAgB,OAAO,MAAM,WAAW;GAEtC,MAAM,WAAW,OAAO,UAAU,YAAY;AAC9C,SAAM,QAAQ,QAAQ,SAAS;AAG/B,UAAO,QAAQ,QAAQ,EAAE,CAAC;;EAE7B,CAAC;;AAeJ,SAAgB,mBAAmB,MAA8B;CAC/D,MAAM,EAAE,cAAc,YAAY,kBAAkB;CAEpD,MAAM,aAAa,cAAc;AACjC,YAAW,OAAO,YAAY,iBAAiB,oBAAoB;CAEnE,MAAM,UAAU,cAAc,WAAW;;;;;;CAOzC,SAAS,WAAW,KAAqB;AACvC,SAAO,cAAc,CAAC,UAAU,IAAe;;AAGjD,QAAO;EACL;GAAE,SAAS;GAAY,QAAQ;GAAoB,MAAM;GAAY;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAiB,MAAM;GAAY;EAClE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAgB,MAAM;GAAgB;EACrE;GAAE,SAAS;GAAY,QAAQ;GAAkB,MAAM;GAAgB;EACvE;GAAE,SAAS;GAAY,QAAQ;GAAmB,MAAM;GAAgB;EACxE;GACE,SAAS,2BAA2B;IAAE;IAAY;IAAY,CAAC;GAC/D,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,oBAAoB;IAAE;IAAY;IAAY;IAAS,CAAC;GACjE,QAAQ;GACR,MAAM;GACP;EACD;GACE,SAAS,qBAAqB;IAAE;IAAY;IAAY,CAAC;GACzD,QAAQ;GACR,MAAM;GACP;EACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maas/payload-plugin-media-cloud",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "contributors": [
6
6
  {
@@ -1,7 +0,0 @@
1
- import { CollectionAfterChangeHook } from "payload";
2
-
3
- //#region src/collectionHooks/thumbnail.d.ts
4
- declare const thumbnailHook: CollectionAfterChangeHook;
5
- //#endregion
6
- export { thumbnailHook };
7
- //# sourceMappingURL=thumbnail.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"thumbnail.mjs","names":["thumbnailHook: CollectionAfterChangeHook"],"sources":["../../src/collectionHooks/thumbnail.ts"],"sourcesContent":["import { buildThumbnailURL } from '../utils/buildThumbnailURL'\nimport { s3Store } from '../plugin'\nimport { MediaCloudErrors } from '../types/errors'\nimport { useErrorHandler } from '../hooks/useErrorHandler'\n\nimport type { CollectionAfterChangeHook } from 'payload'\n\nexport const thumbnailHook: CollectionAfterChangeHook = async ({\n collection,\n doc,\n previousDoc,\n req,\n}) => {\n const { throwError } = useErrorHandler()\n\n // Skip if this is an internal update to prevent infinite loop\n if (req.context?._mediaCloudPluginInternal) {\n return doc\n }\n\n // Handle thumbnail\n if (!doc.thumbnail || previousDoc?.path !== doc.path) {\n try {\n const thumbnail = buildThumbnailURL({\n storage: doc.storage,\n playbackId: doc.storage === 'mux' ? doc.mux.playbackId : undefined,\n s3Key: doc.storage === 's3' ? (doc.path ?? doc.filename) : undefined,\n s3Store,\n })\n\n req.context = { ...req.context }\n req.context._mediaCloudPluginInternal = true\n\n const update = await req.payload.update({\n collection: collection.slug,\n id: doc.id,\n data: { thumbnail },\n req,\n })\n\n delete req.context._mediaCloudPluginInternal\n\n return update\n } catch (error) {\n throwError({\n ...MediaCloudErrors.THUMBNAIL_GENERATION_ERROR,\n cause: error,\n })\n }\n }\n\n return doc\n}\n"],"mappings":";;;;;;AAOA,MAAaA,gBAA2C,OAAO,EAC7D,YACA,KACA,aACA,UACI;CACJ,MAAM,EAAE,eAAe,iBAAiB;AAGxC,KAAI,IAAI,SAAS,0BACf,QAAO;AAIT,KAAI,CAAC,IAAI,aAAa,aAAa,SAAS,IAAI,KAC9C,KAAI;EACF,MAAM,YAAY,kBAAkB;GAClC,SAAS,IAAI;GACb,YAAY,IAAI,YAAY,QAAQ,IAAI,IAAI,aAAa;GACzD,OAAO,IAAI,YAAY,OAAQ,IAAI,QAAQ,IAAI,WAAY;GAC3D;GACD,CAAC;AAEF,MAAI,UAAU,EAAE,GAAG,IAAI,SAAS;AAChC,MAAI,QAAQ,4BAA4B;EAExC,MAAM,SAAS,MAAM,IAAI,QAAQ,OAAO;GACtC,YAAY,WAAW;GACvB,IAAI,IAAI;GACR,MAAM,EAAE,WAAW;GACnB;GACD,CAAC;AAEF,SAAO,IAAI,QAAQ;AAEnB,SAAO;UACA,OAAO;AACd,aAAW;GACT,GAAG,iBAAiB;GACpB,OAAO;GACR,CAAC;;AAIN,QAAO"}