@webiny/api-file-manager-server 0.0.0-unstable.0d717d18dd

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 (125) hide show
  1. package/FileManagerServerFeature.d.ts +5 -0
  2. package/FileManagerServerFeature.js +41 -0
  3. package/FileManagerServerFeature.js.map +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +11 -0
  6. package/assetDelivery/LocalAssetResolver.d.ts +15 -0
  7. package/assetDelivery/LocalAssetResolver.js +40 -0
  8. package/assetDelivery/LocalAssetResolver.js.map +1 -0
  9. package/assetDelivery/LocalContentsReader.d.ts +7 -0
  10. package/assetDelivery/LocalContentsReader.js +18 -0
  11. package/assetDelivery/LocalContentsReader.js.map +1 -0
  12. package/assetDelivery/LocalOutputStrategy.d.ts +12 -0
  13. package/assetDelivery/LocalOutputStrategy.js +21 -0
  14. package/assetDelivery/LocalOutputStrategy.js.map +1 -0
  15. package/assetDelivery/LocalSharpTransform.d.ts +24 -0
  16. package/assetDelivery/LocalSharpTransform.js +161 -0
  17. package/assetDelivery/LocalSharpTransform.js.map +1 -0
  18. package/assetDelivery/abstractions.d.ts +8 -0
  19. package/assetDelivery/abstractions.js +6 -0
  20. package/assetDelivery/abstractions.js.map +1 -0
  21. package/assetDelivery/assetDeliveryConfig.d.ts +2 -0
  22. package/assetDelivery/assetDeliveryConfig.js +15 -0
  23. package/assetDelivery/assetDeliveryConfig.js.map +1 -0
  24. package/assetDelivery/createAssetDelivery.d.ts +3 -0
  25. package/assetDelivery/createAssetDelivery.js +7 -0
  26. package/assetDelivery/createAssetDelivery.js.map +1 -0
  27. package/assetDelivery/feature.d.ts +5 -0
  28. package/assetDelivery/feature.js +38 -0
  29. package/assetDelivery/feature.js.map +1 -0
  30. package/assetDelivery/types.d.ts +8 -0
  31. package/assetDelivery/types.js +0 -0
  32. package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.d.ts +21 -0
  33. package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.js +62 -0
  34. package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.js.map +1 -0
  35. package/features/CleanupStaleMultipartUploads/feature.d.ts +4 -0
  36. package/features/CleanupStaleMultipartUploads/feature.js +11 -0
  37. package/features/CleanupStaleMultipartUploads/feature.js.map +1 -0
  38. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.d.ts +13 -0
  39. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js +62 -0
  40. package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js.map +1 -0
  41. package/features/CompleteMultiPartUpload/feature.d.ts +4 -0
  42. package/features/CompleteMultiPartUpload/feature.js +11 -0
  43. package/features/CompleteMultiPartUpload/feature.js.map +1 -0
  44. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.d.ts +14 -0
  45. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js +58 -0
  46. package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js.map +1 -0
  47. package/features/CreateMultiPartUpload/feature.d.ts +4 -0
  48. package/features/CreateMultiPartUpload/feature.js +11 -0
  49. package/features/CreateMultiPartUpload/feature.js.map +1 -0
  50. package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.d.ts +15 -0
  51. package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.js +34 -0
  52. package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.js.map +1 -0
  53. package/features/DeleteFileFromDisk/feature.d.ts +4 -0
  54. package/features/DeleteFileFromDisk/feature.js +11 -0
  55. package/features/DeleteFileFromDisk/feature.js.map +1 -0
  56. package/features/ExtractMetadata/ExtractMetadataHandler.d.ts +1 -0
  57. package/features/ExtractMetadata/ExtractMetadataHandler.js +1 -0
  58. package/features/ExtractMetadata/ExtractMetadataTask.d.ts +24 -0
  59. package/features/ExtractMetadata/ExtractMetadataTask.js +84 -0
  60. package/features/ExtractMetadata/ExtractMetadataTask.js.map +1 -0
  61. package/features/ExtractMetadata/feature.d.ts +4 -0
  62. package/features/ExtractMetadata/feature.js +13 -0
  63. package/features/ExtractMetadata/feature.js.map +1 -0
  64. package/features/FileManagerServerConfig/FileManagerServerConfig.d.ts +12 -0
  65. package/features/FileManagerServerConfig/FileManagerServerConfig.js +28 -0
  66. package/features/FileManagerServerConfig/FileManagerServerConfig.js.map +1 -0
  67. package/features/FileManagerServerConfig/abstractions.d.ts +10 -0
  68. package/features/FileManagerServerConfig/abstractions.js +5 -0
  69. package/features/FileManagerServerConfig/abstractions.js.map +1 -0
  70. package/features/FileManagerServerConfig/feature.d.ts +4 -0
  71. package/features/FileManagerServerConfig/feature.js +11 -0
  72. package/features/FileManagerServerConfig/feature.js.map +1 -0
  73. package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.d.ts +8 -0
  74. package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.js +11 -0
  75. package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.js.map +1 -0
  76. package/features/FlushCache/FlushCacheOnFileDeleteHandler.d.ts +8 -0
  77. package/features/FlushCache/FlushCacheOnFileDeleteHandler.js +11 -0
  78. package/features/FlushCache/FlushCacheOnFileDeleteHandler.js.map +1 -0
  79. package/features/FlushCache/feature.d.ts +4 -0
  80. package/features/FlushCache/feature.js +13 -0
  81. package/features/FlushCache/feature.js.map +1 -0
  82. package/features/GetFileContentsById/GetFileContentsByIdUseCase.d.ts +15 -0
  83. package/features/GetFileContentsById/GetFileContentsByIdUseCase.js +38 -0
  84. package/features/GetFileContentsById/GetFileContentsByIdUseCase.js.map +1 -0
  85. package/features/GetFileContentsById/feature.d.ts +4 -0
  86. package/features/GetFileContentsById/feature.js +11 -0
  87. package/features/GetFileContentsById/feature.js.map +1 -0
  88. package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.d.ts +15 -0
  89. package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.js +72 -0
  90. package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.js.map +1 -0
  91. package/features/GetFileContentsByKey/feature.d.ts +4 -0
  92. package/features/GetFileContentsByKey/feature.js +11 -0
  93. package/features/GetFileContentsByKey/feature.js.map +1 -0
  94. package/features/GetUploadPayload/GetUploadPayloadUseCase.d.ts +16 -0
  95. package/features/GetUploadPayload/GetUploadPayloadUseCase.js +57 -0
  96. package/features/GetUploadPayload/GetUploadPayloadUseCase.js.map +1 -0
  97. package/features/GetUploadPayload/feature.d.ts +4 -0
  98. package/features/GetUploadPayload/feature.js +11 -0
  99. package/features/GetUploadPayload/feature.js.map +1 -0
  100. package/graphql/ServerGraphQLSchema.d.ts +15 -0
  101. package/graphql/ServerGraphQLSchema.js +221 -0
  102. package/graphql/ServerGraphQLSchema.js.map +1 -0
  103. package/index.d.ts +3 -0
  104. package/index.js +3 -0
  105. package/package.json +44 -0
  106. package/routes/UploadPartRoute/UploadPartRoute.d.ts +14 -0
  107. package/routes/UploadPartRoute/UploadPartRoute.js +66 -0
  108. package/routes/UploadPartRoute/UploadPartRoute.js.map +1 -0
  109. package/routes/UploadPartRoute/feature.d.ts +4 -0
  110. package/routes/UploadPartRoute/feature.js +11 -0
  111. package/routes/UploadPartRoute/feature.js.map +1 -0
  112. package/routes/UploadSingleFileRoute/UploadSingleFileRoute.d.ts +14 -0
  113. package/routes/UploadSingleFileRoute/UploadSingleFileRoute.js +70 -0
  114. package/routes/UploadSingleFileRoute/UploadSingleFileRoute.js.map +1 -0
  115. package/routes/UploadSingleFileRoute/feature.d.ts +4 -0
  116. package/routes/UploadSingleFileRoute/feature.js +11 -0
  117. package/routes/UploadSingleFileRoute/feature.js.map +1 -0
  118. package/routes/utils.d.ts +20 -0
  119. package/routes/utils.js +62 -0
  120. package/routes/utils.js.map +1 -0
  121. package/types.d.ts +10 -0
  122. package/types.js +1 -0
  123. package/utils/uploadToken.d.ts +3 -0
  124. package/utils/uploadToken.js +22 -0
  125. package/utils/uploadToken.js.map +1 -0
@@ -0,0 +1,221 @@
1
+ import p_map from "p-map";
2
+ import { CoreGraphQLSchemaFactory } from "@webiny/api-graphql/graphql/abstractions.js";
3
+ import { IdentityContext } from "@webiny/api-core/features/security/IdentityContext/index.js";
4
+ import { ErrorResponse, Response } from "@webiny/api-graphql/responses.js";
5
+ import { GetSettingsUseCase } from "@webiny/api-file-manager/features/settings/GetSettings/abstractions.js";
6
+ import { GetUploadPayloadUseCase } from "@webiny/api-file-manager/features/upload/GetUploadPayload/index.js";
7
+ import { CreateMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CreateMultiPartUpload/index.js";
8
+ import { CompleteMultiPartUploadUseCase } from "@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/index.js";
9
+ import { FileNormalizer, checkPermissions } from "@webiny/api-file-manager/features/upload/index.js";
10
+ const createNormalizer = ()=>new FileNormalizer();
11
+ const TYPE_DEFS = `
12
+ type UploadFileResponseDataFile {
13
+ id: ID!
14
+ name: String!
15
+ type: String!
16
+ size: Long!
17
+ key: String!
18
+ }
19
+
20
+ input PreSignedPostPayloadInput {
21
+ id: ID
22
+ name: String!
23
+ type: String!
24
+ size: Long!
25
+ key: String
26
+ keyPrefix: String
27
+ }
28
+
29
+ type GetPreSignedPostPayloadResponseDataFile {
30
+ id: ID!
31
+ name: String!
32
+ type: String!
33
+ size: Long!
34
+ key: String!
35
+ }
36
+
37
+ type GetPreSignedPostPayloadResponseData {
38
+ # Contains data that is necessary for initiating a file upload.
39
+ data: JSON!
40
+ file: UploadFileResponseDataFile!
41
+ }
42
+
43
+ type GetPreSignedPostPayloadResponse {
44
+ error: FmError
45
+ data: GetPreSignedPostPayloadResponseData
46
+ }
47
+
48
+ type MultiPartUploadFilePart {
49
+ partNumber: Int!
50
+ url: String!
51
+ }
52
+
53
+ type CreateMultiPartUploadResponseData {
54
+ file: GetPreSignedPostPayloadResponseDataFile!
55
+ uploadId: String!
56
+ parts: [MultiPartUploadFilePart!]!
57
+ }
58
+
59
+ type CompleteMultiPartUploadResponse {
60
+ data: Boolean
61
+ error: FmError
62
+ }
63
+
64
+ type GetPreSignedPostPayloadsResponse {
65
+ error: FmError
66
+ data: [GetPreSignedPostPayloadResponseData!]!
67
+ }
68
+
69
+ extend type FmQuery {
70
+ getPreSignedPostPayload(data: PreSignedPostPayloadInput!): GetPreSignedPostPayloadResponse
71
+ getPreSignedPostPayloads(
72
+ data: [PreSignedPostPayloadInput]!
73
+ ): GetPreSignedPostPayloadsResponse
74
+ }
75
+
76
+ type CreateMultiPartUploadResponse {
77
+ data: CreateMultiPartUploadResponseData
78
+ error: FmError
79
+ }
80
+
81
+ input MultiPartUploadFilePartInput {
82
+ partNumber: Int!
83
+ etag: String!
84
+ }
85
+
86
+ extend type FmMutation {
87
+ createMultiPartUpload(
88
+ data: PreSignedPostPayloadInput!
89
+ numberOfParts: Number!
90
+ ): CreateMultiPartUploadResponse
91
+
92
+ completeMultiPartUpload(
93
+ fileKey: String!
94
+ uploadId: String!
95
+ ): CompleteMultiPartUploadResponse
96
+ }
97
+ `;
98
+ class ServerGraphQLSchemaImpl {
99
+ async execute(builder) {
100
+ builder.addTypeDefs(TYPE_DEFS);
101
+ builder.addResolver({
102
+ path: "FmQuery.getPreSignedPostPayload",
103
+ dependencies: [
104
+ IdentityContext,
105
+ GetSettingsUseCase,
106
+ GetUploadPayloadUseCase
107
+ ],
108
+ resolver (identityContext, getSettings, getUploadPayload) {
109
+ return async ({ args })=>{
110
+ try {
111
+ await checkPermissions(identityContext, {
112
+ rwd: "w"
113
+ });
114
+ const settingsResult = await getSettings.execute();
115
+ const settings = settingsResult.value;
116
+ const normalizer = createNormalizer();
117
+ const payload = await getUploadPayload.execute(await normalizer.normalizeFile(args.data), settings);
118
+ return new Response(payload);
119
+ } catch (e) {
120
+ return new ErrorResponse({
121
+ message: e.message,
122
+ code: e.code,
123
+ data: e.data
124
+ });
125
+ }
126
+ };
127
+ }
128
+ });
129
+ builder.addResolver({
130
+ path: "FmQuery.getPreSignedPostPayloads",
131
+ dependencies: [
132
+ IdentityContext,
133
+ GetSettingsUseCase,
134
+ GetUploadPayloadUseCase
135
+ ],
136
+ resolver (identityContext, getSettings, getUploadPayload) {
137
+ return async ({ args })=>{
138
+ try {
139
+ await checkPermissions(identityContext, {
140
+ rwd: "w"
141
+ });
142
+ const settingsResult = await getSettings.execute();
143
+ const settings = settingsResult.value;
144
+ const normalizer = createNormalizer();
145
+ const payloads = await p_map(args.data, async (data)=>getUploadPayload.execute(await normalizer.normalizeFile(data), settings));
146
+ return new Response(payloads);
147
+ } catch (e) {
148
+ return new ErrorResponse({
149
+ message: e.message,
150
+ code: e.code,
151
+ data: e.data
152
+ });
153
+ }
154
+ };
155
+ }
156
+ });
157
+ builder.addResolver({
158
+ path: "FmMutation.createMultiPartUpload",
159
+ dependencies: [
160
+ IdentityContext,
161
+ CreateMultiPartUploadUseCase
162
+ ],
163
+ resolver (identityContext, createMultiPartUpload) {
164
+ return async ({ args })=>{
165
+ try {
166
+ await checkPermissions(identityContext, {
167
+ rwd: "w"
168
+ });
169
+ const normalizer = createNormalizer();
170
+ const result = await createMultiPartUpload.execute({
171
+ file: await normalizer.normalizeFile(args.data),
172
+ numberOfParts: args.numberOfParts
173
+ });
174
+ return new Response(result);
175
+ } catch (e) {
176
+ return new ErrorResponse({
177
+ message: e.message,
178
+ code: e.code,
179
+ data: e.data
180
+ });
181
+ }
182
+ };
183
+ }
184
+ });
185
+ builder.addResolver({
186
+ path: "FmMutation.completeMultiPartUpload",
187
+ dependencies: [
188
+ IdentityContext,
189
+ CompleteMultiPartUploadUseCase
190
+ ],
191
+ resolver (identityContext, completeMultiPartUpload) {
192
+ return async ({ args })=>{
193
+ try {
194
+ await checkPermissions(identityContext, {
195
+ rwd: "w"
196
+ });
197
+ await completeMultiPartUpload.execute({
198
+ fileKey: args.fileKey,
199
+ uploadId: args.uploadId
200
+ });
201
+ return new Response(true);
202
+ } catch (e) {
203
+ return new ErrorResponse({
204
+ message: e.message,
205
+ code: e.code,
206
+ data: e.data
207
+ });
208
+ }
209
+ };
210
+ }
211
+ });
212
+ return builder;
213
+ }
214
+ }
215
+ const ServerGraphQLSchema = CoreGraphQLSchemaFactory.createImplementation({
216
+ implementation: ServerGraphQLSchemaImpl,
217
+ dependencies: []
218
+ });
219
+ export { ServerGraphQLSchema };
220
+
221
+ //# sourceMappingURL=ServerGraphQLSchema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql/ServerGraphQLSchema.js","sources":["../../src/graphql/ServerGraphQLSchema.ts"],"sourcesContent":["import pMap from \"p-map\";\nimport { CoreGraphQLSchemaFactory } from \"@webiny/api-graphql/graphql/abstractions.js\";\nimport { GraphQLSchemaBuilder } from \"@webiny/api-graphql/features/GraphQLSchemaBuilder/abstractions.js\";\nimport { IdentityContext } from \"@webiny/api-core/features/security/IdentityContext/index.js\";\nimport { ErrorResponse, Response } from \"@webiny/api-graphql/responses.js\";\nimport { GetSettingsUseCase } from \"@webiny/api-file-manager/features/settings/GetSettings/abstractions.js\";\nimport { GetUploadPayloadUseCase } from \"@webiny/api-file-manager/features/upload/GetUploadPayload/index.js\";\nimport { CreateMultiPartUploadUseCase } from \"@webiny/api-file-manager/features/upload/CreateMultiPartUpload/index.js\";\nimport { CompleteMultiPartUploadUseCase } from \"@webiny/api-file-manager/features/upload/CompleteMultiPartUpload/index.js\";\nimport {\n checkPermissions,\n FileNormalizer\n} from \"@webiny/api-file-manager/features/upload/index.js\";\nimport type { FileData } from \"@webiny/api-file-manager/features/upload/types.js\";\n\n// Build the normalizer WITHOUT the gql context: the domain's `createFileNormalizerFromContext` reads\n// `context.plugins.byType(...)` (the legacy plugins registry), which the DI-native server gql context\n// does not populate — calling it throws \"Cannot read properties of undefined (reading 'byType')\". The\n// modifier is optional; FileNormalizer still generates the id/type/key. The server has no legacy\n// FileUploadModifierPlugins, so an empty (no-modifier) normalizer is the correct behaviour here.\nconst createNormalizer = () => new FileNormalizer();\n\nconst TYPE_DEFS = /* GraphQL */ `\n type UploadFileResponseDataFile {\n id: ID!\n name: String!\n type: String!\n size: Long!\n key: String!\n }\n\n input PreSignedPostPayloadInput {\n id: ID\n name: String!\n type: String!\n size: Long!\n key: String\n keyPrefix: String\n }\n\n type GetPreSignedPostPayloadResponseDataFile {\n id: ID!\n name: String!\n type: String!\n size: Long!\n key: String!\n }\n\n type GetPreSignedPostPayloadResponseData {\n # Contains data that is necessary for initiating a file upload.\n data: JSON!\n file: UploadFileResponseDataFile!\n }\n\n type GetPreSignedPostPayloadResponse {\n error: FmError\n data: GetPreSignedPostPayloadResponseData\n }\n\n type MultiPartUploadFilePart {\n partNumber: Int!\n url: String!\n }\n\n type CreateMultiPartUploadResponseData {\n file: GetPreSignedPostPayloadResponseDataFile!\n uploadId: String!\n parts: [MultiPartUploadFilePart!]!\n }\n\n type CompleteMultiPartUploadResponse {\n data: Boolean\n error: FmError\n }\n\n type GetPreSignedPostPayloadsResponse {\n error: FmError\n data: [GetPreSignedPostPayloadResponseData!]!\n }\n\n extend type FmQuery {\n getPreSignedPostPayload(data: PreSignedPostPayloadInput!): GetPreSignedPostPayloadResponse\n getPreSignedPostPayloads(\n data: [PreSignedPostPayloadInput]!\n ): GetPreSignedPostPayloadsResponse\n }\n\n type CreateMultiPartUploadResponse {\n data: CreateMultiPartUploadResponseData\n error: FmError\n }\n\n input MultiPartUploadFilePartInput {\n partNumber: Int!\n etag: String!\n }\n\n extend type FmMutation {\n createMultiPartUpload(\n data: PreSignedPostPayloadInput!\n numberOfParts: Number!\n ): CreateMultiPartUploadResponse\n\n completeMultiPartUpload(\n fileKey: String!\n uploadId: String!\n ): CompleteMultiPartUploadResponse\n }\n`;\n\n/**\n * Upload GraphQL for the self-hosted (server) hosting type. Mirrors the SDL that `api-file-manager-s3`'s\n * `S3GraphQLSchema` contributes (same query/mutation names + `PreSignedPostPayloadInput`), so the\n * transport-agnostic SDK works unchanged. Only the resolvers differ from S3 — they resolve the server\n * upload use cases (local disk + the `/webiny-file-upload` HTTP routes) instead of an S3 client.\n */\nclass ServerGraphQLSchemaImpl implements CoreGraphQLSchemaFactory.Interface {\n public async execute(builder: GraphQLSchemaBuilder.Interface): CoreGraphQLSchemaFactory.Return {\n builder.addTypeDefs(TYPE_DEFS);\n\n builder.addResolver<{ data: any }>({\n path: \"FmQuery.getPreSignedPostPayload\",\n dependencies: [IdentityContext, GetSettingsUseCase, GetUploadPayloadUseCase],\n resolver(\n identityContext: IdentityContext.Interface,\n getSettings: GetSettingsUseCase.Interface,\n getUploadPayload: GetUploadPayloadUseCase.Interface\n ) {\n return async ({ args }) => {\n try {\n await checkPermissions(identityContext, { rwd: \"w\" });\n\n const settingsResult = await getSettings.execute();\n const settings = settingsResult.value;\n\n const normalizer = createNormalizer();\n const payload = await getUploadPayload.execute(\n (await normalizer.normalizeFile(args.data)) as FileData,\n settings\n );\n\n return new Response(payload);\n } catch (e) {\n return new ErrorResponse({\n message: e.message,\n code: e.code,\n data: e.data\n });\n }\n };\n }\n });\n\n builder.addResolver<{ data: any[] }>({\n path: \"FmQuery.getPreSignedPostPayloads\",\n dependencies: [IdentityContext, GetSettingsUseCase, GetUploadPayloadUseCase],\n resolver(\n identityContext: IdentityContext.Interface,\n getSettings: GetSettingsUseCase.Interface,\n getUploadPayload: GetUploadPayloadUseCase.Interface\n ) {\n return async ({ args }) => {\n try {\n await checkPermissions(identityContext, { rwd: \"w\" });\n\n const settingsResult = await getSettings.execute();\n const settings = settingsResult.value;\n\n const normalizer = createNormalizer();\n const payloads = await pMap(args.data, async (data: any) => {\n return getUploadPayload.execute(\n (await normalizer.normalizeFile(data)) as FileData,\n settings\n );\n });\n\n return new Response(payloads);\n } catch (e) {\n return new ErrorResponse({\n message: e.message,\n code: e.code,\n data: e.data\n });\n }\n };\n }\n });\n\n builder.addResolver<{ data: any; numberOfParts: number }>({\n path: \"FmMutation.createMultiPartUpload\",\n dependencies: [IdentityContext, CreateMultiPartUploadUseCase],\n resolver(\n identityContext: IdentityContext.Interface,\n createMultiPartUpload: CreateMultiPartUploadUseCase.Interface\n ) {\n return async ({ args }) => {\n try {\n await checkPermissions(identityContext, { rwd: \"w\" });\n\n const normalizer = createNormalizer();\n const result = await createMultiPartUpload.execute({\n file: (await normalizer.normalizeFile(args.data)) as FileData,\n numberOfParts: args.numberOfParts\n });\n\n return new Response(result);\n } catch (e) {\n return new ErrorResponse({\n message: e.message,\n code: e.code,\n data: e.data\n });\n }\n };\n }\n });\n\n builder.addResolver<{ fileKey: string; uploadId: string }>({\n path: \"FmMutation.completeMultiPartUpload\",\n dependencies: [IdentityContext, CompleteMultiPartUploadUseCase],\n resolver(\n identityContext: IdentityContext.Interface,\n completeMultiPartUpload: CompleteMultiPartUploadUseCase.Interface\n ) {\n return async ({ args }) => {\n try {\n await checkPermissions(identityContext, { rwd: \"w\" });\n\n await completeMultiPartUpload.execute({\n fileKey: args.fileKey,\n uploadId: args.uploadId\n });\n\n return new Response(true);\n } catch (e) {\n return new ErrorResponse({\n message: e.message,\n code: e.code,\n data: e.data\n });\n }\n };\n }\n });\n\n return builder;\n }\n}\n\nexport const ServerGraphQLSchema = CoreGraphQLSchemaFactory.createImplementation({\n implementation: ServerGraphQLSchemaImpl,\n dependencies: []\n});\n"],"names":["createNormalizer","FileNormalizer","TYPE_DEFS","ServerGraphQLSchemaImpl","builder","IdentityContext","GetSettingsUseCase","GetUploadPayloadUseCase","identityContext","getSettings","getUploadPayload","args","checkPermissions","settingsResult","settings","normalizer","payload","Response","e","ErrorResponse","payloads","pMap","data","CreateMultiPartUploadUseCase","createMultiPartUpload","result","CompleteMultiPartUploadUseCase","completeMultiPartUpload","ServerGraphQLSchema","CoreGraphQLSchemaFactory"],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { createFileUploadModifier } from "@webiny/api-file-manager/features/upload/index.js";
2
+ export { createAssetDelivery } from "./assetDelivery/createAssetDelivery.js";
3
+ export { FileManagerServerFeature } from "./FileManagerServerFeature.js";
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { createFileUploadModifier } from "@webiny/api-file-manager/features/upload/index.js";
2
+ export { createAssetDelivery } from "./assetDelivery/createAssetDelivery.js";
3
+ export { FileManagerServerFeature } from "./FileManagerServerFeature.js";
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@webiny/api-file-manager-server",
3
+ "version": "0.0.0-unstable.0d717d18dd",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./index.js",
7
+ "./*": "./*"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/webiny/webiny-js.git"
12
+ },
13
+ "description": "File storage local server plugin for the @webiny/api-file-manager.",
14
+ "author": "Webiny Ltd",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@webiny/api-core": "0.0.0-unstable.0d717d18dd",
18
+ "@webiny/api-file-manager": "0.0.0-unstable.0d717d18dd",
19
+ "@webiny/api-graphql": "0.0.0-unstable.0d717d18dd",
20
+ "@webiny/background-tasks": "0.0.0-unstable.0d717d18dd",
21
+ "@webiny/event-handler-core": "0.0.0-unstable.0d717d18dd",
22
+ "@webiny/feature": "0.0.0-unstable.0d717d18dd",
23
+ "@webiny/handler": "0.0.0-unstable.0d717d18dd",
24
+ "@webiny/plugins": "0.0.0-unstable.0d717d18dd",
25
+ "@webiny/utils": "0.0.0-unstable.0d717d18dd",
26
+ "@webiny/validation": "0.0.0-unstable.0d717d18dd",
27
+ "exifreader": "4.41.3",
28
+ "p-map": "7.0.6",
29
+ "sharp": "0.35.3"
30
+ },
31
+ "devDependencies": {
32
+ "@webiny/build-tools": "0.0.0-unstable.0d717d18dd",
33
+ "rimraf": "6.1.3",
34
+ "typescript": "7.0.2",
35
+ "vitest": "4.1.10"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "gitHead": "8476da73b653c89cc1474d968baf55c1b0ae0e5f",
41
+ "webiny": {
42
+ "publishFrom": "dist"
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ import { HttpRoute } from "@webiny/event-handler-core";
2
+ import type { IHttpRequest, IHttpResponse } from "@webiny/event-handler-core";
3
+ import { FileManagerServerConfig } from "../../features/FileManagerServerConfig/abstractions.js";
4
+ declare class UploadPartRouteImpl implements HttpRoute.Interface {
5
+ private readonly config;
6
+ readonly method = "PUT";
7
+ readonly path = "/webiny-file-upload/parts";
8
+ constructor(config: FileManagerServerConfig.Interface);
9
+ handle(request: IHttpRequest): Promise<IHttpResponse>;
10
+ }
11
+ export declare const UploadPartRoute: typeof UploadPartRouteImpl & {
12
+ __abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
13
+ };
14
+ export {};
@@ -0,0 +1,66 @@
1
+ import node_path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { HttpRoute } from "@webiny/event-handler-core";
5
+ import { verifyUploadToken } from "../../utils/uploadToken.js";
6
+ import { FileManagerServerConfig } from "../../features/FileManagerServerConfig/abstractions.js";
7
+ import { isPathContained, json, toBuffer } from "../utils.js";
8
+ class UploadPartRouteImpl {
9
+ constructor(config){
10
+ this.config = config;
11
+ this.method = "PUT";
12
+ this.path = "/webiny-file-upload/parts";
13
+ }
14
+ async handle(request) {
15
+ const storagePath = this.config.storagePath;
16
+ const secret = this.config.uploadSecret;
17
+ const query = request.query ?? {};
18
+ const uploadId = query["uploadId"];
19
+ const partNumberStr = query["partNumber"];
20
+ const token = query["token"];
21
+ if (!uploadId || !partNumberStr || !token) return json(400, {
22
+ error: "Missing uploadId, partNumber, or token."
23
+ });
24
+ const partNumber = parseInt(partNumberStr, 10);
25
+ if (isNaN(partNumber) || partNumber < 1) return json(400, {
26
+ error: "Invalid partNumber."
27
+ });
28
+ let payload;
29
+ try {
30
+ payload = verifyUploadToken(token, secret);
31
+ } catch (err) {
32
+ return json(400, {
33
+ error: err instanceof Error ? err.message : "Invalid token."
34
+ });
35
+ }
36
+ const expectedKey = `tenants/${payload.tenantId}/multipart/${uploadId}/part-${partNumber}`;
37
+ if (payload.key !== expectedKey) return json(400, {
38
+ error: "Token key mismatch."
39
+ });
40
+ const destPath = node_path.join(storagePath, expectedKey);
41
+ if (!isPathContained(destPath, storagePath)) return json(400, {
42
+ error: "Invalid path."
43
+ });
44
+ await mkdir(node_path.dirname(destPath), {
45
+ recursive: true
46
+ });
47
+ const body = toBuffer(request.body);
48
+ await writeFile(destPath, body);
49
+ const etag = createHash("md5").update(body).digest("hex");
50
+ return {
51
+ statusCode: 200,
52
+ headers: {
53
+ ETag: etag
54
+ }
55
+ };
56
+ }
57
+ }
58
+ const UploadPartRoute = HttpRoute.createImplementation({
59
+ implementation: UploadPartRouteImpl,
60
+ dependencies: [
61
+ FileManagerServerConfig
62
+ ]
63
+ });
64
+ export { UploadPartRoute };
65
+
66
+ //# sourceMappingURL=UploadPartRoute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes/UploadPartRoute/UploadPartRoute.js","sources":["../../../src/routes/UploadPartRoute/UploadPartRoute.ts"],"sourcesContent":["import path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { HttpRoute } from \"@webiny/event-handler-core\";\nimport type { IHttpRequest, IHttpResponse } from \"@webiny/event-handler-core\";\nimport { verifyUploadToken } from \"~/utils/uploadToken.js\";\nimport { FileManagerServerConfig } from \"~/features/FileManagerServerConfig/abstractions.js\";\nimport { isPathContained, json, toBuffer } from \"../utils.js\";\n\nclass UploadPartRouteImpl implements HttpRoute.Interface {\n public readonly method = \"PUT\";\n public readonly path = \"/webiny-file-upload/parts\";\n\n public constructor(private readonly config: FileManagerServerConfig.Interface) {}\n\n public async handle(request: IHttpRequest): Promise<IHttpResponse> {\n const storagePath = this.config.storagePath;\n const secret = this.config.uploadSecret;\n\n const query = request.query ?? {};\n const uploadId = query[\"uploadId\"];\n const partNumberStr = query[\"partNumber\"];\n const token = query[\"token\"];\n\n if (!uploadId || !partNumberStr || !token) {\n return json(400, { error: \"Missing uploadId, partNumber, or token.\" });\n }\n\n const partNumber = parseInt(partNumberStr, 10);\n if (isNaN(partNumber) || partNumber < 1) {\n return json(400, { error: \"Invalid partNumber.\" });\n }\n\n let payload;\n try {\n payload = verifyUploadToken(token, secret);\n } catch (err) {\n return json(400, { error: err instanceof Error ? err.message : \"Invalid token.\" });\n }\n\n const expectedKey = `tenants/${payload.tenantId}/multipart/${uploadId}/part-${partNumber}`;\n if (payload.key !== expectedKey) {\n return json(400, { error: \"Token key mismatch.\" });\n }\n\n const destPath = path.join(storagePath, expectedKey);\n if (!isPathContained(destPath, storagePath)) {\n return json(400, { error: \"Invalid path.\" });\n }\n\n await mkdir(path.dirname(destPath), { recursive: true });\n\n const body = toBuffer(request.body);\n await writeFile(destPath, body);\n\n const etag = createHash(\"md5\").update(body).digest(\"hex\");\n\n return { statusCode: 200, headers: { ETag: etag } };\n }\n}\n\nexport const UploadPartRoute = HttpRoute.createImplementation({\n implementation: UploadPartRouteImpl,\n dependencies: [FileManagerServerConfig]\n});\n"],"names":["UploadPartRouteImpl","config","request","storagePath","secret","query","uploadId","partNumberStr","token","json","partNumber","parseInt","isNaN","payload","verifyUploadToken","err","Error","expectedKey","destPath","path","isPathContained","mkdir","body","toBuffer","writeFile","etag","createHash","UploadPartRoute","HttpRoute","FileManagerServerConfig"],"mappings":";;;;;;;AASA,MAAMA;IAIF,YAAoCC,MAAyC,CAAE;aAA3CA,MAAM,GAANA;aAHpB,MAAM,GAAG;aACT,IAAI,GAAG;IAEyD;IAEhF,MAAa,OAAOC,OAAqB,EAA0B;QAC/D,MAAMC,cAAc,IAAI,CAAC,MAAM,CAAC,WAAW;QAC3C,MAAMC,SAAS,IAAI,CAAC,MAAM,CAAC,YAAY;QAEvC,MAAMC,QAAQH,QAAQ,KAAK,IAAI,CAAC;QAChC,MAAMI,WAAWD,KAAK,CAAC,WAAW;QAClC,MAAME,gBAAgBF,KAAK,CAAC,aAAa;QACzC,MAAMG,QAAQH,KAAK,CAAC,QAAQ;QAE5B,IAAI,CAACC,YAAY,CAACC,iBAAiB,CAACC,OAChC,OAAOC,KAAK,KAAK;YAAE,OAAO;QAA0C;QAGxE,MAAMC,aAAaC,SAASJ,eAAe;QAC3C,IAAIK,MAAMF,eAAeA,aAAa,GAClC,OAAOD,KAAK,KAAK;YAAE,OAAO;QAAsB;QAGpD,IAAII;QACJ,IAAI;YACAA,UAAUC,kBAAkBN,OAAOJ;QACvC,EAAE,OAAOW,KAAK;YACV,OAAON,KAAK,KAAK;gBAAE,OAAOM,eAAeC,QAAQD,IAAI,OAAO,GAAG;YAAiB;QACpF;QAEA,MAAME,cAAc,CAAC,QAAQ,EAAEJ,QAAQ,QAAQ,CAAC,WAAW,EAAEP,SAAS,MAAM,EAAEI,YAAY;QAC1F,IAAIG,QAAQ,GAAG,KAAKI,aAChB,OAAOR,KAAK,KAAK;YAAE,OAAO;QAAsB;QAGpD,MAAMS,WAAWC,UAAAA,IAAS,CAAChB,aAAac;QACxC,IAAI,CAACG,gBAAgBF,UAAUf,cAC3B,OAAOM,KAAK,KAAK;YAAE,OAAO;QAAgB;QAG9C,MAAMY,MAAMF,UAAAA,OAAY,CAACD,WAAW;YAAE,WAAW;QAAK;QAEtD,MAAMI,OAAOC,SAASrB,QAAQ,IAAI;QAClC,MAAMsB,UAAUN,UAAUI;QAE1B,MAAMG,OAAOC,WAAW,OAAO,MAAM,CAACJ,MAAM,MAAM,CAAC;QAEnD,OAAO;YAAE,YAAY;YAAK,SAAS;gBAAE,MAAMG;YAAK;QAAE;IACtD;AACJ;AAEO,MAAME,kBAAkBC,UAAU,oBAAoB,CAAC;IAC1D,gBAAgB5B;IAChB,cAAc;QAAC6B;KAAwB;AAC3C"}
@@ -0,0 +1,4 @@
1
+ export declare const UploadPartRouteFeature: {
2
+ name: string;
3
+ register(container: import("@webiny/di").Container): void;
4
+ };
@@ -0,0 +1,11 @@
1
+ import { createFeature } from "@webiny/feature/api";
2
+ import { UploadPartRoute } from "./UploadPartRoute.js";
3
+ const UploadPartRouteFeature = createFeature({
4
+ name: "FileManagerServer/UploadPartRoute",
5
+ register (container) {
6
+ container.register(UploadPartRoute);
7
+ }
8
+ });
9
+ export { UploadPartRouteFeature };
10
+
11
+ //# sourceMappingURL=feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes/UploadPartRoute/feature.js","sources":["../../../src/routes/UploadPartRoute/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { UploadPartRoute } from \"./UploadPartRoute.js\";\n\nexport const UploadPartRouteFeature = createFeature({\n name: \"FileManagerServer/UploadPartRoute\",\n register(container) {\n container.register(UploadPartRoute);\n }\n});\n"],"names":["UploadPartRouteFeature","createFeature","container","UploadPartRoute"],"mappings":";;AAGO,MAAMA,yBAAyBC,cAAc;IAChD,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;IACvB;AACJ"}
@@ -0,0 +1,14 @@
1
+ import { HttpRoute } from "@webiny/event-handler-core";
2
+ import type { IHttpRequest, IHttpResponse } from "@webiny/event-handler-core";
3
+ import { FileManagerServerConfig } from "../../features/FileManagerServerConfig/abstractions.js";
4
+ declare class UploadSingleFileRouteImpl implements HttpRoute.Interface {
5
+ private readonly config;
6
+ readonly method = "POST";
7
+ readonly path = "/webiny-file-upload";
8
+ constructor(config: FileManagerServerConfig.Interface);
9
+ handle(request: IHttpRequest): Promise<IHttpResponse>;
10
+ }
11
+ export declare const UploadSingleFileRoute: typeof UploadSingleFileRouteImpl & {
12
+ __abstraction: import("@webiny/di").Abstraction<import("@webiny/event-handler-core").IHttpRoute>;
13
+ };
14
+ export {};
@@ -0,0 +1,70 @@
1
+ import node_path from "node:path";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { HttpRoute } from "@webiny/event-handler-core";
4
+ import { verifyUploadToken } from "../../utils/uploadToken.js";
5
+ import { FileManagerServerConfig } from "../../features/FileManagerServerConfig/abstractions.js";
6
+ import { getBoundary, isPathContained, json, parseMultipart, toBuffer } from "../utils.js";
7
+ class UploadSingleFileRouteImpl {
8
+ constructor(config){
9
+ this.config = config;
10
+ this.method = "POST";
11
+ this.path = "/webiny-file-upload";
12
+ }
13
+ async handle(request) {
14
+ const storagePath = this.config.storagePath;
15
+ const secret = this.config.uploadSecret;
16
+ const contentType = request.headers["content-type"] ?? request.headers["Content-Type"] ?? "";
17
+ const boundary = getBoundary(contentType);
18
+ if (!boundary) return json(400, {
19
+ error: "Expected multipart/form-data with a boundary."
20
+ });
21
+ const { fields, files } = parseMultipart(toBuffer(request.body), boundary);
22
+ const key = fields.find((field)=>"key" === field.name)?.value;
23
+ const token = fields.find((field)=>"token" === field.name)?.value;
24
+ const file = files[0];
25
+ if (!key || !token) return json(400, {
26
+ error: "Missing key or token."
27
+ });
28
+ if (!file) return json(400, {
29
+ error: "No file in request."
30
+ });
31
+ let payload;
32
+ try {
33
+ payload = verifyUploadToken(token, secret);
34
+ } catch (err) {
35
+ return json(400, {
36
+ error: err instanceof Error ? err.message : "Invalid token."
37
+ });
38
+ }
39
+ if (payload.key !== key) return json(400, {
40
+ error: "Token key mismatch."
41
+ });
42
+ const fileSize = file.data.length;
43
+ if (payload.uploadMaxFileSize > 0 && fileSize > payload.uploadMaxFileSize) return json(400, {
44
+ error: "File exceeds maximum allowed size."
45
+ });
46
+ if (payload.uploadMinFileSize > 0 && fileSize < payload.uploadMinFileSize) return json(400, {
47
+ error: "File is below minimum allowed size."
48
+ });
49
+ const destPath = node_path.join(storagePath, key);
50
+ if (!isPathContained(destPath, storagePath)) return json(400, {
51
+ error: "Invalid path."
52
+ });
53
+ await mkdir(node_path.dirname(destPath), {
54
+ recursive: true
55
+ });
56
+ await writeFile(destPath, file.data);
57
+ return {
58
+ statusCode: 204
59
+ };
60
+ }
61
+ }
62
+ const UploadSingleFileRoute = HttpRoute.createImplementation({
63
+ implementation: UploadSingleFileRouteImpl,
64
+ dependencies: [
65
+ FileManagerServerConfig
66
+ ]
67
+ });
68
+ export { UploadSingleFileRoute };
69
+
70
+ //# sourceMappingURL=UploadSingleFileRoute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes/UploadSingleFileRoute/UploadSingleFileRoute.js","sources":["../../../src/routes/UploadSingleFileRoute/UploadSingleFileRoute.ts"],"sourcesContent":["import path from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { HttpRoute } from \"@webiny/event-handler-core\";\nimport type { IHttpRequest, IHttpResponse } from \"@webiny/event-handler-core\";\nimport { verifyUploadToken } from \"~/utils/uploadToken.js\";\nimport { FileManagerServerConfig } from \"~/features/FileManagerServerConfig/abstractions.js\";\nimport { isPathContained, json, toBuffer, parseMultipart, getBoundary } from \"../utils.js\";\n\nclass UploadSingleFileRouteImpl implements HttpRoute.Interface {\n public readonly method = \"POST\";\n public readonly path = \"/webiny-file-upload\";\n\n public constructor(private readonly config: FileManagerServerConfig.Interface) {}\n\n public async handle(request: IHttpRequest): Promise<IHttpResponse> {\n const storagePath = this.config.storagePath;\n const secret = this.config.uploadSecret;\n\n const contentType =\n request.headers[\"content-type\"] ?? request.headers[\"Content-Type\"] ?? \"\";\n const boundary = getBoundary(contentType);\n if (!boundary) {\n return json(400, { error: \"Expected multipart/form-data with a boundary.\" });\n }\n\n const { fields, files } = parseMultipart(toBuffer(request.body), boundary);\n const key = fields.find(field => field.name === \"key\")?.value;\n const token = fields.find(field => field.name === \"token\")?.value;\n const file = files[0];\n\n if (!key || !token) {\n return json(400, { error: \"Missing key or token.\" });\n }\n\n if (!file) {\n return json(400, { error: \"No file in request.\" });\n }\n\n let payload;\n try {\n payload = verifyUploadToken(token, secret);\n } catch (err) {\n return json(400, { error: err instanceof Error ? err.message : \"Invalid token.\" });\n }\n\n if (payload.key !== key) {\n return json(400, { error: \"Token key mismatch.\" });\n }\n\n const fileSize = file.data.length;\n if (payload.uploadMaxFileSize > 0 && fileSize > payload.uploadMaxFileSize) {\n return json(400, { error: \"File exceeds maximum allowed size.\" });\n }\n\n if (payload.uploadMinFileSize > 0 && fileSize < payload.uploadMinFileSize) {\n return json(400, { error: \"File is below minimum allowed size.\" });\n }\n\n const destPath = path.join(storagePath, key);\n if (!isPathContained(destPath, storagePath)) {\n return json(400, { error: \"Invalid path.\" });\n }\n\n await mkdir(path.dirname(destPath), { recursive: true });\n await writeFile(destPath, file.data);\n\n return { statusCode: 204 };\n }\n}\n\nexport const UploadSingleFileRoute = HttpRoute.createImplementation({\n implementation: UploadSingleFileRouteImpl,\n dependencies: [FileManagerServerConfig]\n});\n"],"names":["UploadSingleFileRouteImpl","config","request","storagePath","secret","contentType","boundary","getBoundary","json","fields","files","parseMultipart","toBuffer","key","field","token","file","payload","verifyUploadToken","err","Error","fileSize","destPath","path","isPathContained","mkdir","writeFile","UploadSingleFileRoute","HttpRoute","FileManagerServerConfig"],"mappings":";;;;;;AAQA,MAAMA;IAIF,YAAoCC,MAAyC,CAAE;aAA3CA,MAAM,GAANA;aAHpB,MAAM,GAAG;aACT,IAAI,GAAG;IAEyD;IAEhF,MAAa,OAAOC,OAAqB,EAA0B;QAC/D,MAAMC,cAAc,IAAI,CAAC,MAAM,CAAC,WAAW;QAC3C,MAAMC,SAAS,IAAI,CAAC,MAAM,CAAC,YAAY;QAEvC,MAAMC,cACFH,QAAQ,OAAO,CAAC,eAAe,IAAIA,QAAQ,OAAO,CAAC,eAAe,IAAI;QAC1E,MAAMI,WAAWC,YAAYF;QAC7B,IAAI,CAACC,UACD,OAAOE,KAAK,KAAK;YAAE,OAAO;QAAgD;QAG9E,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAE,GAAGC,eAAeC,SAASV,QAAQ,IAAI,GAAGI;QACjE,MAAMO,MAAMJ,OAAO,IAAI,CAACK,CAAAA,QAASA,AAAe,UAAfA,MAAM,IAAI,GAAa;QACxD,MAAMC,QAAQN,OAAO,IAAI,CAACK,CAAAA,QAASA,AAAe,YAAfA,MAAM,IAAI,GAAe;QAC5D,MAAME,OAAON,KAAK,CAAC,EAAE;QAErB,IAAI,CAACG,OAAO,CAACE,OACT,OAAOP,KAAK,KAAK;YAAE,OAAO;QAAwB;QAGtD,IAAI,CAACQ,MACD,OAAOR,KAAK,KAAK;YAAE,OAAO;QAAsB;QAGpD,IAAIS;QACJ,IAAI;YACAA,UAAUC,kBAAkBH,OAAOX;QACvC,EAAE,OAAOe,KAAK;YACV,OAAOX,KAAK,KAAK;gBAAE,OAAOW,eAAeC,QAAQD,IAAI,OAAO,GAAG;YAAiB;QACpF;QAEA,IAAIF,QAAQ,GAAG,KAAKJ,KAChB,OAAOL,KAAK,KAAK;YAAE,OAAO;QAAsB;QAGpD,MAAMa,WAAWL,KAAK,IAAI,CAAC,MAAM;QACjC,IAAIC,QAAQ,iBAAiB,GAAG,KAAKI,WAAWJ,QAAQ,iBAAiB,EACrE,OAAOT,KAAK,KAAK;YAAE,OAAO;QAAqC;QAGnE,IAAIS,QAAQ,iBAAiB,GAAG,KAAKI,WAAWJ,QAAQ,iBAAiB,EACrE,OAAOT,KAAK,KAAK;YAAE,OAAO;QAAsC;QAGpE,MAAMc,WAAWC,UAAAA,IAAS,CAACpB,aAAaU;QACxC,IAAI,CAACW,gBAAgBF,UAAUnB,cAC3B,OAAOK,KAAK,KAAK;YAAE,OAAO;QAAgB;QAG9C,MAAMiB,MAAMF,UAAAA,OAAY,CAACD,WAAW;YAAE,WAAW;QAAK;QACtD,MAAMI,UAAUJ,UAAUN,KAAK,IAAI;QAEnC,OAAO;YAAE,YAAY;QAAI;IAC7B;AACJ;AAEO,MAAMW,wBAAwBC,UAAU,oBAAoB,CAAC;IAChE,gBAAgB5B;IAChB,cAAc;QAAC6B;KAAwB;AAC3C"}
@@ -0,0 +1,4 @@
1
+ export declare const UploadSingleFileRouteFeature: {
2
+ name: string;
3
+ register(container: import("@webiny/di").Container): void;
4
+ };
@@ -0,0 +1,11 @@
1
+ import { createFeature } from "@webiny/feature/api";
2
+ import { UploadSingleFileRoute } from "./UploadSingleFileRoute.js";
3
+ const UploadSingleFileRouteFeature = createFeature({
4
+ name: "FileManagerServer/UploadSingleFileRoute",
5
+ register (container) {
6
+ container.register(UploadSingleFileRoute);
7
+ }
8
+ });
9
+ export { UploadSingleFileRouteFeature };
10
+
11
+ //# sourceMappingURL=feature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes/UploadSingleFileRoute/feature.js","sources":["../../../src/routes/UploadSingleFileRoute/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/api\";\nimport { UploadSingleFileRoute } from \"./UploadSingleFileRoute.js\";\n\nexport const UploadSingleFileRouteFeature = createFeature({\n name: \"FileManagerServer/UploadSingleFileRoute\",\n register(container) {\n container.register(UploadSingleFileRoute);\n }\n});\n"],"names":["UploadSingleFileRouteFeature","createFeature","container","UploadSingleFileRoute"],"mappings":";;AAGO,MAAMA,+BAA+BC,cAAc;IACtD,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;IACvB;AACJ"}
@@ -0,0 +1,20 @@
1
+ import type { IHttpResponse } from "@webiny/event-handler-core";
2
+ export declare const isPathContained: (filePath: string, storagePath: string) => boolean;
3
+ export declare const json: (statusCode: number, data: unknown) => IHttpResponse;
4
+ export declare const toBuffer: (body: unknown) => Buffer;
5
+ interface MultipartField {
6
+ name: string;
7
+ value: string;
8
+ }
9
+ interface MultipartFile {
10
+ name: string;
11
+ filename: string;
12
+ data: Buffer;
13
+ }
14
+ export interface MultipartResult {
15
+ fields: MultipartField[];
16
+ files: MultipartFile[];
17
+ }
18
+ export declare const parseMultipart: (buffer: Buffer, boundary: string) => MultipartResult;
19
+ export declare const getBoundary: (contentType: string) => string | undefined;
20
+ export {};
@@ -0,0 +1,62 @@
1
+ import node_path from "node:path";
2
+ const isPathContained = (filePath, storagePath)=>{
3
+ const resolved = node_path.resolve(filePath);
4
+ const root = node_path.resolve(storagePath);
5
+ return resolved === root || resolved.startsWith(root + node_path.sep);
6
+ };
7
+ const json = (statusCode, data)=>({
8
+ statusCode,
9
+ headers: {
10
+ "content-type": "application/json"
11
+ },
12
+ body: JSON.stringify(data)
13
+ });
14
+ const toBuffer = (body)=>{
15
+ if (Buffer.isBuffer(body)) return body;
16
+ if (body instanceof Uint8Array) return Buffer.from(body);
17
+ if ("string" == typeof body) return Buffer.from(body, "base64");
18
+ return Buffer.alloc(0);
19
+ };
20
+ const parseMultipart = (buffer, boundary)=>{
21
+ const fields = [];
22
+ const files = [];
23
+ const separator = Buffer.from(`--${boundary}`);
24
+ let idx = buffer.indexOf(separator);
25
+ while(-1 !== idx){
26
+ const next = buffer.indexOf(separator, idx + separator.length);
27
+ if (-1 === next) break;
28
+ let part = buffer.subarray(idx + separator.length, next);
29
+ if ("\r\n" === part.subarray(0, 2).toString("latin1")) part = part.subarray(2);
30
+ if ("\r\n" === part.subarray(-2).toString("latin1")) part = part.subarray(0, -2);
31
+ const headerEnd = part.indexOf("\r\n\r\n");
32
+ if (-1 !== headerEnd) {
33
+ const rawHeaders = part.subarray(0, headerEnd).toString("utf8");
34
+ const content = part.subarray(headerEnd + 4);
35
+ const disposition = /content-disposition:[^\r\n]*/i.exec(rawHeaders)?.[0] ?? "";
36
+ const name = /name="([^"]*)"/i.exec(disposition)?.[1];
37
+ const filename = /filename="([^"]*)"/i.exec(disposition)?.[1];
38
+ if (void 0 !== name) if (void 0 !== filename) files.push({
39
+ name,
40
+ filename,
41
+ data: content
42
+ });
43
+ else fields.push({
44
+ name,
45
+ value: content.toString("utf8")
46
+ });
47
+ }
48
+ idx = next;
49
+ }
50
+ return {
51
+ fields,
52
+ files
53
+ };
54
+ };
55
+ const getBoundary = (contentType)=>{
56
+ const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
57
+ const value = match?.[1] ?? match?.[2];
58
+ return value?.trim();
59
+ };
60
+ export { getBoundary, isPathContained, json, parseMultipart, toBuffer };
61
+
62
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes/utils.js","sources":["../../src/routes/utils.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { IHttpResponse } from \"@webiny/event-handler-core\";\n\nexport const isPathContained = (filePath: string, storagePath: string): boolean => {\n const resolved = path.resolve(filePath);\n const root = path.resolve(storagePath);\n return resolved === root || resolved.startsWith(root + path.sep);\n};\n\nexport const json = (statusCode: number, data: unknown): IHttpResponse => ({\n statusCode,\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(data)\n});\n\n/* Normalise a transport request body into a Buffer. */\nexport const toBuffer = (body: unknown): Buffer => {\n if (Buffer.isBuffer(body)) {\n return body;\n }\n if (body instanceof Uint8Array) {\n return Buffer.from(body);\n }\n if (typeof body === \"string\") {\n return Buffer.from(body, \"base64\");\n }\n return Buffer.alloc(0);\n};\n\ninterface MultipartField {\n name: string;\n value: string;\n}\n\ninterface MultipartFile {\n name: string;\n filename: string;\n data: Buffer;\n}\n\nexport interface MultipartResult {\n fields: MultipartField[];\n files: MultipartFile[];\n}\n\n/* Minimal multipart/form-data parser operating on the raw body buffer. */\nexport const parseMultipart = (buffer: Buffer, boundary: string): MultipartResult => {\n const fields: MultipartField[] = [];\n const files: MultipartFile[] = [];\n const separator = Buffer.from(`--${boundary}`);\n\n let idx = buffer.indexOf(separator);\n while (idx !== -1) {\n const next = buffer.indexOf(separator, idx + separator.length);\n if (next === -1) {\n break;\n }\n\n let part = buffer.subarray(idx + separator.length, next);\n if (part.subarray(0, 2).toString(\"latin1\") === \"\\r\\n\") {\n part = part.subarray(2);\n }\n if (part.subarray(-2).toString(\"latin1\") === \"\\r\\n\") {\n part = part.subarray(0, -2);\n }\n\n const headerEnd = part.indexOf(\"\\r\\n\\r\\n\");\n if (headerEnd !== -1) {\n const rawHeaders = part.subarray(0, headerEnd).toString(\"utf8\");\n const content = part.subarray(headerEnd + 4);\n\n const disposition = /content-disposition:[^\\r\\n]*/i.exec(rawHeaders)?.[0] ?? \"\";\n const name = /name=\"([^\"]*)\"/i.exec(disposition)?.[1];\n const filename = /filename=\"([^\"]*)\"/i.exec(disposition)?.[1];\n\n if (name !== undefined) {\n if (filename !== undefined) {\n files.push({ name, filename, data: content });\n } else {\n fields.push({ name, value: content.toString(\"utf8\") });\n }\n }\n }\n\n idx = next;\n }\n\n return { fields, files };\n};\n\nexport const getBoundary = (contentType: string): string | undefined => {\n const match = /boundary=(?:\"([^\"]+)\"|([^;]+))/i.exec(contentType);\n const value = match?.[1] ?? match?.[2];\n return value?.trim();\n};\n"],"names":["isPathContained","filePath","storagePath","resolved","path","root","json","statusCode","data","JSON","toBuffer","body","Buffer","Uint8Array","parseMultipart","buffer","boundary","fields","files","separator","idx","next","part","headerEnd","rawHeaders","content","disposition","name","filename","undefined","getBoundary","contentType","match","value"],"mappings":";AAGO,MAAMA,kBAAkB,CAACC,UAAkBC;IAC9C,MAAMC,WAAWC,UAAAA,OAAY,CAACH;IAC9B,MAAMI,OAAOD,UAAAA,OAAY,CAACF;IAC1B,OAAOC,aAAaE,QAAQF,SAAS,UAAU,CAACE,OAAOD,UAAAA,GAAQ;AACnE;AAEO,MAAME,OAAO,CAACC,YAAoBC,OAAkC;QACvED;QACA,SAAS;YAAE,gBAAgB;QAAmB;QAC9C,MAAME,KAAK,SAAS,CAACD;IACzB;AAGO,MAAME,WAAW,CAACC;IACrB,IAAIC,OAAO,QAAQ,CAACD,OAChB,OAAOA;IAEX,IAAIA,gBAAgBE,YAChB,OAAOD,OAAO,IAAI,CAACD;IAEvB,IAAI,AAAgB,YAAhB,OAAOA,MACP,OAAOC,OAAO,IAAI,CAACD,MAAM;IAE7B,OAAOC,OAAO,KAAK,CAAC;AACxB;AAmBO,MAAME,iBAAiB,CAACC,QAAgBC;IAC3C,MAAMC,SAA2B,EAAE;IACnC,MAAMC,QAAyB,EAAE;IACjC,MAAMC,YAAYP,OAAO,IAAI,CAAC,CAAC,EAAE,EAAEI,UAAU;IAE7C,IAAII,MAAML,OAAO,OAAO,CAACI;IACzB,MAAOC,AAAQ,OAARA,IAAY;QACf,MAAMC,OAAON,OAAO,OAAO,CAACI,WAAWC,MAAMD,UAAU,MAAM;QAC7D,IAAIE,AAAS,OAATA,MACA;QAGJ,IAAIC,OAAOP,OAAO,QAAQ,CAACK,MAAMD,UAAU,MAAM,EAAEE;QACnD,IAAIC,AAA2C,WAA3CA,KAAK,QAAQ,CAAC,GAAG,GAAG,QAAQ,CAAC,WAC7BA,OAAOA,KAAK,QAAQ,CAAC;QAEzB,IAAIA,AAAyC,WAAzCA,KAAK,QAAQ,CAAC,IAAI,QAAQ,CAAC,WAC3BA,OAAOA,KAAK,QAAQ,CAAC,GAAG;QAG5B,MAAMC,YAAYD,KAAK,OAAO,CAAC;QAC/B,IAAIC,AAAc,OAAdA,WAAkB;YAClB,MAAMC,aAAaF,KAAK,QAAQ,CAAC,GAAGC,WAAW,QAAQ,CAAC;YACxD,MAAME,UAAUH,KAAK,QAAQ,CAACC,YAAY;YAE1C,MAAMG,cAAc,gCAAgC,IAAI,CAACF,aAAa,CAAC,EAAE,IAAI;YAC7E,MAAMG,OAAO,kBAAkB,IAAI,CAACD,cAAc,CAAC,EAAE;YACrD,MAAME,WAAW,sBAAsB,IAAI,CAACF,cAAc,CAAC,EAAE;YAE7D,IAAIC,AAASE,WAATF,MACA,IAAIC,AAAaC,WAAbD,UACAV,MAAM,IAAI,CAAC;gBAAES;gBAAMC;gBAAU,MAAMH;YAAQ;iBAE3CR,OAAO,IAAI,CAAC;gBAAEU;gBAAM,OAAOF,QAAQ,QAAQ,CAAC;YAAQ;QAGhE;QAEAL,MAAMC;IACV;IAEA,OAAO;QAAEJ;QAAQC;IAAM;AAC3B;AAEO,MAAMY,cAAc,CAACC;IACxB,MAAMC,QAAQ,kCAAkC,IAAI,CAACD;IACrD,MAAME,QAAQD,OAAO,CAAC,EAAE,IAAIA,OAAO,CAAC,EAAE;IACtC,OAAOC,OAAO;AAClB"}