@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.
- package/FileManagerServerFeature.d.ts +5 -0
- package/FileManagerServerFeature.js +41 -0
- package/FileManagerServerFeature.js.map +1 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/assetDelivery/LocalAssetResolver.d.ts +15 -0
- package/assetDelivery/LocalAssetResolver.js +40 -0
- package/assetDelivery/LocalAssetResolver.js.map +1 -0
- package/assetDelivery/LocalContentsReader.d.ts +7 -0
- package/assetDelivery/LocalContentsReader.js +18 -0
- package/assetDelivery/LocalContentsReader.js.map +1 -0
- package/assetDelivery/LocalOutputStrategy.d.ts +12 -0
- package/assetDelivery/LocalOutputStrategy.js +21 -0
- package/assetDelivery/LocalOutputStrategy.js.map +1 -0
- package/assetDelivery/LocalSharpTransform.d.ts +24 -0
- package/assetDelivery/LocalSharpTransform.js +161 -0
- package/assetDelivery/LocalSharpTransform.js.map +1 -0
- package/assetDelivery/abstractions.d.ts +8 -0
- package/assetDelivery/abstractions.js +6 -0
- package/assetDelivery/abstractions.js.map +1 -0
- package/assetDelivery/assetDeliveryConfig.d.ts +2 -0
- package/assetDelivery/assetDeliveryConfig.js +15 -0
- package/assetDelivery/assetDeliveryConfig.js.map +1 -0
- package/assetDelivery/createAssetDelivery.d.ts +3 -0
- package/assetDelivery/createAssetDelivery.js +7 -0
- package/assetDelivery/createAssetDelivery.js.map +1 -0
- package/assetDelivery/feature.d.ts +5 -0
- package/assetDelivery/feature.js +38 -0
- package/assetDelivery/feature.js.map +1 -0
- package/assetDelivery/types.d.ts +8 -0
- package/assetDelivery/types.js +0 -0
- package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.d.ts +21 -0
- package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.js +62 -0
- package/features/CleanupStaleMultipartUploads/CleanupStaleMultipartUploadsTask.js.map +1 -0
- package/features/CleanupStaleMultipartUploads/feature.d.ts +4 -0
- package/features/CleanupStaleMultipartUploads/feature.js +11 -0
- package/features/CleanupStaleMultipartUploads/feature.js.map +1 -0
- package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.d.ts +13 -0
- package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js +62 -0
- package/features/CompleteMultiPartUpload/CompleteMultiPartUploadUseCase.js.map +1 -0
- package/features/CompleteMultiPartUpload/feature.d.ts +4 -0
- package/features/CompleteMultiPartUpload/feature.js +11 -0
- package/features/CompleteMultiPartUpload/feature.js.map +1 -0
- package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.d.ts +14 -0
- package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js +58 -0
- package/features/CreateMultiPartUpload/CreateMultiPartUploadUseCase.js.map +1 -0
- package/features/CreateMultiPartUpload/feature.d.ts +4 -0
- package/features/CreateMultiPartUpload/feature.js +11 -0
- package/features/CreateMultiPartUpload/feature.js.map +1 -0
- package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.d.ts +15 -0
- package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.js +34 -0
- package/features/DeleteFileFromDisk/DeleteFileFromDiskHandler.js.map +1 -0
- package/features/DeleteFileFromDisk/feature.d.ts +4 -0
- package/features/DeleteFileFromDisk/feature.js +11 -0
- package/features/DeleteFileFromDisk/feature.js.map +1 -0
- package/features/ExtractMetadata/ExtractMetadataHandler.d.ts +1 -0
- package/features/ExtractMetadata/ExtractMetadataHandler.js +1 -0
- package/features/ExtractMetadata/ExtractMetadataTask.d.ts +24 -0
- package/features/ExtractMetadata/ExtractMetadataTask.js +84 -0
- package/features/ExtractMetadata/ExtractMetadataTask.js.map +1 -0
- package/features/ExtractMetadata/feature.d.ts +4 -0
- package/features/ExtractMetadata/feature.js +13 -0
- package/features/ExtractMetadata/feature.js.map +1 -0
- package/features/FileManagerServerConfig/FileManagerServerConfig.d.ts +12 -0
- package/features/FileManagerServerConfig/FileManagerServerConfig.js +28 -0
- package/features/FileManagerServerConfig/FileManagerServerConfig.js.map +1 -0
- package/features/FileManagerServerConfig/abstractions.d.ts +10 -0
- package/features/FileManagerServerConfig/abstractions.js +5 -0
- package/features/FileManagerServerConfig/abstractions.js.map +1 -0
- package/features/FileManagerServerConfig/feature.d.ts +4 -0
- package/features/FileManagerServerConfig/feature.js +11 -0
- package/features/FileManagerServerConfig/feature.js.map +1 -0
- package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.d.ts +8 -0
- package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.js +11 -0
- package/features/FlushCache/FlushCacheOnFileBeforeUpdateHandler.js.map +1 -0
- package/features/FlushCache/FlushCacheOnFileDeleteHandler.d.ts +8 -0
- package/features/FlushCache/FlushCacheOnFileDeleteHandler.js +11 -0
- package/features/FlushCache/FlushCacheOnFileDeleteHandler.js.map +1 -0
- package/features/FlushCache/feature.d.ts +4 -0
- package/features/FlushCache/feature.js +13 -0
- package/features/FlushCache/feature.js.map +1 -0
- package/features/GetFileContentsById/GetFileContentsByIdUseCase.d.ts +15 -0
- package/features/GetFileContentsById/GetFileContentsByIdUseCase.js +38 -0
- package/features/GetFileContentsById/GetFileContentsByIdUseCase.js.map +1 -0
- package/features/GetFileContentsById/feature.d.ts +4 -0
- package/features/GetFileContentsById/feature.js +11 -0
- package/features/GetFileContentsById/feature.js.map +1 -0
- package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.d.ts +15 -0
- package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.js +72 -0
- package/features/GetFileContentsByKey/GetFileContentsByKeyUseCase.js.map +1 -0
- package/features/GetFileContentsByKey/feature.d.ts +4 -0
- package/features/GetFileContentsByKey/feature.js +11 -0
- package/features/GetFileContentsByKey/feature.js.map +1 -0
- package/features/GetUploadPayload/GetUploadPayloadUseCase.d.ts +16 -0
- package/features/GetUploadPayload/GetUploadPayloadUseCase.js +57 -0
- package/features/GetUploadPayload/GetUploadPayloadUseCase.js.map +1 -0
- package/features/GetUploadPayload/feature.d.ts +4 -0
- package/features/GetUploadPayload/feature.js +11 -0
- package/features/GetUploadPayload/feature.js.map +1 -0
- package/graphql/ServerGraphQLSchema.d.ts +15 -0
- package/graphql/ServerGraphQLSchema.js +221 -0
- package/graphql/ServerGraphQLSchema.js.map +1 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/package.json +44 -0
- package/routes/UploadPartRoute/UploadPartRoute.d.ts +14 -0
- package/routes/UploadPartRoute/UploadPartRoute.js +66 -0
- package/routes/UploadPartRoute/UploadPartRoute.js.map +1 -0
- package/routes/UploadPartRoute/feature.d.ts +4 -0
- package/routes/UploadPartRoute/feature.js +11 -0
- package/routes/UploadPartRoute/feature.js.map +1 -0
- package/routes/UploadSingleFileRoute/UploadSingleFileRoute.d.ts +14 -0
- package/routes/UploadSingleFileRoute/UploadSingleFileRoute.js +70 -0
- package/routes/UploadSingleFileRoute/UploadSingleFileRoute.js.map +1 -0
- package/routes/UploadSingleFileRoute/feature.d.ts +4 -0
- package/routes/UploadSingleFileRoute/feature.js +11 -0
- package/routes/UploadSingleFileRoute/feature.js.map +1 -0
- package/routes/utils.d.ts +20 -0
- package/routes/utils.js +62 -0
- package/routes/utils.js.map +1 -0
- package/types.d.ts +10 -0
- package/types.js +1 -0
- package/utils/uploadToken.d.ts +3 -0
- package/utils/uploadToken.js +22 -0
- 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
package/index.js
ADDED
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,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,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 {};
|
package/routes/utils.js
ADDED
|
@@ -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"}
|