@rpcbase/server 0.512.0 → 0.514.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{email-DEw8keax.js → email-DK8uUU4X.js} +5 -2
- package/dist/{email-DEw8keax.js.map → email-DK8uUU4X.js.map} +1 -1
- package/dist/{handler-r-HYO3Oy.js → handler-3gksYOQv.js} +299 -99
- package/dist/{handler-r-HYO3Oy.js.map → handler-3gksYOQv.js.map} +1 -1
- package/dist/{handler-BNrqh1Kb.js → handler-BsauvgjA.js} +58 -19
- package/dist/{handler-BNrqh1Kb.js.map → handler-BsauvgjA.js.map} +1 -1
- package/dist/{handler-Cohj3cz3.js → handler-DY5UEwlw.js} +45 -18
- package/dist/{handler-Cohj3cz3.js.map → handler-DY5UEwlw.js.map} +1 -1
- package/dist/{handler-Bh3a6Br1.js → handler-GZgk5k3c.js} +326 -130
- package/dist/{handler-Bh3a6Br1.js.map → handler-GZgk5k3c.js.map} +1 -1
- package/dist/index.js +293 -155
- package/dist/index.js.map +1 -1
- package/dist/notifications.js +103 -32
- package/dist/notifications.js.map +1 -1
- package/dist/{queryExecutor-DSCpsVI8.js → queryExecutor-Dn62mIDL.js} +47 -33
- package/dist/{queryExecutor-DSCpsVI8.js.map → queryExecutor-Dn62mIDL.js.map} +1 -1
- package/dist/rts/index.js +99 -21
- package/dist/rts/index.js.map +1 -1
- package/dist/{shared-BJomDDWK.js → shared-Dy9x-P7F.js} +10 -7
- package/dist/{shared-BJomDDWK.js.map → shared-Dy9x-P7F.js.map} +1 -1
- package/dist/uploads.js +1 -1
- package/dist/uploads.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { models, getTenantFilesystemDb } from "@rpcbase/db";
|
|
|
2
2
|
import { GridFSBucket, ObjectId } from "mongodb";
|
|
3
3
|
import { JSDOM } from "jsdom";
|
|
4
4
|
import createDOMPurify from "dompurify";
|
|
5
|
-
import { g as getTenantId, a as getModelCtx, b as buildUploadsAbility, c as getUploadSessionAccessQuery, e as ensureUploadIndexes, d as getBucketName, f as getUserId, h as getChunkSizeBytes, i as getSessionTtlMs, j as computeSha256Hex, t as toBufferPayload, n as normalizeSha256Hex, k as getMaxClientUploadBytesPerSecond, l as getRawBodyLimitBytes } from "./shared-
|
|
5
|
+
import { g as getTenantId, a as getModelCtx, b as buildUploadsAbility, c as getUploadSessionAccessQuery, e as ensureUploadIndexes, d as getBucketName, f as getUserId, h as getChunkSizeBytes, i as getSessionTtlMs, j as computeSha256Hex, t as toBufferPayload, n as normalizeSha256Hex, k as getMaxClientUploadBytesPerSecond, l as getRawBodyLimitBytes } from "./shared-Dy9x-P7F.js";
|
|
6
6
|
import { randomBytes } from "node:crypto";
|
|
7
7
|
import { o as object, n as number, b as boolean, s as string, a as array, _ as _enum } from "./schemas-Cjdjgehl.js";
|
|
8
8
|
const MAX_SVG_BYTES = 128 * 1024;
|
|
@@ -16,12 +16,17 @@ const looksLikeSvgText = (text) => {
|
|
|
16
16
|
};
|
|
17
17
|
const looksLikeSvg = (sniff) => looksLikeSvgText(sniff.toString("utf8"));
|
|
18
18
|
const sanitizeSvg = (svg) => DOMPurify.sanitize(svg, {
|
|
19
|
-
USE_PROFILES: {
|
|
19
|
+
USE_PROFILES: {
|
|
20
|
+
svg: true,
|
|
21
|
+
svgFilters: true
|
|
22
|
+
}
|
|
20
23
|
});
|
|
21
24
|
const sanitizeSvgProcessor = {
|
|
22
25
|
id: "sanitize-svg",
|
|
23
26
|
maxBytes: MAX_SVG_BYTES,
|
|
24
|
-
match: ({
|
|
27
|
+
match: ({
|
|
28
|
+
sniff
|
|
29
|
+
}) => looksLikeSvg(sniff),
|
|
25
30
|
process: (data) => {
|
|
26
31
|
if (data.length > MAX_SVG_BYTES) {
|
|
27
32
|
throw new Error("svg_too_large");
|
|
@@ -38,7 +43,10 @@ const sanitizeSvgProcessor = {
|
|
|
38
43
|
if (sanitizedBuffer.length > MAX_SVG_BYTES) {
|
|
39
44
|
throw new Error("svg_too_large");
|
|
40
45
|
}
|
|
41
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
data: sanitizedBuffer,
|
|
48
|
+
mimeType: "image/svg+xml"
|
|
49
|
+
};
|
|
42
50
|
}
|
|
43
51
|
};
|
|
44
52
|
const uploadProcessors = Object.freeze([sanitizeSvgProcessor]);
|
|
@@ -115,53 +123,94 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
115
123
|
const tenantId = getTenantId(ctx);
|
|
116
124
|
if (!tenantId) {
|
|
117
125
|
ctx.res.status(400);
|
|
118
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
error: "tenant_missing"
|
|
129
|
+
};
|
|
119
130
|
}
|
|
120
131
|
const uploadId = String(ctx.req.params?.uploadId ?? "").trim();
|
|
121
132
|
if (!uploadId) {
|
|
122
133
|
ctx.res.status(400);
|
|
123
|
-
return {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: "invalid_upload_id"
|
|
137
|
+
};
|
|
124
138
|
}
|
|
125
139
|
const modelCtx = getModelCtx(ctx, tenantId);
|
|
126
|
-
const [UploadSession, UploadChunk] = await Promise.all([
|
|
127
|
-
models.get("RBUploadSession", modelCtx),
|
|
128
|
-
models.get("RBUploadChunk", modelCtx)
|
|
129
|
-
]);
|
|
140
|
+
const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
|
|
130
141
|
const ability = buildUploadsAbility(ctx, tenantId);
|
|
131
142
|
if (!ability.can("update", "RBUploadSession")) {
|
|
132
143
|
ctx.res.status(401);
|
|
133
|
-
return {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error: "unauthorized"
|
|
147
|
+
};
|
|
134
148
|
}
|
|
135
|
-
const existing = await UploadSession.findOne({
|
|
149
|
+
const existing = await UploadSession.findOne({
|
|
150
|
+
$and: [{
|
|
151
|
+
_id: uploadId
|
|
152
|
+
}, getUploadSessionAccessQuery(ability, "read")]
|
|
153
|
+
}).lean();
|
|
136
154
|
if (!existing) {
|
|
137
155
|
ctx.res.status(404);
|
|
138
|
-
return {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
error: "not_found"
|
|
159
|
+
};
|
|
139
160
|
}
|
|
140
161
|
if (existing.status === "done" && existing.fileId) {
|
|
141
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
fileId: existing.fileId
|
|
165
|
+
};
|
|
142
166
|
}
|
|
143
|
-
const locked = await UploadSession.findOneAndUpdate(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
167
|
+
const locked = await UploadSession.findOneAndUpdate({
|
|
168
|
+
$and: [{
|
|
169
|
+
_id: uploadId
|
|
170
|
+
}, {
|
|
171
|
+
status: "uploading"
|
|
172
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
173
|
+
}, {
|
|
174
|
+
$set: {
|
|
175
|
+
status: "assembling"
|
|
176
|
+
},
|
|
177
|
+
$unset: {
|
|
178
|
+
error: ""
|
|
179
|
+
}
|
|
180
|
+
}, {
|
|
181
|
+
new: true
|
|
182
|
+
}).lean();
|
|
148
183
|
if (!locked) {
|
|
149
184
|
ctx.res.status(409);
|
|
150
|
-
return {
|
|
185
|
+
return {
|
|
186
|
+
ok: false,
|
|
187
|
+
error: "not_uploading"
|
|
188
|
+
};
|
|
151
189
|
}
|
|
152
190
|
await ensureUploadIndexes(UploadSession, UploadChunk);
|
|
153
191
|
const fsDb = await getTenantFilesystemDb(tenantId);
|
|
154
192
|
const nativeDb = fsDb.db;
|
|
155
193
|
if (!nativeDb) {
|
|
156
|
-
await UploadSession.updateOne(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
194
|
+
await UploadSession.updateOne({
|
|
195
|
+
$and: [{
|
|
196
|
+
_id: uploadId
|
|
197
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
198
|
+
}, {
|
|
199
|
+
$set: {
|
|
200
|
+
status: "error",
|
|
201
|
+
error: "filesystem_db_unavailable"
|
|
202
|
+
}
|
|
203
|
+
});
|
|
160
204
|
ctx.res.status(500);
|
|
161
|
-
return {
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
error: "assembly_failed"
|
|
208
|
+
};
|
|
162
209
|
}
|
|
163
210
|
const bucketName = getBucketName();
|
|
164
|
-
const bucket = new GridFSBucket(nativeDb, {
|
|
211
|
+
const bucket = new GridFSBucket(nativeDb, {
|
|
212
|
+
bucketName
|
|
213
|
+
});
|
|
165
214
|
const lockedUserId = typeof locked.userId === "string" ? locked.userId : void 0;
|
|
166
215
|
const maxProcessorBytes = getMaxUploadProcessorBytes();
|
|
167
216
|
const shouldBufferForProcessing = locked.totalSize <= maxProcessorBytes;
|
|
@@ -172,7 +221,11 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
172
221
|
if (!shouldBufferForProcessing && declaredSvg) {
|
|
173
222
|
throw new Error("svg_too_large");
|
|
174
223
|
}
|
|
175
|
-
const cursor = UploadChunk.find({
|
|
224
|
+
const cursor = UploadChunk.find({
|
|
225
|
+
uploadId
|
|
226
|
+
}).sort({
|
|
227
|
+
index: 1
|
|
228
|
+
}).cursor();
|
|
176
229
|
let expectedIndex = 0;
|
|
177
230
|
const chunks = [];
|
|
178
231
|
let bufferedBytes = 0;
|
|
@@ -214,9 +267,15 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
214
267
|
tenantId,
|
|
215
268
|
mimeType: locked.mimeType,
|
|
216
269
|
totalSize: locked.totalSize,
|
|
217
|
-
...typeof locked.isPublic === "boolean" ? {
|
|
218
|
-
|
|
219
|
-
|
|
270
|
+
...typeof locked.isPublic === "boolean" ? {
|
|
271
|
+
isPublic: locked.isPublic
|
|
272
|
+
} : {},
|
|
273
|
+
...typeof locked.ownerKeyHash === "string" ? {
|
|
274
|
+
ownerKeyHash: locked.ownerKeyHash
|
|
275
|
+
} : {},
|
|
276
|
+
...lockedUserId ? {
|
|
277
|
+
userId: lockedUserId
|
|
278
|
+
} : {}
|
|
220
279
|
}
|
|
221
280
|
});
|
|
222
281
|
for (const pending of pendingChunks) {
|
|
@@ -240,7 +299,11 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
240
299
|
}
|
|
241
300
|
if (shouldBufferForProcessing) {
|
|
242
301
|
const assembled = Buffer.concat(chunks, bufferedBytes);
|
|
243
|
-
const {
|
|
302
|
+
const {
|
|
303
|
+
data: processed,
|
|
304
|
+
mimeType: processedMimeType,
|
|
305
|
+
applied
|
|
306
|
+
} = await applyUploadProcessors(assembled, {
|
|
244
307
|
filename: locked.filename,
|
|
245
308
|
clientMimeType: locked.mimeType
|
|
246
309
|
});
|
|
@@ -250,10 +313,19 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
250
313
|
tenantId,
|
|
251
314
|
mimeType: processedMimeType,
|
|
252
315
|
totalSize: locked.totalSize,
|
|
253
|
-
...applied.length ? {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
316
|
+
...applied.length ? {
|
|
317
|
+
processors: applied,
|
|
318
|
+
processedSize: processed.length
|
|
319
|
+
} : {},
|
|
320
|
+
...typeof locked.isPublic === "boolean" ? {
|
|
321
|
+
isPublic: locked.isPublic
|
|
322
|
+
} : {},
|
|
323
|
+
...typeof locked.ownerKeyHash === "string" ? {
|
|
324
|
+
ownerKeyHash: locked.ownerKeyHash
|
|
325
|
+
} : {},
|
|
326
|
+
...lockedUserId ? {
|
|
327
|
+
userId: lockedUserId
|
|
328
|
+
} : {}
|
|
257
329
|
}
|
|
258
330
|
});
|
|
259
331
|
const finished = waitForStreamFinished(uploadStream);
|
|
@@ -277,9 +349,15 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
277
349
|
tenantId,
|
|
278
350
|
mimeType: locked.mimeType,
|
|
279
351
|
totalSize: locked.totalSize,
|
|
280
|
-
...typeof locked.isPublic === "boolean" ? {
|
|
281
|
-
|
|
282
|
-
|
|
352
|
+
...typeof locked.isPublic === "boolean" ? {
|
|
353
|
+
isPublic: locked.isPublic
|
|
354
|
+
} : {},
|
|
355
|
+
...typeof locked.ownerKeyHash === "string" ? {
|
|
356
|
+
ownerKeyHash: locked.ownerKeyHash
|
|
357
|
+
} : {},
|
|
358
|
+
...lockedUserId ? {
|
|
359
|
+
userId: lockedUserId
|
|
360
|
+
} : {}
|
|
283
361
|
}
|
|
284
362
|
});
|
|
285
363
|
for (const pending of pendingChunks) {
|
|
@@ -295,80 +373,146 @@ const completeUpload = async (_payload, ctx) => {
|
|
|
295
373
|
if (!fileId) {
|
|
296
374
|
throw new Error("missing_file_id");
|
|
297
375
|
}
|
|
298
|
-
await UploadSession.updateOne(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
376
|
+
await UploadSession.updateOne({
|
|
377
|
+
$and: [{
|
|
378
|
+
_id: uploadId
|
|
379
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
380
|
+
}, {
|
|
381
|
+
$set: {
|
|
382
|
+
status: "done",
|
|
383
|
+
fileId
|
|
384
|
+
},
|
|
385
|
+
$unset: {
|
|
386
|
+
error: ""
|
|
387
|
+
}
|
|
388
|
+
});
|
|
302
389
|
try {
|
|
303
|
-
await UploadChunk.deleteMany({
|
|
390
|
+
await UploadChunk.deleteMany({
|
|
391
|
+
uploadId
|
|
392
|
+
});
|
|
304
393
|
} catch {
|
|
305
394
|
}
|
|
306
|
-
return {
|
|
395
|
+
return {
|
|
396
|
+
ok: true,
|
|
397
|
+
fileId
|
|
398
|
+
};
|
|
307
399
|
} catch (error) {
|
|
308
400
|
const message = error instanceof Error ? error.message : String(error);
|
|
309
401
|
await abortUploadStream(uploadStream);
|
|
310
402
|
if (message === "missing_chunks") {
|
|
311
|
-
await UploadSession.updateOne(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
403
|
+
await UploadSession.updateOne({
|
|
404
|
+
$and: [{
|
|
405
|
+
_id: uploadId
|
|
406
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
407
|
+
}, {
|
|
408
|
+
$set: {
|
|
409
|
+
status: "uploading"
|
|
410
|
+
}
|
|
411
|
+
});
|
|
315
412
|
ctx.res.status(409);
|
|
316
|
-
return {
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
error: "missing_chunks"
|
|
416
|
+
};
|
|
317
417
|
}
|
|
318
418
|
if (message === "svg_too_large") {
|
|
319
|
-
await UploadSession.updateOne(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
419
|
+
await UploadSession.updateOne({
|
|
420
|
+
$and: [{
|
|
421
|
+
_id: uploadId
|
|
422
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
423
|
+
}, {
|
|
424
|
+
$set: {
|
|
425
|
+
status: "error",
|
|
426
|
+
error: message
|
|
427
|
+
}
|
|
428
|
+
});
|
|
323
429
|
ctx.res.status(413);
|
|
324
|
-
return {
|
|
430
|
+
return {
|
|
431
|
+
ok: false,
|
|
432
|
+
error: message
|
|
433
|
+
};
|
|
325
434
|
}
|
|
326
435
|
if (message === "svg_invalid" || message === "svg_sanitize_failed") {
|
|
327
|
-
await UploadSession.updateOne(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
436
|
+
await UploadSession.updateOne({
|
|
437
|
+
$and: [{
|
|
438
|
+
_id: uploadId
|
|
439
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
440
|
+
}, {
|
|
441
|
+
$set: {
|
|
442
|
+
status: "error",
|
|
443
|
+
error: message
|
|
444
|
+
}
|
|
445
|
+
});
|
|
331
446
|
ctx.res.status(400);
|
|
332
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
error: message
|
|
450
|
+
};
|
|
333
451
|
}
|
|
334
|
-
await UploadSession.updateOne(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
452
|
+
await UploadSession.updateOne({
|
|
453
|
+
$and: [{
|
|
454
|
+
_id: uploadId
|
|
455
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
456
|
+
}, {
|
|
457
|
+
$set: {
|
|
458
|
+
status: "error",
|
|
459
|
+
error: message
|
|
460
|
+
}
|
|
461
|
+
});
|
|
338
462
|
ctx.res.status(500);
|
|
339
|
-
return {
|
|
463
|
+
return {
|
|
464
|
+
ok: false,
|
|
465
|
+
error: "assembly_failed"
|
|
466
|
+
};
|
|
340
467
|
}
|
|
341
468
|
};
|
|
342
469
|
const getStatus = async (_payload, ctx) => {
|
|
343
470
|
const tenantId = getTenantId(ctx);
|
|
344
471
|
if (!tenantId) {
|
|
345
472
|
ctx.res.status(400);
|
|
346
|
-
return {
|
|
473
|
+
return {
|
|
474
|
+
ok: false,
|
|
475
|
+
error: "tenant_missing"
|
|
476
|
+
};
|
|
347
477
|
}
|
|
348
478
|
const uploadId = String(ctx.req.params?.uploadId ?? "").trim();
|
|
349
479
|
if (!uploadId) {
|
|
350
480
|
ctx.res.status(400);
|
|
351
|
-
return {
|
|
481
|
+
return {
|
|
482
|
+
ok: false,
|
|
483
|
+
error: "invalid_upload_id"
|
|
484
|
+
};
|
|
352
485
|
}
|
|
353
486
|
const modelCtx = getModelCtx(ctx, tenantId);
|
|
354
|
-
const [UploadSession, UploadChunk] = await Promise.all([
|
|
355
|
-
models.get("RBUploadSession", modelCtx),
|
|
356
|
-
models.get("RBUploadChunk", modelCtx)
|
|
357
|
-
]);
|
|
487
|
+
const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
|
|
358
488
|
const ability = buildUploadsAbility(ctx, tenantId);
|
|
359
489
|
if (!ability.can("read", "RBUploadSession")) {
|
|
360
490
|
ctx.res.status(401);
|
|
361
|
-
return {
|
|
491
|
+
return {
|
|
492
|
+
ok: false,
|
|
493
|
+
error: "unauthorized"
|
|
494
|
+
};
|
|
362
495
|
}
|
|
363
|
-
const session = await UploadSession.findOne({
|
|
496
|
+
const session = await UploadSession.findOne({
|
|
497
|
+
$and: [{
|
|
498
|
+
_id: uploadId
|
|
499
|
+
}, getUploadSessionAccessQuery(ability, "read")]
|
|
500
|
+
}).lean();
|
|
364
501
|
if (!session) {
|
|
365
502
|
ctx.res.status(404);
|
|
366
|
-
return {
|
|
503
|
+
return {
|
|
504
|
+
ok: false,
|
|
505
|
+
error: "not_found"
|
|
506
|
+
};
|
|
367
507
|
}
|
|
368
|
-
const receivedDocs = await UploadChunk.find(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
508
|
+
const receivedDocs = await UploadChunk.find({
|
|
509
|
+
uploadId
|
|
510
|
+
}, {
|
|
511
|
+
index: 1,
|
|
512
|
+
_id: 0
|
|
513
|
+
}).sort({
|
|
514
|
+
index: 1
|
|
515
|
+
}).lean();
|
|
372
516
|
const received = receivedDocs.map((doc) => typeof doc.index === "number" ? doc.index : -1).filter((n) => Number.isInteger(n) && n >= 0);
|
|
373
517
|
return {
|
|
374
518
|
ok: true,
|
|
@@ -376,7 +520,9 @@ const getStatus = async (_payload, ctx) => {
|
|
|
376
520
|
chunkSize: session.chunkSize,
|
|
377
521
|
chunksTotal: session.chunksTotal,
|
|
378
522
|
received,
|
|
379
|
-
...session.fileId ? {
|
|
523
|
+
...session.fileId ? {
|
|
524
|
+
fileId: session.fileId
|
|
525
|
+
} : {}
|
|
380
526
|
};
|
|
381
527
|
};
|
|
382
528
|
const InitRoute = "/api/rb/file-uploads";
|
|
@@ -415,22 +561,30 @@ const initUpload = async (payload, ctx) => {
|
|
|
415
561
|
const tenantId = getTenantId(ctx);
|
|
416
562
|
if (!tenantId) {
|
|
417
563
|
ctx.res.status(400);
|
|
418
|
-
return {
|
|
564
|
+
return {
|
|
565
|
+
ok: false,
|
|
566
|
+
error: "tenant_missing"
|
|
567
|
+
};
|
|
419
568
|
}
|
|
420
569
|
const userId = getUserId(ctx);
|
|
421
570
|
const parsed = initRequestSchema.safeParse(payload ?? {});
|
|
422
571
|
if (!parsed.success) {
|
|
423
572
|
ctx.res.status(400);
|
|
424
|
-
return {
|
|
573
|
+
return {
|
|
574
|
+
ok: false,
|
|
575
|
+
error: "invalid_payload"
|
|
576
|
+
};
|
|
425
577
|
}
|
|
426
578
|
const chunkSize = getChunkSizeBytes();
|
|
427
|
-
const {
|
|
579
|
+
const {
|
|
580
|
+
filename,
|
|
581
|
+
mimeType,
|
|
582
|
+
totalSize,
|
|
583
|
+
isPublic
|
|
584
|
+
} = parsed.data;
|
|
428
585
|
const chunksTotal = Math.ceil(totalSize / chunkSize);
|
|
429
586
|
const modelCtx = getModelCtx(ctx, tenantId);
|
|
430
|
-
const [UploadSession, UploadChunk] = await Promise.all([
|
|
431
|
-
models.get("RBUploadSession", modelCtx),
|
|
432
|
-
models.get("RBUploadChunk", modelCtx)
|
|
433
|
-
]);
|
|
587
|
+
const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
|
|
434
588
|
await ensureUploadIndexes(UploadSession, UploadChunk);
|
|
435
589
|
const uploadId = new ObjectId().toString();
|
|
436
590
|
const now = Date.now();
|
|
@@ -439,11 +593,17 @@ const initUpload = async (payload, ctx) => {
|
|
|
439
593
|
const ownerKeyHash = uploadKey ? computeSha256Hex(Buffer.from(uploadKey)) : void 0;
|
|
440
594
|
await UploadSession.create({
|
|
441
595
|
_id: uploadId,
|
|
442
|
-
...userId ? {
|
|
443
|
-
|
|
596
|
+
...userId ? {
|
|
597
|
+
userId
|
|
598
|
+
} : {},
|
|
599
|
+
...ownerKeyHash ? {
|
|
600
|
+
ownerKeyHash
|
|
601
|
+
} : {},
|
|
444
602
|
filename,
|
|
445
603
|
mimeType,
|
|
446
|
-
...typeof isPublic === "boolean" ? {
|
|
604
|
+
...typeof isPublic === "boolean" ? {
|
|
605
|
+
isPublic
|
|
606
|
+
} : {},
|
|
447
607
|
totalSize,
|
|
448
608
|
chunkSize,
|
|
449
609
|
chunksTotal,
|
|
@@ -456,58 +616,88 @@ const initUpload = async (payload, ctx) => {
|
|
|
456
616
|
uploadId,
|
|
457
617
|
chunkSize,
|
|
458
618
|
chunksTotal,
|
|
459
|
-
...uploadKey ? {
|
|
619
|
+
...uploadKey ? {
|
|
620
|
+
uploadKey
|
|
621
|
+
} : {}
|
|
460
622
|
};
|
|
461
623
|
};
|
|
462
624
|
const uploadChunk = async (payload, ctx) => {
|
|
463
625
|
const tenantId = getTenantId(ctx);
|
|
464
626
|
if (!tenantId) {
|
|
465
627
|
ctx.res.status(400);
|
|
466
|
-
return {
|
|
628
|
+
return {
|
|
629
|
+
ok: false,
|
|
630
|
+
error: "tenant_missing"
|
|
631
|
+
};
|
|
467
632
|
}
|
|
468
633
|
const uploadId = String(ctx.req.params?.uploadId ?? "").trim();
|
|
469
634
|
const indexRaw = String(ctx.req.params?.index ?? "").trim();
|
|
470
635
|
const index = Number(indexRaw);
|
|
471
636
|
if (!uploadId || !Number.isInteger(index) || index < 0) {
|
|
472
637
|
ctx.res.status(400);
|
|
473
|
-
return {
|
|
638
|
+
return {
|
|
639
|
+
ok: false,
|
|
640
|
+
error: "invalid_chunk_ref"
|
|
641
|
+
};
|
|
474
642
|
}
|
|
475
643
|
const modelCtx = getModelCtx(ctx, tenantId);
|
|
476
|
-
const [UploadSession, UploadChunk] = await Promise.all([
|
|
477
|
-
models.get("RBUploadSession", modelCtx),
|
|
478
|
-
models.get("RBUploadChunk", modelCtx)
|
|
479
|
-
]);
|
|
644
|
+
const [UploadSession, UploadChunk] = await Promise.all([models.get("RBUploadSession", modelCtx), models.get("RBUploadChunk", modelCtx)]);
|
|
480
645
|
const ability = buildUploadsAbility(ctx, tenantId);
|
|
481
646
|
if (!ability.can("update", "RBUploadSession")) {
|
|
482
647
|
ctx.res.status(401);
|
|
483
|
-
return {
|
|
648
|
+
return {
|
|
649
|
+
ok: false,
|
|
650
|
+
error: "unauthorized"
|
|
651
|
+
};
|
|
484
652
|
}
|
|
485
|
-
const session = await UploadSession.findOne({
|
|
653
|
+
const session = await UploadSession.findOne({
|
|
654
|
+
$and: [{
|
|
655
|
+
_id: uploadId
|
|
656
|
+
}, getUploadSessionAccessQuery(ability, "update")]
|
|
657
|
+
}).lean();
|
|
486
658
|
if (!session) {
|
|
487
659
|
ctx.res.status(404);
|
|
488
|
-
return {
|
|
660
|
+
return {
|
|
661
|
+
ok: false,
|
|
662
|
+
error: "not_found"
|
|
663
|
+
};
|
|
489
664
|
}
|
|
490
665
|
if (session.status !== "uploading") {
|
|
491
666
|
ctx.res.status(409);
|
|
492
|
-
return {
|
|
667
|
+
return {
|
|
668
|
+
ok: false,
|
|
669
|
+
error: "not_uploading"
|
|
670
|
+
};
|
|
493
671
|
}
|
|
494
672
|
if (index >= session.chunksTotal) {
|
|
495
673
|
ctx.res.status(400);
|
|
496
|
-
return {
|
|
674
|
+
return {
|
|
675
|
+
ok: false,
|
|
676
|
+
error: "index_out_of_range"
|
|
677
|
+
};
|
|
497
678
|
}
|
|
498
679
|
const data = toBufferPayload(payload);
|
|
499
680
|
if (!data) {
|
|
500
681
|
ctx.res.status(400);
|
|
501
|
-
return {
|
|
682
|
+
return {
|
|
683
|
+
ok: false,
|
|
684
|
+
error: "invalid_body"
|
|
685
|
+
};
|
|
502
686
|
}
|
|
503
687
|
const expectedSize = index === session.chunksTotal - 1 ? session.totalSize - session.chunkSize * (session.chunksTotal - 1) : session.chunkSize;
|
|
504
688
|
if (data.length > expectedSize) {
|
|
505
689
|
ctx.res.status(413);
|
|
506
|
-
return {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
error: "chunk_too_large"
|
|
693
|
+
};
|
|
507
694
|
}
|
|
508
695
|
if (data.length !== expectedSize) {
|
|
509
696
|
ctx.res.status(400);
|
|
510
|
-
return {
|
|
697
|
+
return {
|
|
698
|
+
ok: false,
|
|
699
|
+
error: "invalid_chunk_size"
|
|
700
|
+
};
|
|
511
701
|
}
|
|
512
702
|
const checksumHeader = ctx.req.get("X-Chunk-SHA256");
|
|
513
703
|
const sha256 = checksumHeader ? computeSha256Hex(data) : void 0;
|
|
@@ -515,29 +705,35 @@ const uploadChunk = async (payload, ctx) => {
|
|
|
515
705
|
const expectedSha256 = normalizeSha256Hex(checksumHeader);
|
|
516
706
|
if (sha256 !== expectedSha256) {
|
|
517
707
|
ctx.res.status(400);
|
|
518
|
-
return {
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
error: "checksum_mismatch"
|
|
711
|
+
};
|
|
519
712
|
}
|
|
520
713
|
}
|
|
521
714
|
await ensureUploadIndexes(UploadSession, UploadChunk);
|
|
522
|
-
await UploadChunk.updateOne(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
$setOnInsert: {
|
|
534
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
535
|
-
}
|
|
715
|
+
await UploadChunk.updateOne({
|
|
716
|
+
uploadId,
|
|
717
|
+
index
|
|
718
|
+
}, {
|
|
719
|
+
$set: {
|
|
720
|
+
uploadId,
|
|
721
|
+
index,
|
|
722
|
+
data,
|
|
723
|
+
size: data.length,
|
|
724
|
+
sha256,
|
|
725
|
+
expiresAt: session.expiresAt
|
|
536
726
|
},
|
|
537
|
-
|
|
538
|
-
|
|
727
|
+
$setOnInsert: {
|
|
728
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
729
|
+
}
|
|
730
|
+
}, {
|
|
731
|
+
upsert: true
|
|
732
|
+
});
|
|
539
733
|
ctx.res.status(204);
|
|
540
|
-
return {
|
|
734
|
+
return {
|
|
735
|
+
ok: true
|
|
736
|
+
};
|
|
541
737
|
};
|
|
542
738
|
const rawBodyParser = ({
|
|
543
739
|
limitBytes,
|
|
@@ -584,7 +780,10 @@ const rawBodyParser = ({
|
|
|
584
780
|
done = true;
|
|
585
781
|
cleanup();
|
|
586
782
|
req.destroy();
|
|
587
|
-
res.status(413).json({
|
|
783
|
+
res.status(413).json({
|
|
784
|
+
ok: false,
|
|
785
|
+
error: "chunk_too_large"
|
|
786
|
+
});
|
|
588
787
|
return;
|
|
589
788
|
}
|
|
590
789
|
chunks.push(buffer);
|
|
@@ -666,13 +865,10 @@ const consumeRateBudget = (state, bytes, rateBytesPerSecond, now) => {
|
|
|
666
865
|
};
|
|
667
866
|
const handler = (api) => {
|
|
668
867
|
const chunkSizeBytes = getChunkSizeBytes();
|
|
669
|
-
api.use(
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
maxClientBytesPerSecond: getMaxClientUploadBytesPerSecond()
|
|
674
|
-
})
|
|
675
|
-
);
|
|
868
|
+
api.use(InitRoute, rawBodyParser({
|
|
869
|
+
limitBytes: getRawBodyLimitBytes(chunkSizeBytes),
|
|
870
|
+
maxClientBytesPerSecond: getMaxClientUploadBytesPerSecond()
|
|
871
|
+
}));
|
|
676
872
|
api.post(InitRoute, initUpload);
|
|
677
873
|
api.put(ChunkRoute, uploadChunk);
|
|
678
874
|
api.get(StatusRoute, getStatus);
|
|
@@ -681,4 +877,4 @@ const handler = (api) => {
|
|
|
681
877
|
export {
|
|
682
878
|
handler as default
|
|
683
879
|
};
|
|
684
|
-
//# sourceMappingURL=handler-
|
|
880
|
+
//# sourceMappingURL=handler-GZgk5k3c.js.map
|