@humaan/payload-storage-imagekit 2.3.4 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,7 +7,7 @@ This plugin integrates `@payloadcms/plugin-cloud-storage` with ImageKit and prov
7
7
  - Uploads to ImageKit per collection
8
8
  - Optional client-side uploads for Payload admin to bypass request size limits on platforms like Vercel
9
9
  - Required root folder with per-collection subfolders
10
- - Stored ImageKit references (`imagekit.fileId`, `imagekit.url`, `imagekit.thumbnailUrl`)
10
+ - Stored ImageKit references (`imagekit.fileId`, `imagekit.url`, `imagekit.thumbnailUrl`, `imagekit.isPrivate`)
11
11
  - `crop: false` for configured upload collections
12
12
  - Admin thumbnails using ImageKit transforms (`tr=w-300,h-300`)
13
13
  - Built-in thumbnail path for videos and PDFs in admin previews
@@ -52,13 +52,14 @@ export default buildConfig({
52
52
 
53
53
  `imagekitStorage(options)` options:
54
54
 
55
- - `collections` (required): `Partial<Record<UploadCollectionSlug, true>>`
55
+ - `collections` (required): `Partial<Record<UploadCollectionSlug, true | { private?: boolean }>>`. Use `true` for a normal (public) collection, or `{ private: true }` to deliver the collection's files as ImageKit [private files](#private-files).
56
56
  - `folder` (required): root upload folder in ImageKit, e.g. `/payload-uploads`
57
57
  - `privateKey` (required): ImageKit private API key
58
58
  - `publicKey` (required): ImageKit public key
59
59
  - `urlEndpoint` (required): ImageKit URL endpoint, e.g. `https://ik.imagekit.io/your_id`
60
60
  - `clientUploads` (optional): when `true`, Payload admin uploads go directly from the browser to ImageKit before Payload fetches the file back for persistence and image-size generation. This helps avoid server request body limits such as Vercel's.
61
61
  Duplicate filenames still follow Payload's normal safe-name behavior, so a second `pug.jpg` becomes `pug-1.jpg` instead of overwriting the first asset in ImageKit.
62
+ - `signedUrlExpiresIn` (optional): expiry in seconds for the signed URLs generated for [private](#private-files) collections. Defaults to `900` (15 minutes). Signed URLs are generated fresh on every request, so a short window is safe.
62
63
  - `enabled` (optional): enable / disable plugin
63
64
  - `alwaysInsertFields` (optional): pass-through to cloud storage plugin
64
65
  - `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin
@@ -69,6 +70,36 @@ export default buildConfig({
69
70
  - `migrateFolder`
70
71
  - `payloadStorageImagekit` (alias of `imagekitStorage`)
71
72
 
73
+ ## Private Files
74
+
75
+ ImageKit [private files](https://imagekit.io/docs/media-delivery-basic-security#private-files) are uploaded with `isPrivateFile: true` and **cannot** be accessed through their plain URL — they return `401` unless requested through a time-limited [signed URL](https://imagekit.io/docs/media-delivery-basic-security#how-to-generate-signed-urls).
76
+
77
+ Mark any upload collection private by passing `{ private: true }` instead of `true`:
78
+
79
+ ```ts
80
+ imagekitStorage({
81
+ folder: '/payload-uploads',
82
+ collections: {
83
+ media: true, // public
84
+ documents: { private: true }, // private
85
+ },
86
+ signedUrlExpiresIn: 900, // optional, seconds (default 900)
87
+ privateKey: process.env.IMAGEKIT_PRIVATE_KEY || '',
88
+ publicKey: process.env.IMAGEKIT_PUBLIC_KEY || '',
89
+ urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT || '',
90
+ })
91
+ ```
92
+
93
+ How it works:
94
+
95
+ - **Upload:** files in a private collection are uploaded with `isPrivateFile: true` (both server-side and client-side uploads).
96
+ - **Per-document status:** each document records its actual ImageKit privacy at upload time in `imagekit.isPrivate` (a read-only "Private file" checkbox in the admin). A file's privacy on ImageKit is fixed when it is uploaded, so this stored flag — not the collection's current setting — is the **source of truth** for delivery. This means existing files keep working if you later flip a collection's `private` setting. (Documents uploaded before this flag existed fall back to the collection setting.)
97
+ - **Delivery:** files are served through Payload's own file route (`/api/<collection>/file/<filename>`), which enforces the collection's `access.read` control and then redirects to a freshly signed, expiring ImageKit URL. Nothing signed is ever persisted to the database.
98
+ - **Admin thumbnails** are routed through the same Payload file route so they sign on demand and respect access control (including ImageKit's `ik-thumbnail.jpg` previews for videos and PDFs).
99
+ - **Admin indicators:** private collections get a note in their admin description (visible in the list and edit views), and each document shows the read-only `imagekit.isPrivate` checkbox.
100
+ - **Signed URL preview:** because the stored `imagekit.url` is the unsigned URL (which 401s for private files), private documents also expose a read-only virtual `imagekit.signedUrl` field — a freshly signed, time-limited URL computed on each read (never persisted). Use it to reach the file directly on ImageKit. The access-control-gated alternative is the document's top-level `url` (the Payload file route).
101
+ - **Access control is required.** Because signing happens at request time behind Payload access control, `disablePayloadAccessControl` cannot be combined with private collections — doing so throws at startup. Set per-collection `access.read` rules to control who can read each file.
102
+
72
103
  ## Folder Migration
73
104
 
74
105
  Use `migrateFolder` to copy uploads from one ImageKit root folder to another (e.g. staging to production).
@@ -80,6 +111,7 @@ Behavior:
80
111
  - Optional database update via Payload local API
81
112
  - Dry-run is enabled by default (`dryRun: true`)
82
113
  - Copies from the configured source folder path first, with stored `imagekit.url` as a fallback only when the source file cannot be found
114
+ - Handles [private files](#private-files): mark a collection `{ private: true }` and migrated documents are stored with `imagekit.isPrivate: true` and a signed-URL admin thumbnail. The fast server-side copy preserves the source file's ImageKit privacy; when the copy falls back to re-uploading from the source URL, the source URL is signed (so a private source can be read) and the file is re-uploaded with `isPrivateFile: true`. Because ImageKit privacy is fixed at upload, migrating assumes the destination collection's privacy matches the source — copying a public file into a `{ private: true }` collection keeps the file public on ImageKit.
83
115
 
84
116
  Example:
85
117
 
@@ -106,13 +138,14 @@ await payload.destroy()
106
138
 
107
139
  `migrateFolder(options)` options:
108
140
 
109
- - `collections` (required): `Partial<Record<UploadCollectionSlug, true>>`
141
+ - `collections` (required): `Partial<Record<UploadCollectionSlug, true | { private?: boolean }>>`
110
142
  - `from` (required): source root folder, e.g. `/payload-uploads-staging`
111
143
  - `to` (required): destination root folder, e.g. `/payload-uploads-production`
112
144
  - `privateKey` (required): ImageKit private API key
113
145
  - `urlEndpoint` (required): ImageKit URL endpoint
114
146
  - `dryRun` (optional, default `true`): when `true`, logs what would happen without writes
115
147
  - `payload` (optional): when provided and `dryRun` is `false`, updates `url` + `imagekit.*` fields
148
+ - `signedUrlExpiresIn` (optional, default `900`): expiry in seconds for the signed URL used to read a private source file during the re-upload fallback
116
149
 
117
150
  Run a migration script:
118
151
 
@@ -11,6 +11,7 @@ export type ImageKitUploadHandlerProps = {
11
11
  authPath: `/${string}`;
12
12
  collectionSlug: string;
13
13
  folder: string;
14
+ isPrivate?: boolean;
14
15
  publicKey: string;
15
16
  };
16
17
  export declare const isImageKitClientUploadContext: (value: unknown) => value is ImageKitClientUploadContext;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["export const imageKitUploadAuthBasePath = '/imagekit-upload-auth'\n\nexport const getImageKitUploadAuthPath = (collectionSlug: string): `/${string}` =>\n `${imageKitUploadAuthBasePath}/${collectionSlug}`\n\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n name?: string\n thumbnailUrl?: string\n url?: string\n}\n\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n publicKey: string\n}\n\nexport const isImageKitClientUploadContext = (\n value: unknown,\n): value is ImageKitClientUploadContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value\n}\n"],"names":["imageKitUploadAuthBasePath","getImageKitUploadAuthPath","collectionSlug","isImageKitClientUploadContext","value"],"mappings":"AAAA,OAAO,MAAMA,6BAA6B,wBAAuB;AAEjE,OAAO,MAAMC,4BAA4B,CAACC,iBACxC,GAAGF,2BAA2B,CAAC,EAAEE,gBAAgB,CAAA;AAiBnD,OAAO,MAAMC,gCAAgC,CAC3CC;IAEA,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,OAAO,YAAYA,SAAS,kBAAkBA,SAAS,SAASA;AAClE,EAAC"}
1
+ {"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["export const imageKitUploadAuthBasePath = '/imagekit-upload-auth'\n\nexport const getImageKitUploadAuthPath = (collectionSlug: string): `/${string}` =>\n `${imageKitUploadAuthBasePath}/${collectionSlug}`\n\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n name?: string\n thumbnailUrl?: string\n url?: string\n}\n\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport const isImageKitClientUploadContext = (\n value: unknown,\n): value is ImageKitClientUploadContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n return 'fileId' in value || 'thumbnailUrl' in value || 'url' in value\n}\n"],"names":["imageKitUploadAuthBasePath","getImageKitUploadAuthPath","collectionSlug","isImageKitClientUploadContext","value"],"mappings":"AAAA,OAAO,MAAMA,6BAA6B,wBAAuB;AAEjE,OAAO,MAAMC,4BAA4B,CAACC,iBACxC,GAAGF,2BAA2B,CAAC,EAAEE,gBAAgB,CAAA;AAkBnD,OAAO,MAAMC,gCAAgC,CAC3CC;IAEA,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,OAAO,YAAYA,SAAS,kBAAkBA,SAAS,SAASA;AAClE,EAAC"}
@@ -1,2 +1,2 @@
1
1
  import type { ImageKitUploadHandlerProps } from '../clientUploads.js';
2
- export declare const ImageKitUploadHandler: ({ authPath, collectionSlug, folder, publicKey, }: ImageKitUploadHandlerProps) => null;
2
+ export declare const ImageKitUploadHandler: ({ authPath, collectionSlug, folder, isPrivate, publicKey, }: ImageKitUploadHandlerProps) => null;
@@ -14,7 +14,7 @@ const readErrorMessage = async (response, fallback)=>{
14
14
  }
15
15
  return fallback;
16
16
  };
17
- export const ImageKitUploadHandler = ({ authPath, collectionSlug, folder, publicKey })=>{
17
+ export const ImageKitUploadHandler = ({ authPath, collectionSlug, folder, isPrivate, publicKey })=>{
18
18
  const { config } = useConfig();
19
19
  const { setUploadHandler } = useUploadHandlers();
20
20
  useEffect(()=>{
@@ -44,6 +44,11 @@ export const ImageKitUploadHandler = ({ authPath, collectionSlug, folder, public
44
44
  formData.set('signature', auth.signature);
45
45
  formData.set('token', auth.token);
46
46
  formData.set('useUniqueFileName', 'false');
47
+ if (isPrivate) {
48
+ // ImageKit's upload signature only covers `token` + `expire`, so adding
49
+ // this flag does not invalidate the signed auth parameters.
50
+ formData.set('isPrivateFile', 'true');
51
+ }
47
52
  const uploadResponse = await fetch(uploadAPIURL, {
48
53
  body: formData,
49
54
  method: 'POST'
@@ -68,6 +73,7 @@ export const ImageKitUploadHandler = ({ authPath, collectionSlug, folder, public
68
73
  collectionSlug,
69
74
  config.routes.api,
70
75
  folder,
76
+ isPrivate,
71
77
  publicKey,
72
78
  setUploadHandler
73
79
  ]);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useUploadHandlers } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect } from 'react'\n\nimport type {\n ImageKitClientUploadContext,\n ImageKitUploadHandlerProps,\n} from '../clientUploads.js'\n\ntype ImageKitAuthResponse = {\n expire: number\n fileName?: string\n signature: string\n token: string\n}\n\ntype ImageKitUploadResponse = {\n message?: string\n name?: string\n} & ImageKitClientUploadContext\n\nconst uploadAPIURL = 'https://upload.imagekit.io/api/v1/files/upload'\n\nconst readErrorMessage = async (response: Response, fallback: string): Promise<string> => {\n try {\n const result = (await response.json()) as { message?: string }\n\n if (typeof result.message === 'string' && result.message.trim().length > 0) {\n return result.message\n }\n } catch {\n // Ignore non-JSON responses and fall back to the default message.\n }\n\n return fallback\n}\n\nexport const ImageKitUploadHandler = ({\n authPath,\n collectionSlug,\n folder,\n publicKey,\n}: ImageKitUploadHandlerProps) => {\n const { config } = useConfig()\n const { setUploadHandler } = useUploadHandlers()\n\n useEffect(() => {\n const authURL = formatAdminURL({\n apiRoute: config.routes.api,\n path: authPath,\n })\n\n setUploadHandler({\n collectionSlug,\n handler: async ({ file, updateFilename }) => {\n const authRequestURL = new URL(authURL, window.location.origin)\n\n authRequestURL.searchParams.set('filename', file.name)\n\n const authResponse = await fetch(authRequestURL, {\n credentials: 'include',\n })\n\n if (!authResponse.ok) {\n throw new Error(\n await readErrorMessage(\n authResponse,\n 'Failed to authenticate direct ImageKit upload.',\n ),\n )\n }\n\n const auth = (await authResponse.json()) as ImageKitAuthResponse\n const safeFilename =\n typeof auth.fileName === 'string' && auth.fileName.length > 0 ? auth.fileName : file.name\n const formData = new FormData()\n\n formData.set('expire', String(auth.expire))\n formData.set('file', file)\n formData.set('fileName', safeFilename)\n formData.set('folder', folder)\n formData.set('publicKey', publicKey)\n formData.set('signature', auth.signature)\n formData.set('token', auth.token)\n formData.set('useUniqueFileName', 'false')\n\n const uploadResponse = await fetch(uploadAPIURL, {\n body: formData,\n method: 'POST',\n })\n\n if (!uploadResponse.ok) {\n throw new Error(\n await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.'),\n )\n }\n\n const uploaded = (await uploadResponse.json()) as ImageKitUploadResponse\n const uploadedFilename =\n typeof uploaded.name === 'string' && uploaded.name.length > 0 ? uploaded.name : safeFilename\n\n updateFilename(uploadedFilename)\n\n return {\n name: uploadedFilename,\n fileId: uploaded.fileId,\n filePath: uploaded.filePath,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n },\n })\n }, [authPath, collectionSlug, config.routes.api, folder, publicKey, setUploadHandler])\n\n return null\n}\n"],"names":["useConfig","useUploadHandlers","formatAdminURL","useEffect","uploadAPIURL","readErrorMessage","response","fallback","result","json","message","trim","length","ImageKitUploadHandler","authPath","collectionSlug","folder","publicKey","config","setUploadHandler","authURL","apiRoute","routes","api","path","handler","file","updateFilename","authRequestURL","URL","window","location","origin","searchParams","set","name","authResponse","fetch","credentials","ok","Error","auth","safeFilename","fileName","formData","FormData","String","expire","signature","token","uploadResponse","body","method","uploaded","uploadedFilename","fileId","filePath","thumbnailUrl","url"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,iBAAiB,QAAQ,iBAAgB;AAC7D,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,SAASC,SAAS,QAAQ,QAAO;AAmBjC,MAAMC,eAAe;AAErB,MAAMC,mBAAmB,OAAOC,UAAoBC;IAClD,IAAI;QACF,MAAMC,SAAU,MAAMF,SAASG,IAAI;QAEnC,IAAI,OAAOD,OAAOE,OAAO,KAAK,YAAYF,OAAOE,OAAO,CAACC,IAAI,GAAGC,MAAM,GAAG,GAAG;YAC1E,OAAOJ,OAAOE,OAAO;QACvB;IACF,EAAE,OAAM;IACN,kEAAkE;IACpE;IAEA,OAAOH;AACT;AAEA,OAAO,MAAMM,wBAAwB,CAAC,EACpCC,QAAQ,EACRC,cAAc,EACdC,MAAM,EACNC,SAAS,EACkB;IAC3B,MAAM,EAAEC,MAAM,EAAE,GAAGlB;IACnB,MAAM,EAAEmB,gBAAgB,EAAE,GAAGlB;IAE7BE,UAAU;QACR,MAAMiB,UAAUlB,eAAe;YAC7BmB,UAAUH,OAAOI,MAAM,CAACC,GAAG;YAC3BC,MAAMV;QACR;QAEAK,iBAAiB;YACfJ;YACAU,SAAS,OAAO,EAAEC,IAAI,EAAEC,cAAc,EAAE;gBACtC,MAAMC,iBAAiB,IAAIC,IAAIT,SAASU,OAAOC,QAAQ,CAACC,MAAM;gBAE9DJ,eAAeK,YAAY,CAACC,GAAG,CAAC,YAAYR,KAAKS,IAAI;gBAErD,MAAMC,eAAe,MAAMC,MAAMT,gBAAgB;oBAC/CU,aAAa;gBACf;gBAEA,IAAI,CAACF,aAAaG,EAAE,EAAE;oBACpB,MAAM,IAAIC,MACR,MAAMnC,iBACJ+B,cACA;gBAGN;gBAEA,MAAMK,OAAQ,MAAML,aAAa3B,IAAI;gBACrC,MAAMiC,eACJ,OAAOD,KAAKE,QAAQ,KAAK,YAAYF,KAAKE,QAAQ,CAAC/B,MAAM,GAAG,IAAI6B,KAAKE,QAAQ,GAAGjB,KAAKS,IAAI;gBAC3F,MAAMS,WAAW,IAAIC;gBAErBD,SAASV,GAAG,CAAC,UAAUY,OAAOL,KAAKM,MAAM;gBACzCH,SAASV,GAAG,CAAC,QAAQR;gBACrBkB,SAASV,GAAG,CAAC,YAAYQ;gBACzBE,SAASV,GAAG,CAAC,UAAUlB;gBACvB4B,SAASV,GAAG,CAAC,aAAajB;gBAC1B2B,SAASV,GAAG,CAAC,aAAaO,KAAKO,SAAS;gBACxCJ,SAASV,GAAG,CAAC,SAASO,KAAKQ,KAAK;gBAChCL,SAASV,GAAG,CAAC,qBAAqB;gBAElC,MAAMgB,iBAAiB,MAAMb,MAAMjC,cAAc;oBAC/C+C,MAAMP;oBACNQ,QAAQ;gBACV;gBAEA,IAAI,CAACF,eAAeX,EAAE,EAAE;oBACtB,MAAM,IAAIC,MACR,MAAMnC,iBAAiB6C,gBAAgB;gBAE3C;gBAEA,MAAMG,WAAY,MAAMH,eAAezC,IAAI;gBAC3C,MAAM6C,mBACJ,OAAOD,SAASlB,IAAI,KAAK,YAAYkB,SAASlB,IAAI,CAACvB,MAAM,GAAG,IAAIyC,SAASlB,IAAI,GAAGO;gBAElFf,eAAe2B;gBAEf,OAAO;oBACLnB,MAAMmB;oBACNC,QAAQF,SAASE,MAAM;oBACvBC,UAAUH,SAASG,QAAQ;oBAC3BC,cAAcJ,SAASI,YAAY;oBACnCC,KAAKL,SAASK,GAAG;gBACnB;YACF;QACF;IACF,GAAG;QAAC5C;QAAUC;QAAgBG,OAAOI,MAAM,CAACC,GAAG;QAAEP;QAAQC;QAAWE;KAAiB;IAErF,OAAO;AACT,EAAC"}
1
+ {"version":3,"sources":["../../src/components/ImageKitUploadHandler.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useUploadHandlers } from '@payloadcms/ui'\nimport { formatAdminURL } from 'payload/shared'\nimport { useEffect } from 'react'\n\nimport type { ImageKitClientUploadContext, ImageKitUploadHandlerProps } from '../clientUploads.js'\n\ntype ImageKitAuthResponse = {\n expire: number\n fileName?: string\n signature: string\n token: string\n}\n\ntype ImageKitUploadResponse = {\n message?: string\n name?: string\n} & ImageKitClientUploadContext\n\nconst uploadAPIURL = 'https://upload.imagekit.io/api/v1/files/upload'\n\nconst readErrorMessage = async (response: Response, fallback: string): Promise<string> => {\n try {\n const result = (await response.json()) as { message?: string }\n\n if (typeof result.message === 'string' && result.message.trim().length > 0) {\n return result.message\n }\n } catch {\n // Ignore non-JSON responses and fall back to the default message.\n }\n\n return fallback\n}\n\nexport const ImageKitUploadHandler = ({\n authPath,\n collectionSlug,\n folder,\n isPrivate,\n publicKey,\n}: ImageKitUploadHandlerProps) => {\n const { config } = useConfig()\n const { setUploadHandler } = useUploadHandlers()\n\n useEffect(() => {\n const authURL = formatAdminURL({\n apiRoute: config.routes.api,\n path: authPath,\n })\n\n setUploadHandler({\n collectionSlug,\n handler: async ({ file, updateFilename }) => {\n const authRequestURL = new URL(authURL, window.location.origin)\n\n authRequestURL.searchParams.set('filename', file.name)\n\n const authResponse = await fetch(authRequestURL, {\n credentials: 'include',\n })\n\n if (!authResponse.ok) {\n throw new Error(\n await readErrorMessage(authResponse, 'Failed to authenticate direct ImageKit upload.'),\n )\n }\n\n const auth = (await authResponse.json()) as ImageKitAuthResponse\n const safeFilename =\n typeof auth.fileName === 'string' && auth.fileName.length > 0 ? auth.fileName : file.name\n const formData = new FormData()\n\n formData.set('expire', String(auth.expire))\n formData.set('file', file)\n formData.set('fileName', safeFilename)\n formData.set('folder', folder)\n formData.set('publicKey', publicKey)\n formData.set('signature', auth.signature)\n formData.set('token', auth.token)\n formData.set('useUniqueFileName', 'false')\n\n if (isPrivate) {\n // ImageKit's upload signature only covers `token` + `expire`, so adding\n // this flag does not invalidate the signed auth parameters.\n formData.set('isPrivateFile', 'true')\n }\n\n const uploadResponse = await fetch(uploadAPIURL, {\n body: formData,\n method: 'POST',\n })\n\n if (!uploadResponse.ok) {\n throw new Error(\n await readErrorMessage(uploadResponse, 'Failed to upload file to ImageKit.'),\n )\n }\n\n const uploaded = (await uploadResponse.json()) as ImageKitUploadResponse\n const uploadedFilename =\n typeof uploaded.name === 'string' && uploaded.name.length > 0\n ? uploaded.name\n : safeFilename\n\n updateFilename(uploadedFilename)\n\n return {\n name: uploadedFilename,\n fileId: uploaded.fileId,\n filePath: uploaded.filePath,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n },\n })\n }, [authPath, collectionSlug, config.routes.api, folder, isPrivate, publicKey, setUploadHandler])\n\n return null\n}\n"],"names":["useConfig","useUploadHandlers","formatAdminURL","useEffect","uploadAPIURL","readErrorMessage","response","fallback","result","json","message","trim","length","ImageKitUploadHandler","authPath","collectionSlug","folder","isPrivate","publicKey","config","setUploadHandler","authURL","apiRoute","routes","api","path","handler","file","updateFilename","authRequestURL","URL","window","location","origin","searchParams","set","name","authResponse","fetch","credentials","ok","Error","auth","safeFilename","fileName","formData","FormData","String","expire","signature","token","uploadResponse","body","method","uploaded","uploadedFilename","fileId","filePath","thumbnailUrl","url"],"mappings":"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"}
@@ -2,7 +2,7 @@ import type ImageKit from '@imagekit/nodejs';
2
2
  import type { PayloadHandler, UploadCollectionSlug } from 'payload';
3
3
  type CreateImageKitUploadAuthHandlerArgs = {
4
4
  client: ImageKit;
5
- collections: Partial<Record<UploadCollectionSlug, true>>;
5
+ collections: Partial<Record<UploadCollectionSlug, unknown>>;
6
6
  };
7
7
  export declare const createImageKitUploadAuthHandler: ({ client, collections, }: CreateImageKitUploadAuthHandlerArgs) => PayloadHandler;
8
8
  export {};
@@ -2,7 +2,7 @@ import { getSafeFileName } from 'payload/internal';
2
2
  export const createImageKitUploadAuthHandler = ({ client, collections })=>{
3
3
  return async (req)=>{
4
4
  const collectionSlug = req.routeParams?.collection;
5
- if (typeof collectionSlug !== 'string' || !(collectionSlug in collections) || collections[collectionSlug] !== true) {
5
+ if (typeof collectionSlug !== 'string' || !(collectionSlug in collections)) {
6
6
  return Response.json({
7
7
  message: 'Collection not found.'
8
8
  }, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/endpoints/createImageKitUploadAuthHandler.ts"],"sourcesContent":["import type ImageKit from '@imagekit/nodejs'\nimport type { PayloadHandler, UploadCollectionSlug } from 'payload'\n\nimport { getSafeFileName } from 'payload/internal'\n\ntype CreateImageKitUploadAuthHandlerArgs = {\n client: ImageKit\n collections: Partial<Record<UploadCollectionSlug, true>>\n}\n\nexport const createImageKitUploadAuthHandler = ({\n client,\n collections,\n}: CreateImageKitUploadAuthHandlerArgs): PayloadHandler => {\n return async (req) => {\n const collectionSlug = req.routeParams?.collection\n\n if (typeof collectionSlug !== 'string' || !(collectionSlug in collections) || collections[collectionSlug] !== true) {\n return Response.json({ message: 'Collection not found.' }, { status: 404 })\n }\n\n if (!req.user) {\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n const authenticationParameters = client.helper.getAuthenticationParameters()\n const requestURL = new URL(req.url || 'http://localhost')\n const requestedFilename = requestURL.searchParams.get('filename')?.trim()\n const collection = req.payload.collections[collectionSlug]\n\n if (\n !requestedFilename ||\n typeof collection?.config.upload !== 'object' ||\n typeof collection.config.upload.staticDir !== 'string'\n ) {\n return Response.json(authenticationParameters, {\n headers: {\n 'Cache-Control': 'no-store',\n },\n })\n }\n\n const safeFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: requestedFilename,\n req,\n staticPath: collection.config.upload.staticDir,\n })\n\n return Response.json({ ...authenticationParameters, fileName: safeFilename }, {\n headers: {\n 'Cache-Control': 'no-store',\n },\n })\n }\n}\n"],"names":["getSafeFileName","createImageKitUploadAuthHandler","client","collections","req","collectionSlug","routeParams","collection","Response","json","message","status","user","authenticationParameters","helper","getAuthenticationParameters","requestURL","URL","url","requestedFilename","searchParams","get","trim","payload","config","upload","staticDir","headers","safeFilename","desiredFilename","staticPath","fileName"],"mappings":"AAGA,SAASA,eAAe,QAAQ,mBAAkB;AAOlD,OAAO,MAAMC,kCAAkC,CAAC,EAC9CC,MAAM,EACNC,WAAW,EACyB;IACpC,OAAO,OAAOC;QACZ,MAAMC,iBAAiBD,IAAIE,WAAW,EAAEC;QAExC,IAAI,OAAOF,mBAAmB,YAAY,CAAEA,CAAAA,kBAAkBF,WAAU,KAAMA,WAAW,CAACE,eAAe,KAAK,MAAM;YAClH,OAAOG,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAwB,GAAG;gBAAEC,QAAQ;YAAI;QAC3E;QAEA,IAAI,CAACP,IAAIQ,IAAI,EAAE;YACb,OAAOJ,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAgB,GAAG;gBAAEC,QAAQ;YAAI;QACnE;QAEA,MAAME,2BAA2BX,OAAOY,MAAM,CAACC,2BAA2B;QAC1E,MAAMC,aAAa,IAAIC,IAAIb,IAAIc,GAAG,IAAI;QACtC,MAAMC,oBAAoBH,WAAWI,YAAY,CAACC,GAAG,CAAC,aAAaC;QACnE,MAAMf,aAAaH,IAAImB,OAAO,CAACpB,WAAW,CAACE,eAAe;QAE1D,IACE,CAACc,qBACD,OAAOZ,YAAYiB,OAAOC,WAAW,YACrC,OAAOlB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS,KAAK,UAC9C;YACA,OAAOlB,SAASC,IAAI,CAACI,0BAA0B;gBAC7Cc,SAAS;oBACP,iBAAiB;gBACnB;YACF;QACF;QAEA,MAAMC,eAAe,MAAM5B,gBAAgB;YACzCK;YACAwB,iBAAiBV;YACjBf;YACA0B,YAAYvB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS;QAChD;QAEA,OAAOlB,SAASC,IAAI,CAAC;YAAE,GAAGI,wBAAwB;YAAEkB,UAAUH;QAAa,GAAG;YAC5ED,SAAS;gBACP,iBAAiB;YACnB;QACF;IACF;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/endpoints/createImageKitUploadAuthHandler.ts"],"sourcesContent":["import type ImageKit from '@imagekit/nodejs'\nimport type { PayloadHandler, UploadCollectionSlug } from 'payload'\n\nimport { getSafeFileName } from 'payload/internal'\n\ntype CreateImageKitUploadAuthHandlerArgs = {\n client: ImageKit\n collections: Partial<Record<UploadCollectionSlug, unknown>>\n}\n\nexport const createImageKitUploadAuthHandler = ({\n client,\n collections,\n}: CreateImageKitUploadAuthHandlerArgs): PayloadHandler => {\n return async (req) => {\n const collectionSlug = req.routeParams?.collection\n\n if (typeof collectionSlug !== 'string' || !(collectionSlug in collections)) {\n return Response.json({ message: 'Collection not found.' }, { status: 404 })\n }\n\n if (!req.user) {\n return Response.json({ message: 'Unauthorized.' }, { status: 401 })\n }\n\n const authenticationParameters = client.helper.getAuthenticationParameters()\n const requestURL = new URL(req.url || 'http://localhost')\n const requestedFilename = requestURL.searchParams.get('filename')?.trim()\n const collection = req.payload.collections[collectionSlug]\n\n if (\n !requestedFilename ||\n typeof collection?.config.upload !== 'object' ||\n typeof collection.config.upload.staticDir !== 'string'\n ) {\n return Response.json(authenticationParameters, {\n headers: {\n 'Cache-Control': 'no-store',\n },\n })\n }\n\n const safeFilename = await getSafeFileName({\n collectionSlug,\n desiredFilename: requestedFilename,\n req,\n staticPath: collection.config.upload.staticDir,\n })\n\n return Response.json(\n { ...authenticationParameters, fileName: safeFilename },\n {\n headers: {\n 'Cache-Control': 'no-store',\n },\n },\n )\n }\n}\n"],"names":["getSafeFileName","createImageKitUploadAuthHandler","client","collections","req","collectionSlug","routeParams","collection","Response","json","message","status","user","authenticationParameters","helper","getAuthenticationParameters","requestURL","URL","url","requestedFilename","searchParams","get","trim","payload","config","upload","staticDir","headers","safeFilename","desiredFilename","staticPath","fileName"],"mappings":"AAGA,SAASA,eAAe,QAAQ,mBAAkB;AAOlD,OAAO,MAAMC,kCAAkC,CAAC,EAC9CC,MAAM,EACNC,WAAW,EACyB;IACpC,OAAO,OAAOC;QACZ,MAAMC,iBAAiBD,IAAIE,WAAW,EAAEC;QAExC,IAAI,OAAOF,mBAAmB,YAAY,CAAEA,CAAAA,kBAAkBF,WAAU,GAAI;YAC1E,OAAOK,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAwB,GAAG;gBAAEC,QAAQ;YAAI;QAC3E;QAEA,IAAI,CAACP,IAAIQ,IAAI,EAAE;YACb,OAAOJ,SAASC,IAAI,CAAC;gBAAEC,SAAS;YAAgB,GAAG;gBAAEC,QAAQ;YAAI;QACnE;QAEA,MAAME,2BAA2BX,OAAOY,MAAM,CAACC,2BAA2B;QAC1E,MAAMC,aAAa,IAAIC,IAAIb,IAAIc,GAAG,IAAI;QACtC,MAAMC,oBAAoBH,WAAWI,YAAY,CAACC,GAAG,CAAC,aAAaC;QACnE,MAAMf,aAAaH,IAAImB,OAAO,CAACpB,WAAW,CAACE,eAAe;QAE1D,IACE,CAACc,qBACD,OAAOZ,YAAYiB,OAAOC,WAAW,YACrC,OAAOlB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS,KAAK,UAC9C;YACA,OAAOlB,SAASC,IAAI,CAACI,0BAA0B;gBAC7Cc,SAAS;oBACP,iBAAiB;gBACnB;YACF;QACF;QAEA,MAAMC,eAAe,MAAM5B,gBAAgB;YACzCK;YACAwB,iBAAiBV;YACjBf;YACA0B,YAAYvB,WAAWiB,MAAM,CAACC,MAAM,CAACC,SAAS;QAChD;QAEA,OAAOlB,SAASC,IAAI,CAClB;YAAE,GAAGI,wBAAwB;YAAEkB,UAAUH;QAAa,GACtD;YACED,SAAS;gBACP,iBAAiB;YACnB;QACF;IAEJ;AACF,EAAC"}
@@ -1 +1,12 @@
1
+ export declare const isVideoOrPdfMimeType: (mimeType?: string) => boolean;
1
2
  export declare const getAdminThumbnailURL: (doc: unknown) => string;
3
+ /**
4
+ * Admin thumbnail URL for a private file.
5
+ *
6
+ * Private files 401 on their plain ImageKit URL, so the preview must be served
7
+ * through Payload's own file route (`<apiRoute>/<collection>/file/<filename>`).
8
+ * That route enforces collection access control and the adapter's `staticHandler`
9
+ * signs the ImageKit URL on demand (including the `/ik-thumbnail.jpg` segment for
10
+ * videos and PDFs, derived from the document's mimeType).
11
+ */
12
+ export declare const getPrivateAdminThumbnailURL: (doc: unknown, collectionSlug: string, apiRoute: string) => string;
@@ -1,13 +1,16 @@
1
+ export const isVideoOrPdfMimeType = (mimeType)=>{
2
+ const isVideo = mimeType?.startsWith('video');
3
+ const isPDF = mimeType === 'application/pdf' || mimeType?.endsWith('/pdf');
4
+ return Boolean(isVideo || isPDF);
5
+ };
1
6
  export const getAdminThumbnailURL = (doc)=>{
2
7
  const file = doc;
3
8
  const baseURL = file.imagekit?.url;
4
9
  if (!baseURL) {
5
10
  return '';
6
11
  }
7
- const isVideo = file.mimeType?.startsWith('video');
8
- const isPDF = file.mimeType === 'application/pdf' || file.mimeType?.endsWith('/pdf');
9
12
  let thumbnailBaseURL = baseURL;
10
- if (isVideo || isPDF) {
13
+ if (isVideoOrPdfMimeType(file.mimeType)) {
11
14
  try {
12
15
  const parsedURL = new URL(baseURL);
13
16
  thumbnailBaseURL = `${parsedURL.origin}${parsedURL.pathname}/ik-thumbnail.jpg`;
@@ -18,5 +21,21 @@ export const getAdminThumbnailURL = (doc)=>{
18
21
  }
19
22
  return `${thumbnailBaseURL}${thumbnailBaseURL.includes('?') ? '&' : '?'}tr=w-300,h-300`;
20
23
  };
24
+ /**
25
+ * Admin thumbnail URL for a private file.
26
+ *
27
+ * Private files 401 on their plain ImageKit URL, so the preview must be served
28
+ * through Payload's own file route (`<apiRoute>/<collection>/file/<filename>`).
29
+ * That route enforces collection access control and the adapter's `staticHandler`
30
+ * signs the ImageKit URL on demand (including the `/ik-thumbnail.jpg` segment for
31
+ * videos and PDFs, derived from the document's mimeType).
32
+ */ export const getPrivateAdminThumbnailURL = (doc, collectionSlug, apiRoute)=>{
33
+ const filename = doc?.filename;
34
+ if (!filename) {
35
+ return '';
36
+ }
37
+ const base = `${apiRoute.replace(/\/+$/, '')}/${collectionSlug}/file/${encodeURIComponent(filename)}`;
38
+ return `${base}?tr=w-300,h-300`;
39
+ };
21
40
 
22
41
  //# sourceMappingURL=getAdminThumbnailURL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/getAdminThumbnailURL.ts"],"sourcesContent":["type ImagekitReference = {\n imagekit?: {\n url?: string\n }\n}\n\nexport const getAdminThumbnailURL = (doc: unknown): string => {\n const file = doc as { mimeType?: string } & ImagekitReference\n const baseURL = file.imagekit?.url\n\n if (!baseURL) {\n return ''\n }\n\n const isVideo = file.mimeType?.startsWith('video')\n const isPDF = file.mimeType === 'application/pdf' || file.mimeType?.endsWith('/pdf')\n\n let thumbnailBaseURL = baseURL\n\n if (isVideo || isPDF) {\n try {\n const parsedURL = new URL(baseURL)\n thumbnailBaseURL = `${parsedURL.origin}${parsedURL.pathname}/ik-thumbnail.jpg`\n } catch {\n const normalizedBaseURL = baseURL.split('?')[0].split('#')[0]\n thumbnailBaseURL = `${normalizedBaseURL}/ik-thumbnail.jpg`\n }\n }\n\n return `${thumbnailBaseURL}${thumbnailBaseURL.includes('?') ? '&' : '?'}tr=w-300,h-300`\n}\n"],"names":["getAdminThumbnailURL","doc","file","baseURL","imagekit","url","isVideo","mimeType","startsWith","isPDF","endsWith","thumbnailBaseURL","parsedURL","URL","origin","pathname","normalizedBaseURL","split","includes"],"mappings":"AAMA,OAAO,MAAMA,uBAAuB,CAACC;IACnC,MAAMC,OAAOD;IACb,MAAME,UAAUD,KAAKE,QAAQ,EAAEC;IAE/B,IAAI,CAACF,SAAS;QACZ,OAAO;IACT;IAEA,MAAMG,UAAUJ,KAAKK,QAAQ,EAAEC,WAAW;IAC1C,MAAMC,QAAQP,KAAKK,QAAQ,KAAK,qBAAqBL,KAAKK,QAAQ,EAAEG,SAAS;IAE7E,IAAIC,mBAAmBR;IAEvB,IAAIG,WAAWG,OAAO;QACpB,IAAI;YACF,MAAMG,YAAY,IAAIC,IAAIV;YAC1BQ,mBAAmB,GAAGC,UAAUE,MAAM,GAAGF,UAAUG,QAAQ,CAAC,iBAAiB,CAAC;QAChF,EAAE,OAAM;YACN,MAAMC,oBAAoBb,QAAQc,KAAK,CAAC,IAAI,CAAC,EAAE,CAACA,KAAK,CAAC,IAAI,CAAC,EAAE;YAC7DN,mBAAmB,GAAGK,kBAAkB,iBAAiB,CAAC;QAC5D;IACF;IAEA,OAAO,GAAGL,mBAAmBA,iBAAiBO,QAAQ,CAAC,OAAO,MAAM,IAAI,cAAc,CAAC;AACzF,EAAC"}
1
+ {"version":3,"sources":["../src/getAdminThumbnailURL.ts"],"sourcesContent":["type ImagekitReference = {\n imagekit?: {\n url?: string\n }\n}\n\nexport const isVideoOrPdfMimeType = (mimeType?: string): boolean => {\n const isVideo = mimeType?.startsWith('video')\n const isPDF = mimeType === 'application/pdf' || mimeType?.endsWith('/pdf')\n\n return Boolean(isVideo || isPDF)\n}\n\nexport const getAdminThumbnailURL = (doc: unknown): string => {\n const file = doc as { mimeType?: string } & ImagekitReference\n const baseURL = file.imagekit?.url\n\n if (!baseURL) {\n return ''\n }\n\n let thumbnailBaseURL = baseURL\n\n if (isVideoOrPdfMimeType(file.mimeType)) {\n try {\n const parsedURL = new URL(baseURL)\n thumbnailBaseURL = `${parsedURL.origin}${parsedURL.pathname}/ik-thumbnail.jpg`\n } catch {\n const normalizedBaseURL = baseURL.split('?')[0].split('#')[0]\n thumbnailBaseURL = `${normalizedBaseURL}/ik-thumbnail.jpg`\n }\n }\n\n return `${thumbnailBaseURL}${thumbnailBaseURL.includes('?') ? '&' : '?'}tr=w-300,h-300`\n}\n\n/**\n * Admin thumbnail URL for a private file.\n *\n * Private files 401 on their plain ImageKit URL, so the preview must be served\n * through Payload's own file route (`<apiRoute>/<collection>/file/<filename>`).\n * That route enforces collection access control and the adapter's `staticHandler`\n * signs the ImageKit URL on demand (including the `/ik-thumbnail.jpg` segment for\n * videos and PDFs, derived from the document's mimeType).\n */\nexport const getPrivateAdminThumbnailURL = (\n doc: unknown,\n collectionSlug: string,\n apiRoute: string,\n): string => {\n const filename = (doc as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return ''\n }\n\n const base = `${apiRoute.replace(/\\/+$/, '')}/${collectionSlug}/file/${encodeURIComponent(filename)}`\n\n return `${base}?tr=w-300,h-300`\n}\n"],"names":["isVideoOrPdfMimeType","mimeType","isVideo","startsWith","isPDF","endsWith","Boolean","getAdminThumbnailURL","doc","file","baseURL","imagekit","url","thumbnailBaseURL","parsedURL","URL","origin","pathname","normalizedBaseURL","split","includes","getPrivateAdminThumbnailURL","collectionSlug","apiRoute","filename","base","replace","encodeURIComponent"],"mappings":"AAMA,OAAO,MAAMA,uBAAuB,CAACC;IACnC,MAAMC,UAAUD,UAAUE,WAAW;IACrC,MAAMC,QAAQH,aAAa,qBAAqBA,UAAUI,SAAS;IAEnE,OAAOC,QAAQJ,WAAWE;AAC5B,EAAC;AAED,OAAO,MAAMG,uBAAuB,CAACC;IACnC,MAAMC,OAAOD;IACb,MAAME,UAAUD,KAAKE,QAAQ,EAAEC;IAE/B,IAAI,CAACF,SAAS;QACZ,OAAO;IACT;IAEA,IAAIG,mBAAmBH;IAEvB,IAAIV,qBAAqBS,KAAKR,QAAQ,GAAG;QACvC,IAAI;YACF,MAAMa,YAAY,IAAIC,IAAIL;YAC1BG,mBAAmB,GAAGC,UAAUE,MAAM,GAAGF,UAAUG,QAAQ,CAAC,iBAAiB,CAAC;QAChF,EAAE,OAAM;YACN,MAAMC,oBAAoBR,QAAQS,KAAK,CAAC,IAAI,CAAC,EAAE,CAACA,KAAK,CAAC,IAAI,CAAC,EAAE;YAC7DN,mBAAmB,GAAGK,kBAAkB,iBAAiB,CAAC;QAC5D;IACF;IAEA,OAAO,GAAGL,mBAAmBA,iBAAiBO,QAAQ,CAAC,OAAO,MAAM,IAAI,cAAc,CAAC;AACzF,EAAC;AAED;;;;;;;;CAQC,GACD,OAAO,MAAMC,8BAA8B,CACzCb,KACAc,gBACAC;IAEA,MAAMC,WAAYhB,KAA2CgB;IAE7D,IAAI,CAACA,UAAU;QACb,OAAO;IACT;IAEA,MAAMC,OAAO,GAAGF,SAASG,OAAO,CAAC,QAAQ,IAAI,CAAC,EAAEJ,eAAe,MAAM,EAAEK,mBAAmBH,WAAW;IAErG,OAAO,GAAGC,KAAK,eAAe,CAAC;AACjC,EAAC"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import type { Config, UploadCollectionSlug } from 'payload';
2
- type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, true>>;
2
+ export type ImageKitCollectionOptions = {
3
+ /**
4
+ * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)
5
+ * and deliver them only through short-lived signed URLs, behind Payload access control.
6
+ */
7
+ private?: boolean;
8
+ };
9
+ type ImageKitCollectionConfig = ImageKitCollectionOptions | true;
10
+ type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>;
3
11
  export type PayloadStorageImagekitConfig = {
4
12
  alwaysInsertFields?: boolean;
5
13
  clientUploads?: boolean;
@@ -9,6 +17,12 @@ export type PayloadStorageImagekitConfig = {
9
17
  folder: string;
10
18
  privateKey: string;
11
19
  publicKey: string;
20
+ /**
21
+ * Expiry (in seconds) applied to signed URLs generated for private collections.
22
+ * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short
23
+ * window is safe.
24
+ */
25
+ signedUrlExpiresIn?: number;
12
26
  urlEndpoint: string;
13
27
  };
14
28
  export declare const imagekitStorage: (pluginOptions: PayloadStorageImagekitConfig) => (config: Config) => Config;
package/dist/index.js CHANGED
@@ -3,7 +3,19 @@ import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage';
3
3
  import path from 'node:path';
4
4
  import { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js';
5
5
  import { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js';
6
- import { getAdminThumbnailURL } from './getAdminThumbnailURL.js';
6
+ import { getAdminThumbnailURL, getPrivateAdminThumbnailURL, isVideoOrPdfMimeType } from './getAdminThumbnailURL.js';
7
+ const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
8
+ const PRIVATE_COLLECTION_DESCRIPTION = 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.';
9
+ const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
10
+ /**
11
+ * Whether a specific document is private. The flag stored on the document at upload time
12
+ * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed
13
+ * when it is uploaded and does not change if the collection's `private` setting changes
14
+ * later. Falls back to the collection setting for documents uploaded before this flag existed.
15
+ */ const isDocumentPrivate = (doc, collectionIsPrivate)=>{
16
+ const stored = doc?.imagekit?.isPrivate;
17
+ return typeof stored === 'boolean' ? stored : collectionIsPrivate;
18
+ };
7
19
  const normalizeFolder = (folder)=>{
8
20
  const normalized = folder.trim().replace(/^\/+|\/+$/g, '');
9
21
  if (!normalized) {
@@ -62,20 +74,23 @@ const findUploadDocumentByFilename = async ({ collection, filename, prefix, req
62
74
  }
63
75
  });
64
76
  };
65
- const applyClientUploadContextToData = (data, clientUploadContext)=>{
77
+ const applyClientUploadContextToData = (data, clientUploadContext, buildThumbnailURL = getAdminThumbnailURL, isPrivate = false)=>{
66
78
  if (!isImageKitClientUploadContext(clientUploadContext)) {
67
79
  return data;
68
80
  }
69
81
  data.imagekit = {
70
82
  fileId: clientUploadContext.fileId,
83
+ // Record the collection's privacy at upload time; this is the file's permanent
84
+ // ImageKit status and the source of truth for delivery.
85
+ isPrivate,
71
86
  thumbnailUrl: clientUploadContext.thumbnailUrl,
72
87
  url: clientUploadContext.url
73
88
  };
74
89
  // Preserve the admin preview when the original file was already uploaded client-side.
75
- data.thumbnailURL = getAdminThumbnailURL(data);
90
+ data.thumbnailURL = buildThumbnailURL(data);
76
91
  return data;
77
92
  };
78
- const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
93
+ const imagekitAdapter = ({ apiRoute, client, clientUploads, privateSlugs, rootFolder, signedUrlExpiresIn, urlEndpoint })=>{
79
94
  const fetchUploadedFile = async (fileURL)=>{
80
95
  const response = await fetch(fileURL);
81
96
  if (!response.ok) {
@@ -94,7 +109,24 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
94
109
  statusText: response.statusText
95
110
  });
96
111
  };
97
- return ({ collection })=>({
112
+ return ({ collection })=>{
113
+ const isPrivate = privateSlugs.has(collection.slug);
114
+ // Sign a relative ImageKit path against the configured urlEndpoint. We always sign
115
+ // a computed path (never a stored absolute URL) because ImageKit's signature is
116
+ // derived from the path after stripping urlEndpoint, so the host must match.
117
+ const buildSignedURL = (src, tr)=>client.helper.buildSrc({
118
+ expiresIn: signedUrlExpiresIn,
119
+ signed: true,
120
+ src,
121
+ transformation: tr ? [
122
+ {
123
+ raw: tr
124
+ }
125
+ ] : undefined,
126
+ urlEndpoint
127
+ });
128
+ const buildThumbnailURL = (doc)=>isDocumentPrivate(doc, isPrivate) ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute) : getAdminThumbnailURL(doc);
129
+ return {
98
130
  name: 'imagekit',
99
131
  clientUploads,
100
132
  fields: [
@@ -120,6 +152,46 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
120
152
  readOnly: true
121
153
  }
122
154
  },
155
+ {
156
+ name: 'isPrivate',
157
+ type: 'checkbox',
158
+ admin: {
159
+ description: 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',
160
+ readOnly: true
161
+ },
162
+ label: 'Private file'
163
+ },
164
+ {
165
+ name: 'signedUrl',
166
+ type: 'text',
167
+ admin: {
168
+ // Only relevant for private files; the plain `url` works directly for public ones.
169
+ condition: (_data, siblingData)=>isDocumentPrivate({
170
+ imagekit: siblingData
171
+ }, isPrivate),
172
+ description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,
173
+ readOnly: true
174
+ },
175
+ hooks: {
176
+ // Computed server-side on read so it is always freshly signed and never persisted.
177
+ afterRead: [
178
+ ({ data, siblingData })=>{
179
+ if (!isDocumentPrivate({
180
+ imagekit: siblingData
181
+ }, isPrivate)) {
182
+ return undefined;
183
+ }
184
+ const filename = data?.filename;
185
+ if (!filename) {
186
+ return undefined;
187
+ }
188
+ return buildSignedURL(getFilePath(rootFolder, collection.slug, filename));
189
+ }
190
+ ]
191
+ },
192
+ label: 'Signed URL',
193
+ virtual: true
194
+ },
123
195
  {
124
196
  name: 'thumbnailUrl',
125
197
  type: 'text',
@@ -145,7 +217,7 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
145
217
  },
146
218
  handleUpload: async ({ clientUploadContext, data, file })=>{
147
219
  if (isImageKitClientUploadContext(clientUploadContext)) {
148
- return applyClientUploadContextToData(data, clientUploadContext);
220
+ return applyClientUploadContextToData(data, clientUploadContext, buildThumbnailURL, isPrivate);
149
221
  }
150
222
  const uploadFile = await toFile(file.buffer, file.filename, {
151
223
  type: file.mimeType
@@ -154,15 +226,24 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
154
226
  file: uploadFile,
155
227
  fileName: file.filename,
156
228
  folder: getCollectionFolder(rootFolder, collection.slug),
157
- useUniqueFileName: false
229
+ useUniqueFileName: false,
230
+ ...isPrivate ? {
231
+ isPrivateFile: true
232
+ } : {}
158
233
  });
159
234
  data.imagekit = {
160
235
  fileId: uploaded.fileId,
236
+ isPrivate,
161
237
  thumbnailUrl: uploaded.thumbnailUrl,
162
238
  url: uploaded.url
163
239
  };
164
- // Set the root-level thumbnailURL that Payload's admin interface expects
165
- data.thumbnailURL = getAdminThumbnailURL(data);
240
+ // Set the root-level thumbnailURL that Payload's admin interface expects.
241
+ // `buildThumbnailURL` needs the filename, which Payload may not have copied onto
242
+ // `data` yet at this point, so fall back to the uploaded file's name.
243
+ data.thumbnailURL = buildThumbnailURL({
244
+ ...data,
245
+ filename: data.filename || file.filename
246
+ });
166
247
  return data;
167
248
  },
168
249
  staticHandler: async (req, args)=>{
@@ -173,7 +254,7 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
173
254
  const clientUploadContext = args.params.clientUploadContext;
174
255
  const clientUploadURL = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.url === 'string' && clientUploadContext.url.length > 0 ? clientUploadContext.url : null;
175
256
  const filePath = isImageKitClientUploadContext(clientUploadContext) && typeof clientUploadContext.filePath === 'string' && clientUploadContext.filePath.length > 0 ? clientUploadContext.filePath : getFilePath(rootFolder, args.params.collection, args.params.filename);
176
- const imagekitURL = clientUploadURL || client.helper.buildSrc({
257
+ const imagekitURL = isPrivate ? buildSignedURL(filePath, tr) : clientUploadURL || client.helper.buildSrc({
177
258
  src: filePath,
178
259
  urlEndpoint
179
260
  });
@@ -185,6 +266,18 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
185
266
  prefix: args.params.prefix,
186
267
  req
187
268
  });
269
+ // Sign based on the document's stored status, not the collection's current
270
+ // setting, so files keep working if the collection's privacy is changed later.
271
+ if (isDocumentPrivate(doc, isPrivate)) {
272
+ const mimeType = doc?.mimeType;
273
+ let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename);
274
+ // A `tr` request for a video/PDF is a thumbnail request (the plain transform
275
+ // can't render a preview); serve ImageKit's generated thumbnail instead.
276
+ if (tr && isVideoOrPdfMimeType(mimeType)) {
277
+ signSrc = `${signSrc}/ik-thumbnail.jpg`;
278
+ }
279
+ return Response.redirect(buildSignedURL(signSrc, tr), 302);
280
+ }
188
281
  const imagekitURL = client.helper.buildSrc({
189
282
  src: getStoredImageKitURL(doc) || getFilePath(rootFolder, args.params.collection, args.params.filename),
190
283
  transformation: tr ? [
@@ -196,21 +289,23 @@ const imagekitAdapter = ({ client, clientUploads, rootFolder, urlEndpoint })=>{
196
289
  });
197
290
  return Response.redirect(imagekitURL, 302);
198
291
  }
199
- });
292
+ };
293
+ };
200
294
  };
201
- const toCloudStorageCollections = (collections, adapter, disablePayloadAccessControl)=>{
295
+ const toCloudStorageCollections = (collections, adapter, privateSlugs, disablePayloadAccessControl)=>{
202
296
  const mappedCollections = {};
203
297
  for (const collectionSlug of Object.keys(collections)){
204
298
  mappedCollections[collectionSlug] = {
205
299
  adapter,
206
300
  disableLocalStorage: true,
207
- disablePayloadAccessControl
301
+ // Private files require Payload access control so URLs can be signed at serve time.
302
+ disablePayloadAccessControl: privateSlugs.has(collectionSlug) ? undefined : disablePayloadAccessControl
208
303
  };
209
304
  }
210
305
  return mappedCollections;
211
306
  };
212
307
  export const imagekitStorage = (pluginOptions)=>(config)=>{
213
- const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, folder, privateKey, publicKey, urlEndpoint } = pluginOptions;
308
+ const { alwaysInsertFields, clientUploads, collections, disablePayloadAccessControl, enabled, folder, privateKey, publicKey, signedUrlExpiresIn: signedUrlExpiresInOption, urlEndpoint } = pluginOptions;
214
309
  if (!privateKey?.trim()) {
215
310
  throw new Error('imagekitStorage: `privateKey` is required.');
216
311
  }
@@ -223,14 +318,23 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
223
318
  if (!collections || Object.keys(collections).length === 0) {
224
319
  throw new Error('imagekitStorage: `collections` must include at least one upload collection.');
225
320
  }
321
+ const privateSlugs = new Set(Object.entries(collections).filter(([, value])=>isPrivateCollectionConfig(value)).map(([slug])=>slug));
322
+ if (disablePayloadAccessControl && privateSlugs.size > 0) {
323
+ throw new Error('imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.');
324
+ }
325
+ const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
326
+ const apiRoute = config.routes?.api ?? '/api';
226
327
  const rootFolder = normalizeFolder(folder);
227
328
  const imagekitClient = new ImageKit({
228
329
  privateKey
229
330
  });
230
331
  const adapter = imagekitAdapter({
332
+ apiRoute,
231
333
  client: imagekitClient,
232
334
  clientUploads,
335
+ privateSlugs,
233
336
  rootFolder,
337
+ signedUrlExpiresIn,
234
338
  urlEndpoint
235
339
  });
236
340
  const configuredSlugs = new Set(Object.keys(collections));
@@ -240,11 +344,21 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
240
344
  if (!configuredSlugs.has(collection.slug)) {
241
345
  return collection;
242
346
  }
347
+ const isCollectionPrivate = privateSlugs.has(collection.slug);
348
+ const buildThumbnailURL = (doc)=>isDocumentPrivate(doc, isCollectionPrivate) ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute) : getAdminThumbnailURL(doc);
243
349
  const beforeDocumentControls = collection.admin?.components?.edit?.beforeDocumentControls || [];
350
+ // Surface a "this is a private collection" note in the list and edit views.
351
+ // Preserve an existing string description by appending; leave custom
352
+ // component descriptions untouched.
353
+ const existingDescription = collection.admin?.description;
354
+ const baseAdmin = isCollectionPrivate ? {
355
+ ...collection.admin || {},
356
+ description: typeof existingDescription === 'string' && existingDescription.trim().length > 0 ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}` : existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION
357
+ } : collection.admin;
244
358
  return {
245
359
  ...collection,
246
360
  admin: clientUploads ? {
247
- ...collection.admin || {},
361
+ ...baseAdmin || {},
248
362
  components: {
249
363
  ...collection.admin?.components || {},
250
364
  edit: {
@@ -256,6 +370,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
256
370
  authPath: getImageKitUploadAuthPath(collection.slug),
257
371
  collectionSlug: collection.slug,
258
372
  folder: getCollectionFolder(rootFolder, collection.slug),
373
+ isPrivate: isCollectionPrivate,
259
374
  publicKey
260
375
  },
261
376
  path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler'
@@ -263,7 +378,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
263
378
  ]
264
379
  }
265
380
  }
266
- } : collection.admin,
381
+ } : baseAdmin,
267
382
  hooks: {
268
383
  ...collection.hooks || {},
269
384
  beforeChange: [
@@ -272,13 +387,13 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
272
387
  if (!isRecord(data) || !req.file?.clientUploadContext) {
273
388
  return data;
274
389
  }
275
- return applyClientUploadContextToData(data, req.file.clientUploadContext);
390
+ return applyClientUploadContextToData(data, req.file.clientUploadContext, buildThumbnailURL, isCollectionPrivate);
276
391
  }
277
392
  ]
278
393
  },
279
394
  upload: {
280
395
  ...typeof collection.upload === 'object' ? collection.upload : {},
281
- adminThumbnail: ({ doc })=>getAdminThumbnailURL(doc),
396
+ adminThumbnail: ({ doc })=>buildThumbnailURL(doc),
282
397
  crop: false
283
398
  }
284
399
  };
@@ -297,7 +412,7 @@ export const imagekitStorage = (pluginOptions)=>(config)=>{
297
412
  };
298
413
  return cloudStoragePlugin({
299
414
  alwaysInsertFields,
300
- collections: toCloudStorageCollections(collections, adapter, disablePayloadAccessControl),
415
+ collections: toCloudStorageCollections(collections, adapter, privateSlugs, disablePayloadAccessControl),
301
416
  enabled
302
417
  })(configWithCropDisabled);
303
418
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport { getAdminThumbnailURL } from './getAdminThumbnailURL.js'\n\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, true>>\ntype ImagekitReference = {\n imagekit?: {\n fileId?: string\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImagekitReference\n\nexport type PayloadStorageImagekitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n folder: string\n privateKey: string\n publicKey: string\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImagekitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = getAdminThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n client,\n clientUploads,\n rootFolder,\n urlEndpoint,\n}: {\n client: ImageKit\n clientUploads?: boolean\n rootFolder: string\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n const fileBuffer = await response.arrayBuffer()\n const headers = new Headers()\n const contentType = response.headers.get('Content-Type')\n\n if (contentType) {\n headers.set('Content-Type', contentType)\n }\n\n headers.set('Content-Length', String(fileBuffer.byteLength))\n\n return new Response(fileBuffer, {\n headers,\n status: 200,\n statusText: response.statusText,\n })\n }\n\n return ({ collection }) => ({\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImagekitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(data, clientUploadContext)\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects\n data.thumbnailURL = getAdminThumbnailURL(data)\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imagekitURL =\n clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imagekitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n const imagekitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imagekitURL, 302)\n },\n })\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImagekitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n folder,\n privateKey,\n publicKey,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const rootFolder = normalizeFolder(folder)\n\n const imagekitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n client: imagekitClient,\n clientUploads,\n rootFolder,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(collection.admin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : collection.admin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(data, req.file.clientUploadContext)\n },\n ],\n },\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => getAdminThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imagekitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(collections, adapter, disablePayloadAccessControl),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","imagekit","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","value","getStoredImageKitURL","doc","storedURL","url","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","thumbnailUrl","thumbnailURL","imagekitAdapter","client","clientUploads","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","fileBuffer","arrayBuffer","headers","Headers","contentType","get","set","String","byteLength","Response","statusText","fields","admin","disableListColumn","disableListFilter","hidden","readOnly","interfaceName","generateURL","helper","buildSrc","src","slug","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","mimeType","uploaded","upload","fileName","useUniqueFileName","staticHandler","args","requestURL","URL","tr","searchParams","hasClientUploadContext","params","clientUploadURL","imagekitURL","transformation","raw","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","privateKey","publicKey","imagekitClient","configuredSlugs","Set","configWithCropDisabled","map","has","beforeDocumentControls","components","edit","clientProps","authPath","hooks","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Adapter, CollectionOptions } from '@payloadcms/plugin-cloud-storage/types'\nimport type {\n CollectionSlug,\n Config,\n PayloadRequest,\n TypeWithID,\n UploadCollectionSlug,\n Where,\n} from 'payload'\n\nimport ImageKit, { toFile } from '@imagekit/nodejs'\nimport { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'\nimport path from 'node:path'\n\nimport { getImageKitUploadAuthPath, isImageKitClientUploadContext } from './clientUploads.js'\nimport { createImageKitUploadAuthHandler } from './endpoints/createImageKitUploadAuthHandler.js'\nimport {\n getAdminThumbnailURL,\n getPrivateAdminThumbnailURL,\n isVideoOrPdfMimeType,\n} from './getAdminThumbnailURL.js'\n\nexport type ImageKitCollectionOptions = {\n /**\n * Upload files in this collection as ImageKit private files (`isPrivateFile: true`)\n * and deliver them only through short-lived signed URLs, behind Payload access control.\n */\n private?: boolean\n}\ntype ImageKitCollectionConfig = ImageKitCollectionOptions | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst PRIVATE_COLLECTION_DESCRIPTION =\n 'Files in this collection are uploaded to ImageKit as private files and delivered through short-lived signed URLs.'\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\n/**\n * Whether a specific document is private. The flag stored on the document at upload time\n * (`imagekit.isPrivate`) is the source of truth — a file's privacy on ImageKit is fixed\n * when it is uploaded and does not change if the collection's `private` setting changes\n * later. Falls back to the collection setting for documents uploaded before this flag existed.\n */\nconst isDocumentPrivate = (doc: unknown, collectionIsPrivate: boolean): boolean => {\n const stored = (doc as { imagekit?: { isPrivate?: boolean } } | undefined)?.imagekit?.isPrivate\n\n return typeof stored === 'boolean' ? stored : collectionIsPrivate\n}\ntype ImagekitReference = {\n imagekit?: {\n fileId?: string\n isPrivate?: boolean\n thumbnailUrl?: string\n url?: string\n }\n}\n\ntype UploadDocumentData = {\n thumbnailURL?: string\n} & ImagekitReference\n\nexport type PayloadStorageImagekitConfig = {\n alwaysInsertFields?: boolean\n clientUploads?: boolean\n collections: ImageKitCollectionMap\n disablePayloadAccessControl?: true\n enabled?: boolean\n folder: string\n privateKey: string\n publicKey: string\n /**\n * Expiry (in seconds) applied to signed URLs generated for private collections.\n * Defaults to 900 (15 minutes). URLs are signed fresh on every request, so a short\n * window is safe.\n */\n signedUrlExpiresIn?: number\n urlEndpoint: string\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('imagekitStorage: `folder` is required.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n return undefined\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n !!value && typeof value === 'object'\n\nconst getStoredImageKitURL = (doc: unknown): string | undefined => {\n const storedURL = (doc as ImagekitReference | undefined)?.imagekit?.url\n\n return typeof storedURL === 'string' && storedURL.trim().length > 0 ? storedURL : undefined\n}\n\nconst findUploadDocumentByFilename = async ({\n collection,\n filename,\n prefix,\n req,\n}: {\n collection: string\n filename: string\n prefix?: string\n req: PayloadRequest\n}): Promise<null | TypeWithID> => {\n const constraints: Where[] = [\n {\n filename: {\n equals: filename,\n },\n },\n ]\n\n if (typeof prefix === 'string') {\n constraints.push({\n prefix: {\n equals: prefix,\n },\n })\n }\n\n return req.payload.db.findOne({\n collection,\n req,\n where: {\n and: constraints,\n },\n })\n}\n\nconst applyClientUploadContextToData = <T extends UploadDocumentData>(\n data: T,\n clientUploadContext: unknown,\n buildThumbnailURL: (data: T) => string = getAdminThumbnailURL,\n isPrivate = false,\n): T => {\n if (!isImageKitClientUploadContext(clientUploadContext)) {\n return data\n }\n\n data.imagekit = {\n fileId: clientUploadContext.fileId,\n // Record the collection's privacy at upload time; this is the file's permanent\n // ImageKit status and the source of truth for delivery.\n isPrivate,\n thumbnailUrl: clientUploadContext.thumbnailUrl,\n url: clientUploadContext.url,\n }\n\n // Preserve the admin preview when the original file was already uploaded client-side.\n data.thumbnailURL = buildThumbnailURL(data)\n\n return data\n}\n\nconst imagekitAdapter = ({\n apiRoute,\n client,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n}: {\n apiRoute: string\n client: ImageKit\n clientUploads?: boolean\n privateSlugs: Set<string>\n rootFolder: string\n signedUrlExpiresIn: number\n urlEndpoint: string\n}): Adapter => {\n const fetchUploadedFile = async (fileURL: string): Promise<Response> => {\n const response = await fetch(fileURL)\n\n if (!response.ok) {\n throw new Error(\n `imagekitStorage: failed to fetch uploaded file from ImageKit (${response.status}).`,\n )\n }\n\n const fileBuffer = await response.arrayBuffer()\n const headers = new Headers()\n const contentType = response.headers.get('Content-Type')\n\n if (contentType) {\n headers.set('Content-Type', contentType)\n }\n\n headers.set('Content-Length', String(fileBuffer.byteLength))\n\n return new Response(fileBuffer, {\n headers,\n status: 200,\n statusText: response.statusText,\n })\n }\n\n return ({ collection }) => {\n const isPrivate = privateSlugs.has(collection.slug)\n\n // Sign a relative ImageKit path against the configured urlEndpoint. We always sign\n // a computed path (never a stored absolute URL) because ImageKit's signature is\n // derived from the path after stripping urlEndpoint, so the host must match.\n const buildSignedURL = (src: string, tr?: null | string): string =>\n client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src,\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n const buildThumbnailURL = (doc: UploadDocumentData): string =>\n isDocumentPrivate(doc, isPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n return {\n name: 'imagekit',\n clientUploads,\n fields: [\n {\n name: 'imagekit',\n type: 'group',\n admin: {\n disableListColumn: true,\n disableListFilter: true,\n },\n fields: [\n {\n name: 'fileId',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n {\n name: 'url',\n type: 'text',\n admin: {\n readOnly: true,\n },\n },\n {\n name: 'isPrivate',\n type: 'checkbox',\n admin: {\n description:\n 'Whether this file is stored as a private file on ImageKit. Set at upload time from the collection setting and fixed for the life of the file — re-upload to change it.',\n readOnly: true,\n },\n label: 'Private file',\n },\n {\n name: 'signedUrl',\n type: 'text',\n admin: {\n // Only relevant for private files; the plain `url` works directly for public ones.\n condition: (_data, siblingData) =>\n isDocumentPrivate({ imagekit: siblingData }, isPrivate),\n description: `Temporary signed URL for reaching this private file directly on ImageKit. Expires ${signedUrlExpiresIn} seconds after load and is regenerated on every read. Note: anyone with this URL can access the file until it expires — it is not gated by Payload access control.`,\n readOnly: true,\n },\n hooks: {\n // Computed server-side on read so it is always freshly signed and never persisted.\n afterRead: [\n ({ data, siblingData }) => {\n if (!isDocumentPrivate({ imagekit: siblingData }, isPrivate)) {\n return undefined\n }\n\n const filename = (data as { filename?: string } | undefined)?.filename\n\n if (!filename) {\n return undefined\n }\n\n return buildSignedURL(getFilePath(rootFolder, collection.slug, filename))\n },\n ],\n },\n label: 'Signed URL',\n virtual: true,\n },\n {\n name: 'thumbnailUrl',\n type: 'text',\n admin: {\n hidden: true,\n },\n },\n ],\n interfaceName: 'imagekitFields',\n },\n ],\n generateURL: ({ filename }) =>\n client.helper.buildSrc({\n src: getFilePath(rootFolder, collection.slug, filename),\n urlEndpoint,\n }),\n handleDelete: async ({ doc, filename }) => {\n const storedFileId = (doc as ImagekitReference & typeof doc).imagekit?.fileId\n const fileId =\n storedFileId ||\n (await findImageKitFileId(client, getFilePath(rootFolder, collection.slug, filename)))\n\n if (!fileId) {\n return\n }\n\n await client.files.delete(fileId)\n },\n handleUpload: async ({ clientUploadContext, data, file }) => {\n if (isImageKitClientUploadContext(clientUploadContext)) {\n return applyClientUploadContextToData(\n data,\n clientUploadContext,\n buildThumbnailURL,\n isPrivate,\n )\n }\n\n const uploadFile = await toFile(file.buffer, file.filename, { type: file.mimeType })\n\n const uploaded = await client.files.upload({\n file: uploadFile,\n fileName: file.filename,\n folder: getCollectionFolder(rootFolder, collection.slug),\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n data.imagekit = {\n fileId: uploaded.fileId,\n isPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n\n // Set the root-level thumbnailURL that Payload's admin interface expects.\n // `buildThumbnailURL` needs the filename, which Payload may not have copied onto\n // `data` yet at this point, so fall back to the uploaded file's name.\n data.thumbnailURL = buildThumbnailURL({\n ...data,\n filename: (data as { filename?: string }).filename || file.filename,\n })\n\n return data\n },\n staticHandler: async (req, args) => {\n const requestURL = new URL(req.url || 'http://localhost')\n const tr = requestURL.searchParams.get('tr')\n const hasClientUploadContext = 'clientUploadContext' in args.params\n\n if (hasClientUploadContext) {\n const clientUploadContext = args.params.clientUploadContext\n const clientUploadURL =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.url === 'string' &&\n clientUploadContext.url.length > 0\n ? clientUploadContext.url\n : null\n const filePath =\n isImageKitClientUploadContext(clientUploadContext) &&\n typeof clientUploadContext.filePath === 'string' &&\n clientUploadContext.filePath.length > 0\n ? clientUploadContext.filePath\n : getFilePath(rootFolder, args.params.collection, args.params.filename)\n const imagekitURL = isPrivate\n ? buildSignedURL(filePath, tr)\n : clientUploadURL ||\n client.helper.buildSrc({\n src: filePath,\n urlEndpoint,\n })\n\n return fetchUploadedFile(imagekitURL)\n }\n\n const doc =\n args.doc ||\n (await findUploadDocumentByFilename({\n collection: args.params.collection,\n filename: args.params.filename,\n prefix: args.params.prefix,\n req,\n }))\n\n // Sign based on the document's stored status, not the collection's current\n // setting, so files keep working if the collection's privacy is changed later.\n if (isDocumentPrivate(doc, isPrivate)) {\n const mimeType = (doc as { mimeType?: string } | null)?.mimeType\n let signSrc = getFilePath(rootFolder, args.params.collection, args.params.filename)\n\n // A `tr` request for a video/PDF is a thumbnail request (the plain transform\n // can't render a preview); serve ImageKit's generated thumbnail instead.\n if (tr && isVideoOrPdfMimeType(mimeType)) {\n signSrc = `${signSrc}/ik-thumbnail.jpg`\n }\n\n return Response.redirect(buildSignedURL(signSrc, tr), 302)\n }\n\n const imagekitURL = client.helper.buildSrc({\n src:\n getStoredImageKitURL(doc) ||\n getFilePath(rootFolder, args.params.collection, args.params.filename),\n transformation: tr ? [{ raw: tr }] : undefined,\n urlEndpoint,\n })\n\n return Response.redirect(imagekitURL, 302)\n },\n }\n }\n}\n\nconst toCloudStorageCollections = (\n collections: ImageKitCollectionMap,\n adapter: Adapter,\n privateSlugs: Set<string>,\n disablePayloadAccessControl?: true,\n): Partial<Record<CollectionSlug, CollectionOptions>> => {\n const mappedCollections: Partial<Record<CollectionSlug, CollectionOptions>> = {}\n\n for (const collectionSlug of Object.keys(collections)) {\n mappedCollections[collectionSlug] = {\n adapter,\n disableLocalStorage: true,\n // Private files require Payload access control so URLs can be signed at serve time.\n disablePayloadAccessControl: privateSlugs.has(collectionSlug)\n ? undefined\n : disablePayloadAccessControl,\n }\n }\n\n return mappedCollections\n}\n\nexport const imagekitStorage =\n (pluginOptions: PayloadStorageImagekitConfig) =>\n (config: Config): Config => {\n const {\n alwaysInsertFields,\n clientUploads,\n collections,\n disablePayloadAccessControl,\n enabled,\n folder,\n privateKey,\n publicKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n urlEndpoint,\n } = pluginOptions\n\n if (!privateKey?.trim()) {\n throw new Error('imagekitStorage: `privateKey` is required.')\n }\n\n if (!publicKey?.trim()) {\n throw new Error('imagekitStorage: `publicKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('imagekitStorage: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('imagekitStorage: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n\n if (disablePayloadAccessControl && privateSlugs.size > 0) {\n throw new Error(\n 'imagekitStorage: `disablePayloadAccessControl` cannot be used with private collections. Private files require Payload access control to remain enabled so URLs can be signed at serve time.',\n )\n }\n\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = config.routes?.api ?? '/api'\n\n const rootFolder = normalizeFolder(folder)\n\n const imagekitClient = new ImageKit({ privateKey })\n const adapter = imagekitAdapter({\n apiRoute,\n client: imagekitClient,\n clientUploads,\n privateSlugs,\n rootFolder,\n signedUrlExpiresIn,\n urlEndpoint,\n })\n\n const configuredSlugs = new Set(Object.keys(collections))\n const configWithCropDisabled: Config = {\n ...config,\n collections: (config.collections || []).map((collection) => {\n if (!configuredSlugs.has(collection.slug)) {\n return collection\n }\n\n const isCollectionPrivate = privateSlugs.has(collection.slug)\n const buildThumbnailURL = (doc: unknown): string =>\n isDocumentPrivate(doc, isCollectionPrivate)\n ? getPrivateAdminThumbnailURL(doc, collection.slug, apiRoute)\n : getAdminThumbnailURL(doc)\n\n const beforeDocumentControls =\n collection.admin?.components?.edit?.beforeDocumentControls || []\n\n // Surface a \"this is a private collection\" note in the list and edit views.\n // Preserve an existing string description by appending; leave custom\n // component descriptions untouched.\n const existingDescription = collection.admin?.description\n const baseAdmin = isCollectionPrivate\n ? {\n ...(collection.admin || {}),\n description:\n typeof existingDescription === 'string' && existingDescription.trim().length > 0\n ? `${existingDescription} ${PRIVATE_COLLECTION_DESCRIPTION}`\n : (existingDescription ?? PRIVATE_COLLECTION_DESCRIPTION),\n }\n : collection.admin\n\n return {\n ...collection,\n admin: clientUploads\n ? {\n ...(baseAdmin || {}),\n components: {\n ...(collection.admin?.components || {}),\n edit: {\n ...(collection.admin?.components?.edit || {}),\n beforeDocumentControls: [\n ...beforeDocumentControls,\n {\n clientProps: {\n authPath: getImageKitUploadAuthPath(collection.slug),\n collectionSlug: collection.slug,\n folder: getCollectionFolder(rootFolder, collection.slug),\n isPrivate: isCollectionPrivate,\n publicKey,\n },\n path: '@humaan/payload-storage-imagekit/client#ImageKitUploadHandler',\n },\n ],\n },\n },\n }\n : baseAdmin,\n hooks: {\n ...(collection.hooks || {}),\n beforeChange: [\n ...(collection.hooks?.beforeChange || []),\n ({ data, req }) => {\n if (!isRecord(data) || !req.file?.clientUploadContext) {\n return data\n }\n\n return applyClientUploadContextToData(\n data,\n req.file.clientUploadContext,\n buildThumbnailURL,\n isCollectionPrivate,\n )\n },\n ],\n },\n upload: {\n ...(typeof collection.upload === 'object' ? collection.upload : {}),\n adminThumbnail: ({ doc }) => buildThumbnailURL(doc),\n crop: false,\n },\n }\n }),\n endpoints: clientUploads\n ? [\n ...(config.endpoints || []),\n {\n handler: createImageKitUploadAuthHandler({\n client: imagekitClient,\n collections,\n }),\n method: 'get',\n path: `${getImageKitUploadAuthPath(':collection')}`,\n },\n ]\n : config.endpoints,\n }\n\n return cloudStoragePlugin({\n alwaysInsertFields,\n collections: toCloudStorageCollections(\n collections,\n adapter,\n privateSlugs,\n disablePayloadAccessControl,\n ),\n enabled,\n })(configWithCropDisabled)\n }\n\nexport { migrateFolder } from './migrate.js'\nexport type { MigrateFolderOptions, MigrationJobResult } from './migrate.js'\n\n// Backward-compatible alias\nexport const payloadStorageImagekit = imagekitStorage\n"],"names":["ImageKit","toFile","cloudStoragePlugin","path","getImageKitUploadAuthPath","isImageKitClientUploadContext","createImageKitUploadAuthHandler","getAdminThumbnailURL","getPrivateAdminThumbnailURL","isVideoOrPdfMimeType","DEFAULT_SIGNED_URL_EXPIRES_IN","PRIVATE_COLLECTION_DESCRIPTION","isPrivateCollectionConfig","value","private","isDocumentPrivate","doc","collectionIsPrivate","stored","imagekit","isPrivate","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","findImageKitFileId","filePath","parentPath","dirname","name","basename","limit","skip","assets","list","type","found","find","asset","fileId","length","undefined","isRecord","getStoredImageKitURL","storedURL","url","findUploadDocumentByFilename","collection","prefix","req","constraints","equals","push","payload","db","findOne","where","and","applyClientUploadContextToData","data","clientUploadContext","buildThumbnailURL","thumbnailUrl","thumbnailURL","imagekitAdapter","apiRoute","client","clientUploads","privateSlugs","signedUrlExpiresIn","urlEndpoint","fetchUploadedFile","fileURL","response","fetch","ok","status","fileBuffer","arrayBuffer","headers","Headers","contentType","get","set","String","byteLength","Response","statusText","has","slug","buildSignedURL","src","tr","helper","buildSrc","expiresIn","signed","transformation","raw","fields","admin","disableListColumn","disableListFilter","hidden","readOnly","description","label","condition","_data","siblingData","hooks","afterRead","virtual","interfaceName","generateURL","handleDelete","storedFileId","files","delete","handleUpload","file","uploadFile","buffer","mimeType","uploaded","upload","fileName","useUniqueFileName","isPrivateFile","staticHandler","args","requestURL","URL","searchParams","hasClientUploadContext","params","clientUploadURL","imagekitURL","signSrc","redirect","toCloudStorageCollections","collections","adapter","disablePayloadAccessControl","mappedCollections","Object","keys","disableLocalStorage","imagekitStorage","pluginOptions","config","alwaysInsertFields","enabled","privateKey","publicKey","signedUrlExpiresInOption","Set","entries","filter","map","size","routes","api","imagekitClient","configuredSlugs","configWithCropDisabled","isCollectionPrivate","beforeDocumentControls","components","edit","existingDescription","baseAdmin","clientProps","authPath","beforeChange","adminThumbnail","crop","endpoints","handler","method","migrateFolder","payloadStorageImagekit"],"mappings":"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"}
package/dist/migrate.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import type { Payload, UploadCollectionSlug } from 'payload';
2
- type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, true>>;
2
+ type ImageKitCollectionConfig = {
3
+ private?: boolean;
4
+ } | true;
5
+ type ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>;
3
6
  export type MigrateFolderOptions = {
4
7
  /**
5
- * The Payload upload collections to migrate. Keys are collection slugs, value is `true`.
8
+ * The Payload upload collections to migrate. Keys are collection slugs; the value is
9
+ * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.
6
10
  */
7
11
  collections: ImageKitCollectionMap;
8
12
  /**
@@ -25,6 +29,12 @@ export type MigrateFolderOptions = {
25
29
  * ImageKit private API key.
26
30
  */
27
31
  privateKey: string;
32
+ /**
33
+ * Expiry (in seconds) for the signed URL used to read a private source file when the
34
+ * fast server-side copy is not possible and the migrator falls back to re-uploading
35
+ * from the source URL. Defaults to 900 (15 minutes).
36
+ */
37
+ signedUrlExpiresIn?: number;
28
38
  /**
29
39
  * The destination ImageKit root folder (e.g. `/production`).
30
40
  */
package/dist/migrate.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import ImageKit from '@imagekit/nodejs';
2
2
  import path from 'node:path';
3
- import { getAdminThumbnailURL } from './getAdminThumbnailURL.js';
3
+ import { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js';
4
+ const DEFAULT_SIGNED_URL_EXPIRES_IN = 900;
5
+ const isPrivateCollectionConfig = (value)=>value !== true && typeof value === 'object' && value?.private === true;
4
6
  const normalizeFolder = (folder)=>{
5
7
  const normalized = folder.trim().replace(/^\/+|\/+$/g, '');
6
8
  if (!normalized) {
@@ -63,12 +65,15 @@ const isImageKitFileNotFoundError = (error)=>{
63
65
  const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : '';
64
66
  return /does not exist|not found|no such file/i.test(message);
65
67
  };
66
- const uploadFileFromURL = async ({ client, fileName, folder, sourceUrl })=>{
68
+ const uploadFileFromURL = async ({ client, fileName, folder, isPrivate, sourceUrl })=>{
67
69
  const uploaded = await client.files.upload({
68
70
  file: sourceUrl,
69
71
  fileName,
70
72
  folder,
71
- useUniqueFileName: false
73
+ useUniqueFileName: false,
74
+ ...isPrivate ? {
75
+ isPrivateFile: true
76
+ } : {}
72
77
  });
73
78
  if (!uploaded.fileId || !uploaded.url) {
74
79
  throw new Error(`migrateFolder: upload from URL did not return file details for "${fileName}".`);
@@ -105,7 +110,7 @@ const readFileIdFromCopyResponse = async (response)=>{
105
110
  return undefined;
106
111
  };
107
112
  export const migrateFolder = async (options)=>{
108
- const { collections, dryRun = true, from, payload, privateKey, to, urlEndpoint } = options;
113
+ const { collections, dryRun = true, from, payload, privateKey, signedUrlExpiresIn: signedUrlExpiresInOption, to, urlEndpoint } = options;
109
114
  if (!privateKey?.trim()) {
110
115
  throw new Error('migrateFolder: `privateKey` is required.');
111
116
  }
@@ -115,6 +120,9 @@ export const migrateFolder = async (options)=>{
115
120
  if (!collections || Object.keys(collections).length === 0) {
116
121
  throw new Error('migrateFolder: `collections` must include at least one upload collection.');
117
122
  }
123
+ const privateSlugs = new Set(Object.entries(collections).filter(([, value])=>isPrivateCollectionConfig(value)).map(([slug])=>slug));
124
+ const signedUrlExpiresIn = typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0 ? signedUrlExpiresInOption : DEFAULT_SIGNED_URL_EXPIRES_IN;
125
+ const apiRoute = payload?.config.routes?.api ?? '/api';
118
126
  const fromFolder = normalizeFolder(from);
119
127
  const toFolder = normalizeFolder(to);
120
128
  if (fromFolder === toFolder) {
@@ -145,7 +153,8 @@ export const migrateFolder = async (options)=>{
145
153
  if (payload) {
146
154
  console.log('[migrateFolder] Starting document-by-document migration.');
147
155
  for (const slug of collectionSlugs){
148
- console.log(`[migrateFolder] Migrating collection "${slug}"...`);
156
+ const isCollectionPrivate = privateSlugs.has(slug);
157
+ console.log(`[migrateFolder] Migrating collection "${slug}"${isCollectionPrivate ? ' (private)' : ''}...`);
149
158
  const found = await payload.find({
150
159
  collection: slug,
151
160
  depth: 0,
@@ -201,22 +210,35 @@ export const migrateFolder = async (options)=>{
201
210
  throw error;
202
211
  }
203
212
  console.warn(`[migrateFolder] Source file not found at ${sourceFilePath}. Falling back to stored ImageKit URL for "${filename}".`);
213
+ // A private source file 401s on its plain URL, so sign it before ImageKit
214
+ // fetches it, and re-upload as private so the copy keeps its privacy.
215
+ const sourceUrl = isCollectionPrivate ? client.helper.buildSrc({
216
+ expiresIn: signedUrlExpiresIn,
217
+ signed: true,
218
+ src: existingImagekit.url,
219
+ urlEndpoint
220
+ }) : existingImagekit.url;
204
221
  uploaded = await uploadFileFromURL({
205
222
  client,
206
223
  fileName: filename,
207
224
  folder: destinationFolder,
208
- sourceUrl: existingImagekit.url
225
+ isPrivate: isCollectionPrivate,
226
+ sourceUrl
209
227
  });
210
228
  }
211
229
  const canonicalUrl = client.helper.buildSrc({
212
230
  src: destinationFilePath,
213
231
  urlEndpoint
214
232
  });
215
- const adminThumbnailURL = getAdminThumbnailURL({
233
+ const mimeType = doc.mimeType;
234
+ const adminThumbnailURL = isCollectionPrivate ? getPrivateAdminThumbnailURL({
235
+ filename,
236
+ mimeType
237
+ }, slug, apiRoute) : getAdminThumbnailURL({
216
238
  imagekit: {
217
239
  url: uploaded.url
218
240
  },
219
- mimeType: doc.mimeType
241
+ mimeType
220
242
  });
221
243
  // Update the document with new ImageKit data
222
244
  await payload.update({
@@ -225,6 +247,7 @@ export const migrateFolder = async (options)=>{
225
247
  data: {
226
248
  imagekit: {
227
249
  fileId: uploaded.fileId,
250
+ isPrivate: isCollectionPrivate,
228
251
  thumbnailUrl: uploaded.thumbnailUrl,
229
252
  url: uploaded.url
230
253
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\nimport { getAdminThumbnailURL } from './getAdminThumbnailURL.js'\n\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, true>>\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs, value is `true`.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` and `url` fields\n * in the database will be updated to reflect the new file locations after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to\n * reconstruct canonical `url` values when updating the database.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getUploadedImagekitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { fileId?: unknown; file_id?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const { collections, dryRun = true, from, payload, privateKey, to, urlEndpoint } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n console.log(`[migrateFolder] Migrating collection \"${slug}\"...`)\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImagekit = (doc as any).imagekit\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have ImageKit data or filename\n if (!filename || !existingImagekit?.fileId || !existingImagekit?.url) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - missing filename or ImageKit data`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(`[migrateFolder] [dry-run] Would upload: ${toFolder}/${slug}/${filename}`)\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n try {\n const sourceFilePath = getFilePath(fromFolder, slug, filename)\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const destinationFilePath = getFilePath(toFolder, slug, filename)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `migrateFolder: copy request completed for \"${sourceFilePath}\", but the destination asset at \"${destinationFilePath}\" was not discoverable via ImageKit after retrying.`,\n )\n }\n\n uploaded = await getUploadedImagekitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found at ${sourceFilePath}. Falling back to stored ImageKit URL for \"${filename}\".`,\n )\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: filename,\n folder: destinationFolder,\n sourceUrl: existingImagekit.url,\n })\n }\n\n const canonicalUrl = client.helper.buildSrc({\n src: destinationFilePath,\n urlEndpoint,\n })\n\n const adminThumbnailURL = getAdminThumbnailURL({\n imagekit: {\n url: uploaded.url,\n },\n mimeType: (doc as { mimeType?: string }).mimeType,\n })\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n thumbnailURL: adminThumbnailURL || canonicalUrl,\n url: canonicalUrl,\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${filename} (${doc.id})`)\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${filename}\":`,\n error,\n )\n }\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","getAdminThumbnailURL","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","ensureFolderExists","client","folderPath","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","filePath","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","sourceUrl","uploaded","files","upload","file","useUniqueFileName","url","thumbnailUrl","getUploadedImagekitFields","get","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","to","urlEndpoint","Object","keys","fromFolder","toFolder","console","log","collectionSlugs","results","slug","destinationFolderPath","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","existingImagekit","id","sourceFilePath","destinationFolder","destinationFilePath","copyResponse","copy","destinationPath","asResponse","copiedFileId","warn","canonicalUrl","helper","buildSrc","src","adminThumbnailURL","mimeType","update","data","thumbnailURL","push","jobId","status"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/migrate.ts"],"sourcesContent":["import type { Payload, UploadCollectionSlug } from 'payload'\n\nimport ImageKit from '@imagekit/nodejs'\nimport path from 'node:path'\n\nimport { getAdminThumbnailURL, getPrivateAdminThumbnailURL } from './getAdminThumbnailURL.js'\n\ntype ImageKitCollectionConfig = { private?: boolean } | true\ntype ImageKitCollectionMap = Partial<Record<UploadCollectionSlug, ImageKitCollectionConfig>>\n\nconst DEFAULT_SIGNED_URL_EXPIRES_IN = 900\n\nconst isPrivateCollectionConfig = (value: ImageKitCollectionConfig | undefined): boolean =>\n value !== true && typeof value === 'object' && value?.private === true\n\nexport type MigrateFolderOptions = {\n /**\n * The Payload upload collections to migrate. Keys are collection slugs; the value is\n * `true`, or `{ private: true }` to mark migrated files as ImageKit private files.\n */\n collections: ImageKitCollectionMap\n /**\n * Dry-run mode. When `true`, migration logs what would happen without writing\n * to ImageKit or Payload. Defaults to `true`.\n * @default true\n */\n dryRun?: boolean\n /**\n * The source ImageKit root folder (e.g. `/staging`). Each collection subfolder\n * (`/staging/media`) will be copied into `to`.\n */\n from: string\n /**\n * Optional Payload instance. When provided, the stored `imagekit.*` and `url` fields\n * in the database will be updated to reflect the new file locations after copying.\n */\n payload?: Payload\n /**\n * ImageKit private API key.\n */\n privateKey: string\n /**\n * Expiry (in seconds) for the signed URL used to read a private source file when the\n * fast server-side copy is not possible and the migrator falls back to re-uploading\n * from the source URL. Defaults to 900 (15 minutes).\n */\n signedUrlExpiresIn?: number\n /**\n * The destination ImageKit root folder (e.g. `/production`).\n */\n to: string\n /**\n * ImageKit URL endpoint (e.g. `https://ik.imagekit.io/your_id`). Required to\n * reconstruct canonical `url` values when updating the database.\n */\n urlEndpoint: string\n}\n\nexport type MigrationJobResult = {\n collectionSlug: string\n jobId: string\n status: 'Completed' | 'Pending'\n}\n\nconst normalizeFolder = (folder: string): string => {\n const normalized = folder.trim().replace(/^\\/+|\\/+$/g, '')\n\n if (!normalized) {\n throw new Error('migrateFolder: folder path cannot be empty.')\n }\n\n return `/${normalized}`\n}\n\nconst getCollectionFolder = (rootFolder: string, collectionSlug: string): string =>\n path.posix.join(rootFolder, collectionSlug)\n\nconst getFilePath = (rootFolder: string, collectionSlug: string, filename: string): string =>\n path.posix.join(getCollectionFolder(rootFolder, collectionSlug), filename)\n\nconst ensureFolderExists = async (client: ImageKit, folderPath: string): Promise<void> => {\n const normalized = normalizeFolder(folderPath)\n const segments = normalized.split('/').filter(Boolean)\n\n let parentFolderPath = '/'\n\n for (const folderName of segments) {\n try {\n await client.folders.create({\n folderName,\n parentFolderPath,\n })\n } catch (error) {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n if (!/already exists/i.test(message)) {\n throw error\n }\n }\n\n parentFolderPath = path.posix.join(parentFolderPath, folderName)\n }\n}\n\nconst findImageKitFileId = async (\n imagekit: ImageKit,\n filePath: string,\n options?: {\n retries?: number\n retryDelayMs?: number\n },\n): Promise<string | undefined> => {\n const parentPath = path.posix.dirname(filePath)\n const name = path.posix.basename(filePath)\n\n const retries = options?.retries ?? 0\n const retryDelayMs = options?.retryDelayMs ?? 250\n const limit = 100\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n for (let skip = 0; ; skip += limit) {\n const assets = await imagekit.assets.list({\n type: 'file',\n limit,\n path: parentPath === '/' ? '/' : `${parentPath}/`,\n skip,\n })\n\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n break\n }\n }\n\n if (attempt < retries) {\n await new Promise((resolve) => {\n setTimeout(resolve, retryDelayMs)\n })\n }\n }\n\n return undefined\n}\n\nconst isImageKitFileNotFoundError = (error: unknown): boolean => {\n const message =\n error && typeof error === 'object' && 'message' in error ? String(error.message) : ''\n\n return /does not exist|not found|no such file/i.test(message)\n}\n\nconst uploadFileFromURL = async ({\n client,\n fileName,\n folder,\n isPrivate,\n sourceUrl,\n}: {\n client: ImageKit\n fileName: string\n folder: string\n isPrivate?: boolean\n sourceUrl: string\n}) => {\n const uploaded = await client.files.upload({\n file: sourceUrl,\n fileName,\n folder,\n useUniqueFileName: false,\n ...(isPrivate ? { isPrivateFile: true } : {}),\n })\n\n if (!uploaded.fileId || !uploaded.url) {\n throw new Error(`migrateFolder: upload from URL did not return file details for \"${fileName}\".`)\n }\n\n return {\n fileId: uploaded.fileId,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n }\n}\n\nconst getUploadedImagekitFields = async ({\n client,\n fileId,\n}: {\n client: ImageKit\n fileId: string\n}): Promise<{ fileId: string; thumbnailUrl?: string; url: string }> => {\n const file = await client.files.get(fileId)\n\n if (!file.fileId || !file.url) {\n throw new Error(`migrateFolder: missing ImageKit file details for \"${fileId}\".`)\n }\n\n return {\n fileId: file.fileId,\n thumbnailUrl: file.thumbnail,\n url: file.url,\n }\n}\n\nconst readFileIdFromCopyResponse = async (response: Response): Promise<string | undefined> => {\n try {\n const result = (await response.json()) as { file_id?: unknown; fileId?: unknown }\n\n if (typeof result.fileId === 'string' && result.fileId.length > 0) {\n return result.fileId\n }\n\n if (typeof result.file_id === 'string' && result.file_id.length > 0) {\n return result.file_id\n }\n } catch {\n // Ignore empty or non-JSON responses and fall back to asset discovery.\n }\n\n return undefined\n}\n\nexport const migrateFolder = async (\n options: MigrateFolderOptions,\n): Promise<MigrationJobResult[]> => {\n const {\n collections,\n dryRun = true,\n from,\n payload,\n privateKey,\n signedUrlExpiresIn: signedUrlExpiresInOption,\n to,\n urlEndpoint,\n } = options\n\n if (!privateKey?.trim()) {\n throw new Error('migrateFolder: `privateKey` is required.')\n }\n\n if (!urlEndpoint?.trim()) {\n throw new Error('migrateFolder: `urlEndpoint` is required.')\n }\n\n if (!collections || Object.keys(collections).length === 0) {\n throw new Error('migrateFolder: `collections` must include at least one upload collection.')\n }\n\n const privateSlugs = new Set(\n Object.entries(collections)\n .filter(([, value]) => isPrivateCollectionConfig(value))\n .map(([slug]) => slug),\n )\n const signedUrlExpiresIn =\n typeof signedUrlExpiresInOption === 'number' && signedUrlExpiresInOption > 0\n ? signedUrlExpiresInOption\n : DEFAULT_SIGNED_URL_EXPIRES_IN\n const apiRoute = payload?.config.routes?.api ?? '/api'\n\n const fromFolder = normalizeFolder(from)\n const toFolder = normalizeFolder(to)\n\n if (fromFolder === toFolder) {\n throw new Error('migrateFolder: `from` and `to` cannot be the same folder.')\n }\n\n const client = new ImageKit({ privateKey })\n\n console.log(\n `[migrateFolder] Starting ${dryRun ? 'DRY-RUN' : 'LIVE'} migration from ${fromFolder} to ${toFolder} for ${Object.keys(collections).length} collection(s).`,\n )\n\n if (dryRun) {\n console.log(\n `[migrateFolder] [dry-run] Would ensure destination root folder exists: ${toFolder}`,\n )\n } else {\n console.log(`[migrateFolder] Ensuring destination root folder exists: ${toFolder}`)\n await ensureFolderExists(client, toFolder)\n }\n\n // Always copy file-by-file per collection\n const collectionSlugs = Object.keys(collections)\n const results: MigrationJobResult[] = []\n\n // Ensure destination folder structure exists\n if (!dryRun) {\n for (const slug of collectionSlugs) {\n const destinationFolderPath = path.posix.join(toFolder, slug)\n console.log(`[migrateFolder] Ensuring destination folder: ${destinationFolderPath}`)\n await ensureFolderExists(client, destinationFolderPath)\n }\n }\n\n // Process documents directly like the old migration script\n if (payload) {\n console.log('[migrateFolder] Starting document-by-document migration.')\n\n for (const slug of collectionSlugs) {\n const isCollectionPrivate = privateSlugs.has(slug)\n\n console.log(\n `[migrateFolder] Migrating collection \"${slug}\"${isCollectionPrivate ? ' (private)' : ''}...`,\n )\n\n const found = await payload.find({\n collection: slug,\n depth: 0,\n limit: 0,\n pagination: false,\n })\n\n console.log(\n `[migrateFolder] Processing ${found.docs.length} document(s) in collection \"${slug}\".`,\n )\n\n let updatedCount = 0\n let skippedCount = 0\n let errorCount = 0\n\n // Process each document one by one, like the old migration script\n for (let i = 0; i < found.docs.length; i++) {\n const doc = found.docs[i]\n const filename = (doc as { filename?: string }).filename\n const existingImagekit = (doc as any).imagekit\n\n console.log(`[migrateFolder] [${i + 1}/${found.docs.length}] Processing document ${doc.id}`)\n\n // Skip items that don't have ImageKit data or filename\n if (!filename || !existingImagekit?.fileId || !existingImagekit?.url) {\n skippedCount += 1\n console.log(\n `[migrateFolder] Skipping document ${doc.id} - missing filename or ImageKit data`,\n )\n continue\n }\n\n if (dryRun) {\n console.log(`[migrateFolder] [dry-run] Would upload: ${toFolder}/${slug}/${filename}`)\n console.log(`[migrateFolder] [dry-run] Would update Payload document: ${doc.id}`)\n } else {\n try {\n const sourceFilePath = getFilePath(fromFolder, slug, filename)\n const destinationFolder = getCollectionFolder(toFolder, slug)\n const destinationFilePath = getFilePath(toFolder, slug, filename)\n\n let uploaded: { fileId: string; thumbnailUrl?: string; url: string }\n\n try {\n const copyResponse = await client.files\n .copy({\n destinationPath: destinationFolder,\n sourceFilePath,\n })\n .asResponse()\n\n let copiedFileId = await readFileIdFromCopyResponse(copyResponse)\n\n if (!copiedFileId) {\n copiedFileId = await findImageKitFileId(client, destinationFilePath, {\n retries: 20,\n retryDelayMs: 1000,\n })\n }\n\n if (!copiedFileId) {\n throw new Error(\n `migrateFolder: copy request completed for \"${sourceFilePath}\", but the destination asset at \"${destinationFilePath}\" was not discoverable via ImageKit after retrying.`,\n )\n }\n\n uploaded = await getUploadedImagekitFields({\n client,\n fileId: copiedFileId,\n })\n } catch (error) {\n if (!isImageKitFileNotFoundError(error)) {\n throw error\n }\n\n console.warn(\n `[migrateFolder] Source file not found at ${sourceFilePath}. Falling back to stored ImageKit URL for \"${filename}\".`,\n )\n\n // A private source file 401s on its plain URL, so sign it before ImageKit\n // fetches it, and re-upload as private so the copy keeps its privacy.\n const sourceUrl = isCollectionPrivate\n ? client.helper.buildSrc({\n expiresIn: signedUrlExpiresIn,\n signed: true,\n src: existingImagekit.url,\n urlEndpoint,\n })\n : existingImagekit.url\n\n uploaded = await uploadFileFromURL({\n client,\n fileName: filename,\n folder: destinationFolder,\n isPrivate: isCollectionPrivate,\n sourceUrl,\n })\n }\n\n const canonicalUrl = client.helper.buildSrc({\n src: destinationFilePath,\n urlEndpoint,\n })\n\n const mimeType = (doc as { mimeType?: string }).mimeType\n const adminThumbnailURL = isCollectionPrivate\n ? getPrivateAdminThumbnailURL({ filename, mimeType }, slug, apiRoute)\n : getAdminThumbnailURL({ imagekit: { url: uploaded.url }, mimeType })\n\n // Update the document with new ImageKit data\n await payload.update({\n id: doc.id,\n collection: slug,\n data: {\n imagekit: {\n fileId: uploaded.fileId,\n isPrivate: isCollectionPrivate,\n thumbnailUrl: uploaded.thumbnailUrl,\n url: uploaded.url,\n },\n thumbnailURL: adminThumbnailURL || canonicalUrl,\n url: canonicalUrl,\n },\n })\n\n updatedCount += 1\n console.log(`[migrateFolder] Content migrated! ${filename} (${doc.id})`)\n } catch (error) {\n errorCount += 1\n console.error(\n `[migrateFolder] Failed to migrate document \"${doc.id}\" for file \"${filename}\":`,\n error,\n )\n }\n }\n }\n\n results.push({\n collectionSlug: slug,\n jobId: `migrate:${slug}`,\n status: dryRun ? 'Pending' : 'Completed',\n })\n\n console.log(\n `[migrateFolder] Collection \"${slug}\" complete: updated=${updatedCount}, skipped=${skippedCount}, errors=${errorCount}.`,\n )\n }\n\n console.log('[migrateFolder] Document-by-document migration complete.')\n } else {\n console.log('[migrateFolder] No Payload instance provided - skipping migration.')\n }\n\n console.log(`[migrateFolder] ${dryRun ? 'DRY-RUN simulation' : 'Migration'} complete.`)\n\n return results\n}\n"],"names":["ImageKit","path","getAdminThumbnailURL","getPrivateAdminThumbnailURL","DEFAULT_SIGNED_URL_EXPIRES_IN","isPrivateCollectionConfig","value","private","normalizeFolder","folder","normalized","trim","replace","Error","getCollectionFolder","rootFolder","collectionSlug","posix","join","getFilePath","filename","ensureFolderExists","client","folderPath","segments","split","filter","Boolean","parentFolderPath","folderName","folders","create","error","message","String","test","findImageKitFileId","imagekit","filePath","options","parentPath","dirname","name","basename","retries","retryDelayMs","limit","attempt","skip","assets","list","type","found","find","asset","fileId","length","Promise","resolve","setTimeout","undefined","isImageKitFileNotFoundError","uploadFileFromURL","fileName","isPrivate","sourceUrl","uploaded","files","upload","file","useUniqueFileName","isPrivateFile","url","thumbnailUrl","getUploadedImagekitFields","get","thumbnail","readFileIdFromCopyResponse","response","result","json","file_id","migrateFolder","collections","dryRun","from","payload","privateKey","signedUrlExpiresIn","signedUrlExpiresInOption","to","urlEndpoint","Object","keys","privateSlugs","Set","entries","map","slug","apiRoute","config","routes","api","fromFolder","toFolder","console","log","collectionSlugs","results","destinationFolderPath","isCollectionPrivate","has","collection","depth","pagination","docs","updatedCount","skippedCount","errorCount","i","doc","existingImagekit","id","sourceFilePath","destinationFolder","destinationFilePath","copyResponse","copy","destinationPath","asResponse","copiedFileId","warn","helper","buildSrc","expiresIn","signed","src","canonicalUrl","mimeType","adminThumbnailURL","update","data","thumbnailURL","push","jobId","status"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humaan/payload-storage-imagekit",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "description": "ImageKit storage adapter plugin for Payload CMS uploads",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,44 +31,44 @@
31
31
  "dist"
32
32
  ],
33
33
  "devDependencies": {
34
- "@eslint/eslintrc": "^3.2.0",
35
- "@payloadcms/db-mongodb": "3.82.1",
36
- "@payloadcms/db-postgres": "3.82.1",
37
- "@payloadcms/db-sqlite": "3.82.1",
34
+ "@eslint/eslintrc": "^3.3.5",
35
+ "@payloadcms/db-mongodb": "3.85.1",
36
+ "@payloadcms/db-postgres": "3.85.1",
37
+ "@payloadcms/db-sqlite": "3.85.1",
38
38
  "@payloadcms/eslint-config": "3.28.0",
39
- "@payloadcms/next": "3.82.1",
40
- "@payloadcms/plugin-cloud-storage": "3.82.1",
41
- "@payloadcms/richtext-lexical": "3.82.1",
42
- "@payloadcms/ui": "3.82.1",
39
+ "@payloadcms/next": "3.85.1",
40
+ "@payloadcms/plugin-cloud-storage": "3.85.1",
41
+ "@payloadcms/richtext-lexical": "3.85.1",
42
+ "@payloadcms/ui": "3.85.1",
43
43
  "@playwright/test": "1.59.1",
44
44
  "@swc-node/register": "1.10.9",
45
45
  "@swc/cli": "0.6.0",
46
46
  "@types/node": "24.12.3",
47
- "@types/react": "19.2.14",
47
+ "@types/react": "19.2.17",
48
48
  "@types/react-dom": "19.2.3",
49
49
  "copyfiles": "2.4.1",
50
50
  "cross-env": "10.1.0",
51
- "eslint": "^9.23.0",
52
- "eslint-config-next": "16.2.6",
53
- "graphql": "^16.8.1",
51
+ "eslint": "^9.39.4",
52
+ "eslint-config-next": "16.2.9",
53
+ "graphql": "^16.14.2",
54
54
  "mongodb-memory-server": "10.1.4",
55
- "next": "16.2.6",
56
- "open": "^10.1.0",
57
- "payload": "3.82.1",
58
- "prettier": "^3.4.2",
55
+ "next": "16.2.9",
56
+ "open": "^10.2.0",
57
+ "payload": "3.85.1",
58
+ "prettier": "^3.9.3",
59
59
  "qs-esm": "8.0.1",
60
- "react": "19.2.6",
61
- "react-dom": "19.2.6",
60
+ "react": "19.2.7",
61
+ "react-dom": "19.2.7",
62
62
  "rimraf": "3.0.2",
63
63
  "sharp": "0.34.2",
64
- "sort-package-json": "^2.10.0",
64
+ "sort-package-json": "^2.15.1",
65
65
  "typescript": "6.0.3",
66
- "vite": "7.3.2",
66
+ "vite": "7.3.6",
67
67
  "vite-tsconfig-paths": "6.0.5",
68
- "vitest": "4.1.6"
68
+ "vitest": "4.1.9"
69
69
  },
70
70
  "peerDependencies": {
71
- "@payloadcms/plugin-cloud-storage": ">=3.82.1 <4.0.0",
71
+ "@payloadcms/plugin-cloud-storage": ">=3.85.1 <4.0.0",
72
72
  "payload": ">=3.80.0 <4.0.0"
73
73
  },
74
74
  "engines": {
@@ -80,7 +80,7 @@
80
80
  },
81
81
  "registry": "https://registry.npmjs.org/",
82
82
  "dependencies": {
83
- "@imagekit/nodejs": "7.6.2"
83
+ "@imagekit/nodejs": "^7.8.1"
84
84
  },
85
85
  "scripts": {
86
86
  "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",