@adminforth/upload 2.9.0 → 2.10.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/index.js +98 -2
- package/index.ts +126 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,9 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthPlugin, Filters, suggestIfTypo } from "adminforth";
|
|
10
|
+
import { AdminForthPlugin, Filters, suggestIfTypo, RateLimiter } from "adminforth";
|
|
11
11
|
import { Readable } from "stream";
|
|
12
|
-
import { RateLimiter } from "adminforth";
|
|
13
12
|
import { randomUUID } from "crypto";
|
|
14
13
|
import { interpretResource } from 'adminforth';
|
|
15
14
|
import { ActionCheckSource } from 'adminforth';
|
|
@@ -483,4 +482,101 @@ export default class UploadPlugin extends AdminForthPlugin {
|
|
|
483
482
|
}),
|
|
484
483
|
});
|
|
485
484
|
}
|
|
485
|
+
uploadFromBuffer(_a) {
|
|
486
|
+
return __awaiter(this, arguments, void 0, function* ({ filename, contentType, buffer, adminUser, extra, }) {
|
|
487
|
+
var _b;
|
|
488
|
+
if (!filename || !contentType || !buffer) {
|
|
489
|
+
throw new Error('filename, contentType and buffer are required');
|
|
490
|
+
}
|
|
491
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
492
|
+
if (lastDotIndex === -1) {
|
|
493
|
+
throw new Error('filename must contain an extension');
|
|
494
|
+
}
|
|
495
|
+
const originalExtension = filename.substring(lastDotIndex + 1).toLowerCase();
|
|
496
|
+
const originalFilename = filename.substring(0, lastDotIndex);
|
|
497
|
+
if (this.options.allowedFileExtensions && !this.options.allowedFileExtensions.includes(originalExtension)) {
|
|
498
|
+
throw new Error(`File extension "${originalExtension}" is not allowed, allowed extensions are: ${this.options.allowedFileExtensions.join(', ')}`);
|
|
499
|
+
}
|
|
500
|
+
let nodeBuffer;
|
|
501
|
+
if (Buffer.isBuffer(buffer)) {
|
|
502
|
+
nodeBuffer = buffer;
|
|
503
|
+
}
|
|
504
|
+
else if (buffer instanceof ArrayBuffer) {
|
|
505
|
+
nodeBuffer = Buffer.from(buffer);
|
|
506
|
+
}
|
|
507
|
+
else if (ArrayBuffer.isView(buffer)) {
|
|
508
|
+
nodeBuffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
throw new Error('Unsupported buffer type');
|
|
512
|
+
}
|
|
513
|
+
const size = nodeBuffer.byteLength;
|
|
514
|
+
if (this.options.maxFileSize && size > this.options.maxFileSize) {
|
|
515
|
+
throw new Error(`File size ${size} is too large. Maximum allowed size is ${this.options.maxFileSize}`);
|
|
516
|
+
}
|
|
517
|
+
const filePath = this.options.filePath({
|
|
518
|
+
originalFilename,
|
|
519
|
+
originalExtension,
|
|
520
|
+
contentType,
|
|
521
|
+
record: undefined,
|
|
522
|
+
});
|
|
523
|
+
if (filePath.startsWith('/')) {
|
|
524
|
+
throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
|
|
525
|
+
}
|
|
526
|
+
const { uploadUrl, uploadExtraParams } = yield this.options.storageAdapter.getUploadSignedUrl(filePath, contentType, 1800);
|
|
527
|
+
const headers = {
|
|
528
|
+
'Content-Type': contentType,
|
|
529
|
+
};
|
|
530
|
+
if (uploadExtraParams) {
|
|
531
|
+
Object.entries(uploadExtraParams).forEach(([key, value]) => {
|
|
532
|
+
headers[key] = value;
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
const resp = yield fetch(uploadUrl, {
|
|
536
|
+
method: 'PUT',
|
|
537
|
+
headers,
|
|
538
|
+
body: nodeBuffer,
|
|
539
|
+
});
|
|
540
|
+
if (!resp.ok) {
|
|
541
|
+
let bodyText = '';
|
|
542
|
+
try {
|
|
543
|
+
bodyText = yield resp.text();
|
|
544
|
+
}
|
|
545
|
+
catch (e) {
|
|
546
|
+
// ignore
|
|
547
|
+
}
|
|
548
|
+
throw new Error(`Upload failed with status ${resp.status}: ${bodyText}`);
|
|
549
|
+
}
|
|
550
|
+
yield this.options.storageAdapter.markKeyForNotDeletation(filePath);
|
|
551
|
+
if (!this.resourceConfig) {
|
|
552
|
+
throw new Error('resourceConfig is not initialized yet');
|
|
553
|
+
}
|
|
554
|
+
const { error: createError } = yield this.adminforth.createResourceRecord({
|
|
555
|
+
resource: this.resourceConfig,
|
|
556
|
+
record: { [this.options.pathColumnName]: filePath },
|
|
557
|
+
adminUser,
|
|
558
|
+
extra,
|
|
559
|
+
});
|
|
560
|
+
if (createError) {
|
|
561
|
+
try {
|
|
562
|
+
yield this.options.storageAdapter.markKeyForDeletation(filePath);
|
|
563
|
+
}
|
|
564
|
+
catch (e) {
|
|
565
|
+
// best-effort cleanup, ignore error
|
|
566
|
+
}
|
|
567
|
+
throw new Error(`Error creating record after upload: ${createError}`);
|
|
568
|
+
}
|
|
569
|
+
let previewUrl;
|
|
570
|
+
if ((_b = this.options.preview) === null || _b === void 0 ? void 0 : _b.previewUrl) {
|
|
571
|
+
previewUrl = this.options.preview.previewUrl({ filePath });
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
previewUrl = yield this.options.storageAdapter.getDownloadUrl(filePath, 1800);
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
path: filePath,
|
|
578
|
+
previewUrl,
|
|
579
|
+
};
|
|
580
|
+
});
|
|
581
|
+
}
|
|
486
582
|
}
|
package/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { PluginOptions } from './types.js';
|
|
3
|
-
import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo } from "adminforth";
|
|
3
|
+
import { AdminForthPlugin, AdminForthResourceColumn, AdminForthResource, Filters, IAdminForth, IHttpServer, suggestIfTypo, RateLimiter, AdminUser, HttpExtra } from "adminforth";
|
|
4
4
|
import { Readable } from "stream";
|
|
5
|
-
import { RateLimiter } from "adminforth";
|
|
6
5
|
import { randomUUID } from "crypto";
|
|
7
6
|
import { interpretResource } from 'adminforth';
|
|
8
7
|
import { ActionCheckSource } from 'adminforth';
|
|
@@ -555,4 +554,129 @@ export default class UploadPlugin extends AdminForthPlugin {
|
|
|
555
554
|
}
|
|
556
555
|
|
|
557
556
|
|
|
557
|
+
async uploadFromBuffer({
|
|
558
|
+
filename,
|
|
559
|
+
contentType,
|
|
560
|
+
buffer,
|
|
561
|
+
adminUser,
|
|
562
|
+
extra,
|
|
563
|
+
}: {
|
|
564
|
+
filename: string;
|
|
565
|
+
contentType: string;
|
|
566
|
+
buffer: Buffer | Uint8Array | ArrayBuffer;
|
|
567
|
+
adminUser: AdminUser;
|
|
568
|
+
extra?: HttpExtra;
|
|
569
|
+
}): Promise<{ path: string; previewUrl: string }> {
|
|
570
|
+
if (!filename || !contentType || !buffer) {
|
|
571
|
+
throw new Error('filename, contentType and buffer are required');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const lastDotIndex = filename.lastIndexOf('.');
|
|
575
|
+
if (lastDotIndex === -1) {
|
|
576
|
+
throw new Error('filename must contain an extension');
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const originalExtension = filename.substring(lastDotIndex + 1).toLowerCase();
|
|
580
|
+
const originalFilename = filename.substring(0, lastDotIndex);
|
|
581
|
+
|
|
582
|
+
if (this.options.allowedFileExtensions && !this.options.allowedFileExtensions.includes(originalExtension)) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`File extension "${originalExtension}" is not allowed, allowed extensions are: ${this.options.allowedFileExtensions.join(', ')}`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
let nodeBuffer: Buffer;
|
|
589
|
+
if (Buffer.isBuffer(buffer)) {
|
|
590
|
+
nodeBuffer = buffer;
|
|
591
|
+
} else if (buffer instanceof ArrayBuffer) {
|
|
592
|
+
nodeBuffer = Buffer.from(buffer);
|
|
593
|
+
} else if (ArrayBuffer.isView(buffer)) {
|
|
594
|
+
nodeBuffer = Buffer.from(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
595
|
+
} else {
|
|
596
|
+
throw new Error('Unsupported buffer type');
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const size = nodeBuffer.byteLength;
|
|
600
|
+
if (this.options.maxFileSize && size > this.options.maxFileSize) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`File size ${size} is too large. Maximum allowed size is ${this.options.maxFileSize}`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
const filePath: string = this.options.filePath({
|
|
606
|
+
originalFilename,
|
|
607
|
+
originalExtension,
|
|
608
|
+
contentType,
|
|
609
|
+
record: undefined,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
if (filePath.startsWith('/')) {
|
|
613
|
+
throw new Error('s3Path should not start with /, please adjust s3path function to not return / at the start of the path');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const { uploadUrl, uploadExtraParams } = await this.options.storageAdapter.getUploadSignedUrl(
|
|
617
|
+
filePath,
|
|
618
|
+
contentType,
|
|
619
|
+
1800,
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
const headers: Record<string, string> = {
|
|
623
|
+
'Content-Type': contentType,
|
|
624
|
+
};
|
|
625
|
+
if (uploadExtraParams) {
|
|
626
|
+
Object.entries(uploadExtraParams).forEach(([key, value]) => {
|
|
627
|
+
headers[key] = value as string;
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const resp = await fetch(uploadUrl as any, {
|
|
632
|
+
method: 'PUT',
|
|
633
|
+
headers,
|
|
634
|
+
body: nodeBuffer as any,
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
if (!resp.ok) {
|
|
638
|
+
let bodyText = '';
|
|
639
|
+
try {
|
|
640
|
+
bodyText = await resp.text();
|
|
641
|
+
} catch (e) {
|
|
642
|
+
// ignore
|
|
643
|
+
}
|
|
644
|
+
throw new Error(`Upload failed with status ${resp.status}: ${bodyText}`);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
await this.options.storageAdapter.markKeyForNotDeletation(filePath);
|
|
648
|
+
|
|
649
|
+
if (!this.resourceConfig) {
|
|
650
|
+
throw new Error('resourceConfig is not initialized yet');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const { error: createError } = await this.adminforth.createResourceRecord({
|
|
654
|
+
resource: this.resourceConfig,
|
|
655
|
+
record: { [this.options.pathColumnName]: filePath },
|
|
656
|
+
adminUser,
|
|
657
|
+
extra,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
if (createError) {
|
|
661
|
+
try {
|
|
662
|
+
await this.options.storageAdapter.markKeyForDeletation(filePath);
|
|
663
|
+
} catch (e) {
|
|
664
|
+
// best-effort cleanup, ignore error
|
|
665
|
+
}
|
|
666
|
+
throw new Error(`Error creating record after upload: ${createError}`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
let previewUrl: string;
|
|
670
|
+
if (this.options.preview?.previewUrl) {
|
|
671
|
+
previewUrl = this.options.preview.previewUrl({ filePath });
|
|
672
|
+
} else {
|
|
673
|
+
previewUrl = await this.options.storageAdapter.getDownloadUrl(filePath, 1800);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
path: filePath,
|
|
678
|
+
previewUrl,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
558
682
|
}
|