@payloadcms/plugin-cloud-storage 1.0.14 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/.editorconfig +10 -0
  2. package/.eslintrc.js +14 -14
  3. package/.gitignore +248 -0
  4. package/.idea/.gitignore +5 -0
  5. package/.idea/httpRequests/2023-04-07T152957.206.png +0 -0
  6. package/.idea/httpRequests/2023-04-07T153025.403.html +10 -0
  7. package/.idea/httpRequests/2023-04-07T153146.200.png +0 -0
  8. package/.idea/httpRequests/http-client.cookies +1 -0
  9. package/.idea/httpRequests/http-requests-log.http +74 -0
  10. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  11. package/.idea/jsLinters/eslint.xml +6 -0
  12. package/.idea/modules.xml +8 -0
  13. package/.idea/plugin-cloud-storage.iml +12 -0
  14. package/.idea/vcs.xml +6 -0
  15. package/.idea/workspace.xml +269 -0
  16. package/.prettierignore +1 -0
  17. package/.prettierrc.js +8 -0
  18. package/.vscode/launch.json +28 -0
  19. package/.vscode/settings.json +9 -0
  20. package/LICENSE.md +22 -22
  21. package/README.md +178 -174
  22. package/azure.d.ts +1 -1
  23. package/azure.js +1 -1
  24. package/dev/.env +25 -0
  25. package/dev/.env.example +21 -0
  26. package/dev/build/127.d2c2ffcfff69fabfdd1b.js +1 -0
  27. package/dev/build/16.17dbe03b1d0a96f3e564.js +2 -0
  28. package/dev/build/16.17dbe03b1d0a96f3e564.js.LICENSE.txt +8 -0
  29. package/dev/build/171.bbcbae3ea90468ad0cad.js +2 -0
  30. package/dev/build/171.bbcbae3ea90468ad0cad.js.LICENSE.txt +8 -0
  31. package/dev/build/18.e50c27edff6716f930d9.js +1 -0
  32. package/dev/build/205.33c7a29683ba98de93e0.js +1 -0
  33. package/dev/build/2211c49456cd07331ea9.woff +0 -0
  34. package/dev/build/234.79395f82c18207c13766.js +1 -0
  35. package/dev/build/266.9d4a240b3e0985bd7dd5.js +1 -0
  36. package/dev/build/296.4c5d646257b42c915834.js +1 -0
  37. package/dev/build/304.40dbe690de322c8f7c0d.js +2 -0
  38. package/dev/build/304.40dbe690de322c8f7c0d.js.LICENSE.txt +37 -0
  39. package/dev/build/349.446c12bffd3905085fdb.js +1 -0
  40. package/dev/build/354.5acd04b85b96a9839125.js +1 -0
  41. package/dev/build/40ad7515b8674bb854a1.woff2 +0 -0
  42. package/dev/build/422.086542466cdc9f6a2437.js +2 -0
  43. package/dev/build/422.086542466cdc9f6a2437.js.LICENSE.txt +6 -0
  44. package/dev/build/491.0bfe1bb0ecfe383179aa.js +1 -0
  45. package/dev/build/4d8845b830f4e8e2affb.png +0 -0
  46. package/dev/build/51922ceb71da289688d3.woff2 +0 -0
  47. package/dev/build/522443364fda49e9e0ed.woff2 +0 -0
  48. package/dev/build/531.1c6f53f3b44a3c45b444.js +2 -0
  49. package/dev/build/531.1c6f53f3b44a3c45b444.js.LICENSE.txt +6 -0
  50. package/dev/build/570.f2d9b99706765fbf0225.js +1 -0
  51. package/dev/build/599.570a04990d5806004f61.js +1 -0
  52. package/dev/build/5b718d9772de251a8c0a.woff2 +0 -0
  53. package/dev/build/778.41ae26bcd617861ad586.js +1 -0
  54. package/dev/build/783.0117995f2ff6036d6746.js +1 -0
  55. package/dev/build/787999a6af6a17efbc7c.woff +0 -0
  56. package/dev/build/78b8935fb481e11c92ce.woff +0 -0
  57. package/dev/build/860.7688681d3269f3f16e9a.js +1 -0
  58. package/dev/build/892.1a4ca5ac67d81038ceec.js +1 -0
  59. package/dev/build/896.d8cb1160388dc29d6364.js +1 -0
  60. package/dev/build/8b4ddd0d08500553efde.woff +0 -0
  61. package/dev/build/8f612153248094525d9d.woff +0 -0
  62. package/dev/build/995.cc11e738ff81a85821b4.js +1 -0
  63. package/dev/build/9c7dfd0036f7bd24b053.woff2 +0 -0
  64. package/dev/build/a1cfdc5b5250b7c4b481.woff2 +0 -0
  65. package/dev/build/d7aeda9e48ce098e7b48.woff +0 -0
  66. package/dev/build/e009f21405b4d7e89367.woff2 +0 -0
  67. package/dev/build/e7caa9e17af6ac87d182.woff +0 -0
  68. package/dev/build/ebcc1430049fddb274f8.svg +15 -0
  69. package/dev/build/efe8f6a3b46446cc9135.woff +0 -0
  70. package/dev/build/f53bb8d4b29adc903703.woff2 +0 -0
  71. package/dev/build/index.html +1 -0
  72. package/dev/build/main.a2003d502fbb9aaa3e8d.js +2 -0
  73. package/dev/build/main.a2003d502fbb9aaa3e8d.js.LICENSE.txt +57 -0
  74. package/dev/build/styles.css +1 -0
  75. package/dev/build/styles.fa29d16b0baf5b98a1cf.js +1 -0
  76. package/dev/nodemon.json +8 -0
  77. package/dev/package.json +32 -0
  78. package/dev/src/collections/Media.ts +56 -0
  79. package/dev/src/collections/Users.ts +23 -0
  80. package/dev/src/mocks/fsMock.js +1 -0
  81. package/dev/src/mocks/promisifyMock.js +1 -0
  82. package/dev/src/payload.config.ts +111 -0
  83. package/dev/src/server.ts +26 -0
  84. package/dev/tsconfig.json +20 -0
  85. package/dist/adapters/azure/fileStub.d.ts +2 -0
  86. package/dist/adapters/azure/fileStub.js +4 -0
  87. package/dist/adapters/azure/fileStub.js.map +1 -0
  88. package/dist/adapters/azure/generateURL.d.ts +7 -7
  89. package/dist/adapters/azure/generateURL.js +15 -15
  90. package/dist/adapters/azure/handleDelete.d.ts +9 -9
  91. package/dist/adapters/azure/handleDelete.js +63 -63
  92. package/dist/adapters/azure/handleUpload.d.ts +10 -10
  93. package/dist/adapters/azure/handleUpload.js +80 -65
  94. package/dist/adapters/azure/handleUpload.js.map +1 -1
  95. package/dist/adapters/azure/index.d.ts +8 -8
  96. package/dist/adapters/azure/index.js +42 -42
  97. package/dist/adapters/azure/mock.d.ts +13 -7
  98. package/dist/adapters/azure/mock.js +12 -8
  99. package/dist/adapters/azure/mock.js.map +1 -1
  100. package/dist/adapters/azure/staticHandler.d.ts +9 -9
  101. package/dist/adapters/azure/staticHandler.js +81 -77
  102. package/dist/adapters/azure/staticHandler.js.map +1 -1
  103. package/dist/adapters/azure/webpack.d.ts +2 -2
  104. package/dist/adapters/azure/webpack.js +24 -24
  105. package/dist/adapters/azure/webpack.js.map +1 -1
  106. package/dist/adapters/gcs/generateURL.d.ts +8 -8
  107. package/dist/adapters/gcs/generateURL.js +15 -15
  108. package/dist/adapters/gcs/handleDelete.d.ts +8 -8
  109. package/dist/adapters/gcs/handleDelete.js +62 -62
  110. package/dist/adapters/gcs/handleUpload.d.ts +12 -12
  111. package/dist/adapters/gcs/handleUpload.js +72 -72
  112. package/dist/adapters/gcs/index.d.ts +8 -8
  113. package/dist/adapters/gcs/index.js +35 -35
  114. package/dist/adapters/gcs/mock.d.ts +1 -1
  115. package/dist/adapters/gcs/mock.js +4 -4
  116. package/dist/adapters/gcs/staticHandler.d.ts +10 -10
  117. package/dist/adapters/gcs/staticHandler.js +76 -76
  118. package/dist/adapters/gcs/webpack.d.ts +2 -2
  119. package/dist/adapters/gcs/webpack.js +24 -24
  120. package/dist/adapters/s3/fileStub.d.ts +2 -2
  121. package/dist/adapters/s3/fileStub.js +3 -3
  122. package/dist/adapters/s3/generateURL.d.ts +8 -8
  123. package/dist/adapters/s3/generateURL.js +15 -15
  124. package/dist/adapters/s3/handleDelete.d.ts +8 -8
  125. package/dist/adapters/s3/handleDelete.js +63 -63
  126. package/dist/adapters/s3/handleUpload.d.ts +12 -12
  127. package/dist/adapters/s3/handleUpload.js +93 -93
  128. package/dist/adapters/s3/index.d.ts +8 -8
  129. package/dist/adapters/s3/index.js +59 -59
  130. package/dist/adapters/s3/mock.d.ts +8 -8
  131. package/dist/adapters/s3/mock.js +9 -9
  132. package/dist/adapters/s3/staticHandler.d.ts +10 -10
  133. package/dist/adapters/s3/staticHandler.js +80 -80
  134. package/dist/adapters/s3/webpack.d.ts +2 -2
  135. package/dist/adapters/s3/webpack.js +24 -24
  136. package/dist/fields/getFields.d.ts +11 -11
  137. package/dist/fields/getFields.js +118 -118
  138. package/dist/hooks/afterDelete.d.ts +10 -10
  139. package/dist/hooks/afterDelete.js +88 -88
  140. package/dist/hooks/afterRead.d.ts +12 -12
  141. package/dist/hooks/afterRead.js +79 -79
  142. package/dist/hooks/beforeChange.d.ts +10 -10
  143. package/dist/hooks/beforeChange.js +77 -77
  144. package/dist/index.d.ts +1 -1
  145. package/dist/index.js +5 -5
  146. package/dist/plugin.d.ts +3 -3
  147. package/dist/plugin.js +124 -124
  148. package/dist/types.d.ts +62 -62
  149. package/dist/types.js +2 -2
  150. package/dist/utilities/getFilePrefix.d.ts +5 -5
  151. package/dist/utilities/getFilePrefix.js +80 -80
  152. package/dist/utilities/getIncomingFiles.d.ts +7 -7
  153. package/dist/utilities/getIncomingFiles.js +37 -37
  154. package/dist/utilities/getRangeFromHeader.d.ts +6 -0
  155. package/dist/utilities/getRangeFromHeader.js +67 -0
  156. package/dist/utilities/getRangeFromHeader.js.map +1 -0
  157. package/dist/webpack.d.ts +9 -9
  158. package/dist/webpack.js +39 -39
  159. package/docs/local-dev.md +47 -0
  160. package/eslint-config/index.js +15 -0
  161. package/eslint-config/rules/import.js +38 -0
  162. package/eslint-config/rules/prettier.js +7 -0
  163. package/eslint-config/rules/style.js +21 -0
  164. package/eslint-config/rules/typescript.js +628 -0
  165. package/gcs.d.ts +1 -1
  166. package/gcs.js +1 -1
  167. package/package.json +67 -64
  168. package/s3.d.ts +1 -1
  169. package/s3.js +1 -1
  170. package/src/adapters/azure/emulator/docker-compose.yml +16 -0
  171. package/src/adapters/azure/fileStub.js +1 -0
  172. package/src/adapters/azure/generateURL.ts +13 -0
  173. package/src/adapters/azure/handleDelete.ts +16 -0
  174. package/src/adapters/azure/handleUpload.ts +41 -0
  175. package/src/adapters/azure/index.ts +47 -0
  176. package/src/adapters/azure/mock.js +13 -0
  177. package/src/adapters/azure/staticHandler.ts +38 -0
  178. package/src/adapters/azure/webpack.ts +20 -0
  179. package/src/adapters/gcs/emulator/docker-compose.yml +15 -0
  180. package/src/adapters/gcs/generateURL.ts +16 -0
  181. package/src/adapters/gcs/handleDelete.ts +16 -0
  182. package/src/adapters/gcs/handleUpload.ts +34 -0
  183. package/src/adapters/gcs/index.ts +37 -0
  184. package/src/adapters/gcs/mock.js +3 -0
  185. package/src/adapters/gcs/staticHandler.ts +34 -0
  186. package/src/adapters/gcs/webpack.ts +17 -0
  187. package/src/adapters/s3/emulator/docker-compose.yml +15 -0
  188. package/src/adapters/s3/fileStub.js +1 -0
  189. package/src/adapters/s3/generateURL.ts +14 -0
  190. package/src/adapters/s3/handleDelete.ts +17 -0
  191. package/src/adapters/s3/handleUpload.ts +62 -0
  192. package/src/adapters/s3/index.ts +38 -0
  193. package/src/adapters/s3/mock.js +9 -0
  194. package/src/adapters/s3/staticHandler.ts +40 -0
  195. package/src/adapters/s3/webpack.ts +19 -0
  196. package/src/fields/getFields.ts +155 -0
  197. package/src/hooks/afterDelete.ts +35 -0
  198. package/src/hooks/afterRead.ts +38 -0
  199. package/src/hooks/beforeChange.ts +30 -0
  200. package/src/index.ts +1 -0
  201. package/src/plugin.ts +94 -0
  202. package/src/types.ts +73 -0
  203. package/src/utilities/getFilePrefix.ts +26 -0
  204. package/src/utilities/getIncomingFiles.ts +44 -0
  205. package/src/utilities/getRangeFromHeader.ts +27 -0
  206. package/src/webpack.ts +46 -0
  207. package/tsconfig.json +23 -0
  208. package/yarn.lock +8155 -0
package/package.json CHANGED
@@ -1,64 +1,67 @@
1
- {
2
- "name": "@payloadcms/plugin-cloud-storage",
3
- "description": "The official cloud storage plugin for Payload CMS",
4
- "version": "1.0.14",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "license": "SEE LICENSE IN LICENSE.MD",
8
- "scripts": {
9
- "build": "tsc",
10
- "lint": "eslint src",
11
- "lint:fix": "eslint --fix --ext .ts,.tsx src",
12
- "clean": "rimraf dist && rimraf dev/yarn.lock",
13
- "prepublishOnly": "yarn clean && yarn build"
14
- },
15
- "peerDependencies": {
16
- "@aws-sdk/client-s3": "^3.142.0",
17
- "@aws-sdk/lib-storage": "^3.267.0",
18
- "@azure/storage-blob": "^12.11.0",
19
- "@google-cloud/storage": "^6.4.1",
20
- "payload": "^1.0.27"
21
- },
22
- "peerDependenciesMeta": {
23
- "@aws-sdk/client-s3": {
24
- "optional": true
25
- },
26
- "@aws-sdk/lib-storage": {
27
- "optional": true
28
- },
29
- "@azure/storage-blob": {
30
- "optional": true
31
- },
32
- "@google-cloud/storage": {
33
- "optional": true
34
- }
35
- },
36
- "files": [
37
- "dist",
38
- "*.js",
39
- "*.d.ts",
40
- "!.prettierrc.js"
41
- ],
42
- "devDependencies": {
43
- "@aws-sdk/client-s3": "^3.142.0",
44
- "@aws-sdk/lib-storage": "^3.267.0",
45
- "@azure/storage-blob": "^12.11.0",
46
- "@google-cloud/storage": "^6.4.1",
47
- "@types/express": "^4.17.9",
48
- "@typescript-eslint/eslint-plugin": "5.12.1",
49
- "@typescript-eslint/parser": "5.12.1",
50
- "cross-env": "^7.0.3",
51
- "dotenv": "^8.2.0",
52
- "eslint": "^8.19.0",
53
- "eslint-config-airbnb-base": "^14.2.1",
54
- "eslint-config-prettier": "^8.5.0",
55
- "eslint-plugin-import": "2.25.4",
56
- "eslint-plugin-prettier": "^4.0.0",
57
- "nodemon": "^2.0.6",
58
- "payload": "^1.6.6",
59
- "prettier": "^2.7.1",
60
- "rimraf": "^4.1.2",
61
- "ts-node": "^9.1.1",
62
- "typescript": "^4.1.3"
63
- }
64
- }
1
+ {
2
+ "name": "@payloadcms/plugin-cloud-storage",
3
+ "description": "The official cloud storage plugin for Payload CMS",
4
+ "version": "1.0.15",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "lint": "eslint src",
11
+ "lint:fix": "eslint --fix --ext .ts,.tsx src",
12
+ "clean": "rimraf dist && rimraf dev/yarn.lock",
13
+ "prepublishOnly": "yarn clean && yarn build"
14
+ },
15
+ "peerDependencies": {
16
+ "@aws-sdk/client-s3": "^3.142.0",
17
+ "@aws-sdk/lib-storage": "^3.267.0",
18
+ "@azure/storage-blob": "^12.11.0",
19
+ "@google-cloud/storage": "^6.4.1",
20
+ "payload": "^1.0.27"
21
+ },
22
+ "peerDependenciesMeta": {
23
+ "@aws-sdk/client-s3": {
24
+ "optional": true
25
+ },
26
+ "@aws-sdk/lib-storage": {
27
+ "optional": true
28
+ },
29
+ "@azure/storage-blob": {
30
+ "optional": true
31
+ },
32
+ "@google-cloud/storage": {
33
+ "optional": true
34
+ }
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "*.js",
39
+ "*.d.ts",
40
+ "!.prettierrc.js"
41
+ ],
42
+ "devDependencies": {
43
+ "@aws-sdk/client-s3": "^3.142.0",
44
+ "@aws-sdk/lib-storage": "^3.267.0",
45
+ "@azure/storage-blob": "^12.11.0",
46
+ "@google-cloud/storage": "^6.4.1",
47
+ "@types/express": "^4.17.9",
48
+ "@typescript-eslint/eslint-plugin": "5.12.1",
49
+ "@typescript-eslint/parser": "5.12.1",
50
+ "cross-env": "^7.0.3",
51
+ "dotenv": "^8.2.0",
52
+ "eslint": "^8.19.0",
53
+ "eslint-config-airbnb-base": "^14.2.1",
54
+ "eslint-config-prettier": "^8.5.0",
55
+ "eslint-plugin-import": "2.25.4",
56
+ "eslint-plugin-prettier": "^4.0.0",
57
+ "nodemon": "^2.0.6",
58
+ "payload": "^1.6.6",
59
+ "prettier": "^2.7.1",
60
+ "rimraf": "^4.1.2",
61
+ "ts-node": "^9.1.1",
62
+ "typescript": "^4.1.3"
63
+ },
64
+ "dependencies": {
65
+ "range-parser": "^1.2.1"
66
+ }
67
+ }
package/s3.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './dist/adapters/s3'
1
+ export * from './dist/adapters/s3'
package/s3.js CHANGED
@@ -1 +1 @@
1
- exports.s3Adapter = require('./dist/adapters/s3').s3Adapter
1
+ exports.s3Adapter = require('./dist/adapters/s3').s3Adapter
@@ -0,0 +1,16 @@
1
+ version: "3"
2
+
3
+ services:
4
+ azure-storage:
5
+ image: mcr.microsoft.com/azure-storage/azurite:3.18.0
6
+ restart: always
7
+ command: "azurite --loose --blobHost 0.0.0.0 --tableHost 0.0.0.0 --queueHost 0.0.0.0"
8
+ ports:
9
+ - "10000:10000"
10
+ - "10001:10001"
11
+ - "10002:10002"
12
+ volumes:
13
+ - ./azurestoragedata:/data"
14
+
15
+ volumes:
16
+ azurestoragedata:
@@ -0,0 +1 @@
1
+ export default 'file-stub'
@@ -0,0 +1,13 @@
1
+ import path from 'path'
2
+ import type { GenerateURL } from '../../types'
3
+
4
+ interface Args {
5
+ containerName: string
6
+ baseURL: string
7
+ }
8
+
9
+ export const getGenerateURL =
10
+ ({ containerName, baseURL }: Args): GenerateURL =>
11
+ ({ filename, prefix = '' }) => {
12
+ return `${baseURL}/${containerName}/${path.posix.join(prefix, filename)}`
13
+ }
@@ -0,0 +1,16 @@
1
+ import path from 'path'
2
+ import type { CollectionConfig } from 'payload/types'
3
+ import type { ContainerClient } from '@azure/storage-blob'
4
+ import type { HandleDelete } from '../../types'
5
+
6
+ interface Args {
7
+ collection: CollectionConfig
8
+ getStorageClient: () => ContainerClient
9
+ }
10
+
11
+ export const getHandleDelete = ({ getStorageClient }: Args): HandleDelete => {
12
+ return async ({ filename, doc: { prefix = '' } }) => {
13
+ const blockBlobClient = getStorageClient().getBlockBlobClient(path.posix.join(prefix, filename))
14
+ await blockBlobClient.deleteIfExists()
15
+ }
16
+ }
@@ -0,0 +1,41 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import { Readable } from 'stream'
4
+ import type { ContainerClient } from '@azure/storage-blob'
5
+ import { AbortController } from '@azure/abort-controller'
6
+ import type { CollectionConfig } from 'payload/types'
7
+ import type { HandleUpload } from '../../types'
8
+
9
+ interface Args {
10
+ collection: CollectionConfig
11
+ getStorageClient: () => ContainerClient
12
+ prefix?: string
13
+ }
14
+
15
+ const multipartThreshold = 1024 * 1024 * 50 // 50MB
16
+ export const getHandleUpload = ({ getStorageClient, prefix = '' }: Args): HandleUpload => {
17
+ return async ({ data, file }) => {
18
+ const blockBlobClient = getStorageClient().getBlockBlobClient(
19
+ path.posix.join(prefix, file.filename),
20
+ )
21
+
22
+ // when there are no temp files, or the upload is less than the threshold size, do not stream files
23
+ if (!file.tempFilePath && file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
24
+ await blockBlobClient.upload(file.buffer, file.buffer.byteLength, {
25
+ blobHTTPHeaders: { blobContentType: file.mimeType },
26
+ })
27
+
28
+ return data
29
+ }
30
+
31
+ const fileBufferOrStream: Readable = file.tempFilePath
32
+ ? fs.createReadStream(file.tempFilePath)
33
+ : Readable.from(file.buffer)
34
+
35
+ await blockBlobClient.uploadStream(fileBufferOrStream, 4 * 1024 * 1024, 4, {
36
+ abortSignal: AbortController.timeout(30 * 60 * 1000),
37
+ })
38
+
39
+ return data
40
+ }
41
+ }
@@ -0,0 +1,47 @@
1
+ import { BlobServiceClient, ContainerClient } from '@azure/storage-blob'
2
+ import type { Adapter, GeneratedAdapter } from '../../types'
3
+ import { getHandler } from './staticHandler'
4
+ import { getGenerateURL } from './generateURL'
5
+ import { getHandleDelete } from './handleDelete'
6
+ import { getHandleUpload } from './handleUpload'
7
+ import { extendWebpackConfig } from './webpack'
8
+
9
+ export interface Args {
10
+ connectionString: string
11
+ containerName: string
12
+ baseURL: string
13
+ allowContainerCreate: boolean
14
+ }
15
+
16
+ export const azureBlobStorageAdapter = ({
17
+ connectionString,
18
+ allowContainerCreate,
19
+ containerName,
20
+ baseURL,
21
+ }: Args): Adapter => {
22
+ let storageClient: ContainerClient | null = null
23
+ const getStorageClient = () => {
24
+ if (storageClient) return storageClient
25
+ const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
26
+ return (storageClient = blobServiceClient.getContainerClient(containerName))
27
+ }
28
+
29
+ const createContainerIfNotExists = () => {
30
+ getStorageClient().createIfNotExists({ access: 'blob' })
31
+ }
32
+
33
+ return ({ collection, prefix }): GeneratedAdapter => {
34
+ return {
35
+ handleUpload: getHandleUpload({
36
+ collection,
37
+ getStorageClient,
38
+ prefix,
39
+ }),
40
+ handleDelete: getHandleDelete({ collection, getStorageClient }),
41
+ generateURL: getGenerateURL({ containerName, baseURL }),
42
+ staticHandler: getHandler({ getStorageClient, collection }),
43
+ webpack: extendWebpackConfig,
44
+ ...(allowContainerCreate && { onInit: createContainerIfNotExists }),
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ exports.BlobServiceClient = {
2
+ fromConnectionString: () => ({
3
+ getContainerClient: () => ({
4
+ createIfNotExists: () => null,
5
+ }),
6
+ }),
7
+ }
8
+
9
+ exports.AbortController = {
10
+ timeout: () => null,
11
+ }
12
+
13
+ exports.Readable = { from: () => null }
@@ -0,0 +1,38 @@
1
+ import type { ContainerClient } from '@azure/storage-blob'
2
+ import path from 'path'
3
+ import type { CollectionConfig } from 'payload/types'
4
+ import type { StaticHandler } from '../../types'
5
+ import { getFilePrefix } from '../../utilities/getFilePrefix'
6
+ import getRangeFromHeader from '../../utilities/getRangeFromHeader'
7
+
8
+ interface Args {
9
+ getStorageClient: () => ContainerClient
10
+ collection: CollectionConfig
11
+ }
12
+
13
+ export const getHandler = ({ getStorageClient, collection }: Args): StaticHandler => {
14
+ return async (req, res, next) => {
15
+ try {
16
+ const prefix = await getFilePrefix({ req, collection })
17
+ const blockBlobClient = getStorageClient().getBlockBlobClient(
18
+ path.posix.join(prefix, req.params.filename),
19
+ )
20
+
21
+ const { start, end } = await getRangeFromHeader(blockBlobClient, req.headers.range)
22
+
23
+ const blob = await blockBlobClient.download(start, end)
24
+ // eslint-disable-next-line no-underscore-dangle
25
+ const response = blob._response
26
+ res.header(response.headers.rawHeaders())
27
+ res.status(response.status)
28
+
29
+ if (blob?.readableStreamBody) {
30
+ return blob.readableStreamBody.pipe(res)
31
+ }
32
+
33
+ return next()
34
+ } catch (err: unknown) {
35
+ return next()
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,20 @@
1
+ import type { Configuration as WebpackConfig } from 'webpack'
2
+ import path from 'path'
3
+
4
+ export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): WebpackConfig => {
5
+ const newConfig: WebpackConfig = {
6
+ ...existingWebpackConfig,
7
+ resolve: {
8
+ ...(existingWebpackConfig.resolve || {}),
9
+ alias: {
10
+ ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
11
+ stream: path.resolve(__dirname, './mock.js'),
12
+ '@azure/storage-blob': path.resolve(__dirname, './mock.js'),
13
+ '@azure/abort-controller': path.resolve(__dirname, './mock.js'),
14
+ fs: path.resolve(__dirname, './fileStub.js'),
15
+ },
16
+ },
17
+ }
18
+
19
+ return newConfig
20
+ }
@@ -0,0 +1,15 @@
1
+ version: "3"
2
+
3
+ services:
4
+ google-cloud-storage:
5
+ image: fsouza/fake-gcs-server
6
+ restart: always
7
+ command: ["-scheme", "http", "-port", "4443", "-public-host", "http://localhost:4443", "-external-url", "http://localhost:4443", "-backend", "memory"]
8
+ ports:
9
+ - "4443:4443"
10
+ volumes:
11
+ - ./google-cloud-storage/payload-bucket:/data/payload-bucket
12
+
13
+ volumes:
14
+ google-cloud-storage:
15
+
@@ -0,0 +1,16 @@
1
+ import path from 'path'
2
+ import { Storage } from '@google-cloud/storage'
3
+ import type { GenerateURL } from '../../types'
4
+
5
+ interface Args {
6
+ getStorageClient: () => Storage
7
+ bucket: string
8
+ }
9
+
10
+ export const getGenerateURL =
11
+ ({ getStorageClient, bucket }: Args): GenerateURL =>
12
+ ({ filename, prefix = '' }) => {
13
+ return decodeURIComponent(
14
+ getStorageClient().bucket(bucket).file(path.posix.join(prefix, filename)).publicUrl(),
15
+ )
16
+ }
@@ -0,0 +1,16 @@
1
+ import path from 'path'
2
+ import { Storage } from '@google-cloud/storage'
3
+ import type { HandleDelete } from '../../types'
4
+
5
+ interface Args {
6
+ getStorageClient: () => Storage
7
+ bucket: string
8
+ }
9
+
10
+ export const getHandleDelete = ({ getStorageClient, bucket }: Args): HandleDelete => {
11
+ return async ({ filename, doc: { prefix = '' } }) => {
12
+ await getStorageClient().bucket(bucket).file(path.posix.join(prefix, filename)).delete({
13
+ ignoreNotFound: true,
14
+ })
15
+ }
16
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'path'
2
+ import type { Storage } from '@google-cloud/storage'
3
+ import type { CollectionConfig } from 'payload/types'
4
+ import type { HandleUpload } from '../../types'
5
+
6
+ interface Args {
7
+ collection: CollectionConfig
8
+ bucket: string
9
+ acl?: 'Private' | 'Public'
10
+ prefix?: string
11
+ getStorageClient: () => Storage
12
+ }
13
+
14
+ export const getHandleUpload = ({
15
+ getStorageClient,
16
+ bucket,
17
+ acl,
18
+ prefix = '',
19
+ }: Args): HandleUpload => {
20
+ return async ({ data, file }) => {
21
+ const gcsFile = getStorageClient().bucket(bucket).file(path.posix.join(prefix, file.filename))
22
+ await gcsFile.save(file.buffer, {
23
+ metadata: {
24
+ contentType: file.mimeType,
25
+ },
26
+ })
27
+
28
+ if (acl) {
29
+ await gcsFile[`make${acl}`]()
30
+ }
31
+
32
+ return data
33
+ }
34
+ }
@@ -0,0 +1,37 @@
1
+ import { Storage, StorageOptions } from '@google-cloud/storage'
2
+ import type { Adapter, GeneratedAdapter } from '../../types'
3
+ import { getGenerateURL } from './generateURL'
4
+ import { getHandler } from './staticHandler'
5
+ import { getHandleDelete } from './handleDelete'
6
+ import { getHandleUpload } from './handleUpload'
7
+ import { extendWebpackConfig } from './webpack'
8
+
9
+ export interface Args {
10
+ options: StorageOptions
11
+ bucket: string
12
+ acl?: 'Private' | 'Public'
13
+ }
14
+
15
+ export const gcsAdapter =
16
+ ({ options, bucket, acl }: Args): Adapter =>
17
+ ({ collection, prefix }): GeneratedAdapter => {
18
+ let storageClient: Storage | null = null
19
+ const getStorageClient = () => {
20
+ if (storageClient) return storageClient
21
+ return (storageClient = new Storage(options))
22
+ }
23
+
24
+ return {
25
+ handleUpload: getHandleUpload({
26
+ collection,
27
+ getStorageClient,
28
+ bucket,
29
+ acl,
30
+ prefix,
31
+ }),
32
+ handleDelete: getHandleDelete({ getStorageClient, bucket }),
33
+ generateURL: getGenerateURL({ getStorageClient, bucket }),
34
+ staticHandler: getHandler({ getStorageClient, bucket, collection }),
35
+ webpack: extendWebpackConfig,
36
+ }
37
+ }
@@ -0,0 +1,3 @@
1
+ exports.Storage = function () {
2
+ return null
3
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'path'
2
+ import type { Storage } from '@google-cloud/storage'
3
+ import type { CollectionConfig } from 'payload/types'
4
+ import type { StaticHandler } from '../../types'
5
+ import { getFilePrefix } from '../../utilities/getFilePrefix'
6
+
7
+ interface Args {
8
+ getStorageClient: () => Storage
9
+ bucket: string
10
+ collection: CollectionConfig
11
+ }
12
+
13
+ export const getHandler = ({ getStorageClient, bucket, collection }: Args): StaticHandler => {
14
+ return async (req, res, next) => {
15
+ try {
16
+ const prefix = await getFilePrefix({ req, collection })
17
+ const file = getStorageClient()
18
+ .bucket(bucket)
19
+ .file(path.posix.join(prefix, req.params.filename))
20
+
21
+ const [metadata] = await file.getMetadata()
22
+
23
+ res.set({
24
+ 'Content-Length': metadata.size,
25
+ 'Content-Type': metadata.contentType,
26
+ ETag: metadata.etag,
27
+ })
28
+
29
+ return file.createReadStream().pipe(res)
30
+ } catch (err: unknown) {
31
+ return next()
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,17 @@
1
+ import type { Configuration as WebpackConfig } from 'webpack'
2
+ import path from 'path'
3
+
4
+ export const extendWebpackConfig = (existingWebpackConfig: WebpackConfig): WebpackConfig => {
5
+ const newConfig: WebpackConfig = {
6
+ ...existingWebpackConfig,
7
+ resolve: {
8
+ ...(existingWebpackConfig.resolve || {}),
9
+ alias: {
10
+ ...(existingWebpackConfig.resolve?.alias ? existingWebpackConfig.resolve.alias : {}),
11
+ '@google-cloud/storage': path.resolve(__dirname, './mock.js'),
12
+ },
13
+ },
14
+ }
15
+
16
+ return newConfig
17
+ }
@@ -0,0 +1,15 @@
1
+ version: '3.2'
2
+ services:
3
+ localstack:
4
+ image: localstack/localstack:latest
5
+ container_name: localstack_demo
6
+ ports:
7
+ - '4563-4599:4563-4599'
8
+ - '8055:8080'
9
+ environment:
10
+ - SERVICES=s3
11
+ - DEBUG=1
12
+ - DATA_DIR=/tmp/localstack/data
13
+ volumes:
14
+ - './.localstack:/var/lib/localstack'
15
+ - '/var/run/docker.sock:/var/run/docker.sock'
@@ -0,0 +1 @@
1
+ export default 'file-stub'
@@ -0,0 +1,14 @@
1
+ import path from 'path'
2
+ import type * as AWS from '@aws-sdk/client-s3'
3
+ import type { GenerateURL } from '../../types'
4
+
5
+ interface Args {
6
+ config: AWS.S3ClientConfig
7
+ bucket: string
8
+ }
9
+
10
+ export const getGenerateURL =
11
+ ({ config: { endpoint }, bucket }: Args): GenerateURL =>
12
+ ({ filename, prefix = '' }) => {
13
+ return `${endpoint}/${bucket}/${path.posix.join(prefix, filename)}`
14
+ }
@@ -0,0 +1,17 @@
1
+ import path from 'path'
2
+ import type * as AWS from '@aws-sdk/client-s3'
3
+ import type { HandleDelete } from '../../types'
4
+
5
+ interface Args {
6
+ getStorageClient: () => AWS.S3
7
+ bucket: string
8
+ }
9
+
10
+ export const getHandleDelete = ({ getStorageClient, bucket }: Args): HandleDelete => {
11
+ return async ({ filename, doc: { prefix = '' } }) => {
12
+ await getStorageClient().deleteObject({
13
+ Bucket: bucket,
14
+ Key: path.posix.join(prefix, filename),
15
+ })
16
+ }
17
+ }
@@ -0,0 +1,62 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import type * as AWS from '@aws-sdk/client-s3'
4
+ import type { CollectionConfig } from 'payload/types'
5
+ import type stream from 'stream'
6
+
7
+ import { Upload } from '@aws-sdk/lib-storage'
8
+ import type { HandleUpload } from '../../types'
9
+
10
+ interface Args {
11
+ collection: CollectionConfig
12
+ bucket: string
13
+ acl?: 'private' | 'public-read'
14
+ prefix?: string
15
+ getStorageClient: () => AWS.S3
16
+ }
17
+
18
+ const multipartThreshold = 1024 * 1024 * 50 // 50MB
19
+
20
+ export const getHandleUpload = ({
21
+ getStorageClient,
22
+ bucket,
23
+ acl,
24
+ prefix = '',
25
+ }: Args): HandleUpload => {
26
+ return async ({ data, file }) => {
27
+ const fileKey = path.posix.join(prefix, file.filename)
28
+
29
+ const fileBufferOrStream: Buffer | stream.Readable = file.tempFilePath
30
+ ? fs.createReadStream(file.tempFilePath)
31
+ : file.buffer
32
+
33
+ if (file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
34
+ await getStorageClient().putObject({
35
+ Bucket: bucket,
36
+ Key: fileKey,
37
+ Body: fileBufferOrStream,
38
+ ACL: acl,
39
+ ContentType: file.mimeType,
40
+ })
41
+
42
+ return data
43
+ }
44
+
45
+ const parallelUploadS3 = new Upload({
46
+ client: getStorageClient(),
47
+ params: {
48
+ Bucket: bucket,
49
+ Key: fileKey,
50
+ Body: fileBufferOrStream,
51
+ ACL: acl,
52
+ ContentType: file.mimeType,
53
+ },
54
+ queueSize: 4,
55
+ partSize: multipartThreshold,
56
+ })
57
+
58
+ await parallelUploadS3.done()
59
+
60
+ return data
61
+ }
62
+ }