@ackplus/nest-file-storage 1.0.1 → 1.1.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/README.md +6 -404
- package/eslint.config.mjs +22 -0
- package/jest.config.ts +10 -0
- package/package.json +5 -62
- package/project.json +38 -0
- package/{dist/index.d.ts → src/index.ts} +0 -1
- package/src/lib/constants.ts +1 -0
- package/src/lib/file-storage.service.ts +36 -0
- package/{dist/lib/index.d.ts → src/lib/index.ts} +0 -1
- package/{dist/lib/interceptor/file-storage.interceptor.js → src/lib/interceptor/file-storage.interceptor.ts} +70 -37
- package/src/lib/nest-file-storage.module.ts +78 -0
- package/src/lib/storage/azure.storage.ts +214 -0
- package/{dist/lib/storage/local.storage.js → src/lib/storage/local.storage.ts} +96 -82
- package/{dist/lib/storage/s3.storage.js → src/lib/storage/s3.storage.ts} +107 -96
- package/src/lib/storage.factory.ts +58 -0
- package/{dist/lib/types.d.ts → src/lib/types.ts} +35 -23
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +14 -0
- package/tsconfig.spec.json +15 -0
- package/LICENSE +0 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -21
- package/dist/index.js.map +0 -1
- package/dist/lib/constants.d.ts +0 -2
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/constants.js +0 -5
- package/dist/lib/constants.js.map +0 -1
- package/dist/lib/file-storage.service.d.ts +0 -8
- package/dist/lib/file-storage.service.d.ts.map +0 -1
- package/dist/lib/file-storage.service.js +0 -30
- package/dist/lib/file-storage.service.js.map +0 -1
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/index.js +0 -22
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/interceptor/file-storage.interceptor.d.ts +0 -25
- package/dist/lib/interceptor/file-storage.interceptor.d.ts.map +0 -1
- package/dist/lib/interceptor/file-storage.interceptor.js.map +0 -1
- package/dist/lib/nest-file-storage.module.d.ts +0 -9
- package/dist/lib/nest-file-storage.module.d.ts.map +0 -1
- package/dist/lib/nest-file-storage.module.js +0 -80
- package/dist/lib/nest-file-storage.module.js.map +0 -1
- package/dist/lib/storage/azure.storage.d.ts +0 -19
- package/dist/lib/storage/azure.storage.d.ts.map +0 -1
- package/dist/lib/storage/azure.storage.js +0 -189
- package/dist/lib/storage/azure.storage.js.map +0 -1
- package/dist/lib/storage/local.storage.d.ts +0 -35
- package/dist/lib/storage/local.storage.d.ts.map +0 -1
- package/dist/lib/storage/local.storage.js.map +0 -1
- package/dist/lib/storage/s3.storage.d.ts +0 -20
- package/dist/lib/storage/s3.storage.d.ts.map +0 -1
- package/dist/lib/storage/s3.storage.js.map +0 -1
- package/dist/lib/storage.factory.d.ts +0 -9
- package/dist/lib/storage.factory.d.ts.map +0 -1
- package/dist/lib/storage.factory.js +0 -82
- package/dist/lib/storage.factory.js.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js +0 -10
- package/dist/lib/types.js.map +0 -1
|
@@ -1,32 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
|
+
import { Request } from 'express';
|
|
3
|
+
import multer from 'multer';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
import { FileStorageService } from '../file-storage.service';
|
|
7
|
+
import { StorageFactory } from '../storage.factory';
|
|
8
|
+
import { FileStorageEnum, StorageOptions, FileStorageConfigOptions, UploadedFile } from '../types';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export type FileUploadConfig = {
|
|
12
|
+
type: 'single' | 'array' | 'fields';
|
|
13
|
+
fieldName?: string;
|
|
14
|
+
maxCount?: number;
|
|
15
|
+
fields?: { name: string; maxCount?: number }[];
|
|
4
16
|
};
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
|
|
18
|
+
export type FileStorageInterceptorOptions = {
|
|
19
|
+
fileName?: (file: any, req?: Request) => string;
|
|
20
|
+
fileDist?: (file: any, req?: Request) => string;
|
|
21
|
+
prefix?: string;
|
|
22
|
+
storageType?: FileStorageEnum;
|
|
23
|
+
storageOptions?: StorageOptions;
|
|
24
|
+
|
|
25
|
+
// File mapping callback - user defines what to return (defaults to file.key)
|
|
26
|
+
mapToRequestBody?: (file: any, fieldName: string, req?: Request) => any;
|
|
27
|
+
};
|
|
28
|
+
|
|
11
29
|
// Helper function to map file object
|
|
12
|
-
function mapFileObject(file) {
|
|
30
|
+
function mapFileObject(file: any) {
|
|
13
31
|
return {
|
|
14
32
|
fieldName: file.fieldname,
|
|
15
33
|
originalName: file.originalname,
|
|
16
34
|
fileName: file.filename,
|
|
17
35
|
mimetype: file.mimetype,
|
|
18
36
|
size: file.size,
|
|
19
|
-
key: file.key,
|
|
20
|
-
path: file.path,
|
|
21
|
-
url: file.url,
|
|
37
|
+
key: (file as any).key,
|
|
38
|
+
path: (file as any).path,
|
|
39
|
+
url: (file as any).url,
|
|
22
40
|
encoding: file.encoding,
|
|
23
|
-
fullPath: file.fullPath
|
|
24
|
-
};
|
|
41
|
+
fullPath: (file as any).fullPath
|
|
42
|
+
} as unknown as UploadedFile;
|
|
25
43
|
}
|
|
44
|
+
|
|
26
45
|
// Helper function to apply file mapping with callback
|
|
27
|
-
function applyFileKeyMapping(
|
|
46
|
+
function applyFileKeyMapping(
|
|
47
|
+
request: Request,
|
|
48
|
+
fileConfig: FileUploadConfig,
|
|
49
|
+
interceptorOptions?: FileStorageInterceptorOptions
|
|
50
|
+
): void {
|
|
28
51
|
// Default callback returns the file key
|
|
29
|
-
const mapCallback = interceptorOptions?.mapToRequestBody || ((file) => {
|
|
52
|
+
const mapCallback = interceptorOptions?.mapToRequestBody || ((file: any) => {
|
|
30
53
|
// For arrays, return array of keys
|
|
31
54
|
if (Array.isArray(file)) {
|
|
32
55
|
return file.map(f => f.key);
|
|
@@ -34,6 +57,7 @@ function applyFileKeyMapping(request, fileConfig, interceptorOptions) {
|
|
|
34
57
|
// For single file, return the key
|
|
35
58
|
return file.key;
|
|
36
59
|
});
|
|
60
|
+
|
|
37
61
|
if (fileConfig.type === 'single') {
|
|
38
62
|
const file = request.file;
|
|
39
63
|
if (file) {
|
|
@@ -41,17 +65,15 @@ function applyFileKeyMapping(request, fileConfig, interceptorOptions) {
|
|
|
41
65
|
const mappedFile = mapFileObject(file);
|
|
42
66
|
request.body[fieldName] = mapCallback(mappedFile, fieldName, request);
|
|
43
67
|
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const files = request.files;
|
|
68
|
+
} else if (fileConfig.type === 'array') {
|
|
69
|
+
const files = request.files as Express.Multer.File[];
|
|
47
70
|
if (files && files.length > 0) {
|
|
48
71
|
const fieldName = fileConfig.fieldName || 'files';
|
|
49
72
|
const mappedFiles = files.map(file => mapFileObject(file));
|
|
50
73
|
request.body[fieldName] = mapCallback(mappedFiles, fieldName, request);
|
|
51
74
|
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const files = request.files;
|
|
75
|
+
} else if (fileConfig.type === 'fields') {
|
|
76
|
+
const files = request.files as { [fieldname: string]: Express.Multer.File[] };
|
|
55
77
|
if (files) {
|
|
56
78
|
Object.keys(files).forEach(fieldName => {
|
|
57
79
|
const mappedFiles = files[fieldName].map(file => mapFileObject(file));
|
|
@@ -60,40 +82,47 @@ function applyFileKeyMapping(request, fileConfig, interceptorOptions) {
|
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
}
|
|
85
|
+
|
|
63
86
|
/**
|
|
64
87
|
* Function-based interceptor that accepts storage options dynamically.
|
|
65
88
|
*/
|
|
66
|
-
function FileStorageInterceptor(
|
|
89
|
+
export function FileStorageInterceptor(
|
|
90
|
+
fileConfig: FileUploadConfig | string,
|
|
91
|
+
interceptorOptions?: FileStorageInterceptorOptions,
|
|
92
|
+
): NestInterceptor {
|
|
67
93
|
if (typeof fileConfig === 'string') {
|
|
68
94
|
fileConfig = {
|
|
69
95
|
type: 'single',
|
|
70
96
|
fieldName: fileConfig,
|
|
71
97
|
};
|
|
72
98
|
}
|
|
99
|
+
|
|
73
100
|
return {
|
|
74
|
-
async intercept(context, next) {
|
|
75
|
-
const options =
|
|
101
|
+
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
|
|
102
|
+
const options = FileStorageService.getOptions();
|
|
76
103
|
const request = context.switchToHttp().getRequest();
|
|
77
104
|
const response = context.switchToHttp().getResponse();
|
|
105
|
+
|
|
78
106
|
// Determine storage type - handle both config approaches
|
|
79
|
-
let storageType;
|
|
80
|
-
let storageConfig;
|
|
107
|
+
let storageType: FileStorageEnum;
|
|
108
|
+
let storageConfig: any;
|
|
109
|
+
|
|
81
110
|
if ('storage' in options) {
|
|
82
111
|
// Configuration-based approach
|
|
83
|
-
const configOptions = options;
|
|
112
|
+
const configOptions = options as FileStorageConfigOptions;
|
|
84
113
|
storageType = interceptorOptions?.storageType ?? configOptions.storage;
|
|
85
|
-
storageConfig = configOptions[
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
114
|
+
storageConfig = (configOptions as any)[`Config`];
|
|
115
|
+
} else {
|
|
88
116
|
// Class factory approach - default to LOCAL
|
|
89
|
-
storageType = interceptorOptions?.storageType ??
|
|
117
|
+
storageType = interceptorOptions?.storageType ?? FileStorageEnum.LOCAL;
|
|
90
118
|
storageConfig = {};
|
|
91
119
|
}
|
|
120
|
+
|
|
92
121
|
const storageOptions = {
|
|
93
122
|
...storageConfig,
|
|
94
123
|
...(interceptorOptions?.storageOptions || {}),
|
|
95
124
|
fileName: interceptorOptions?.fileName || storageConfig?.fileName,
|
|
96
|
-
fileDist: (file, req) => {
|
|
125
|
+
fileDist: (file: any, req: any) => {
|
|
97
126
|
if (interceptorOptions?.fileDist) {
|
|
98
127
|
return interceptorOptions.fileDist(file, req);
|
|
99
128
|
}
|
|
@@ -101,9 +130,11 @@ function FileStorageInterceptor(fileConfig, interceptorOptions) {
|
|
|
101
130
|
},
|
|
102
131
|
prefix: interceptorOptions?.prefix || storageConfig?.prefix,
|
|
103
132
|
};
|
|
133
|
+
|
|
104
134
|
// Create storage instance dynamically
|
|
105
|
-
const storage = await
|
|
106
|
-
const multerInstance = (
|
|
135
|
+
const storage = await StorageFactory.createStorage(storageType, storageOptions);
|
|
136
|
+
const multerInstance = multer({ storage });
|
|
137
|
+
|
|
107
138
|
// Multer setup based on fileConfig
|
|
108
139
|
let multerMiddleware;
|
|
109
140
|
switch (fileConfig.type) {
|
|
@@ -128,14 +159,16 @@ function FileStorageInterceptor(fileConfig, interceptorOptions) {
|
|
|
128
159
|
default:
|
|
129
160
|
throw new Error('Invalid file upload type. Use "single", "array", or "fields".');
|
|
130
161
|
}
|
|
162
|
+
|
|
131
163
|
// Execute Multer middleware
|
|
132
164
|
await new Promise((resolve, reject) => {
|
|
133
165
|
multerMiddleware(request, response, (err) => (err ? reject(err) : resolve(true)));
|
|
134
166
|
});
|
|
167
|
+
|
|
135
168
|
// Apply file key mapping after multer processing
|
|
136
169
|
applyFileKeyMapping(request, fileConfig, interceptorOptions);
|
|
170
|
+
|
|
137
171
|
return next.handle();
|
|
138
172
|
}
|
|
139
173
|
};
|
|
140
174
|
}
|
|
141
|
-
//# sourceMappingURL=file-storage.interceptor.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Module, DynamicModule, Provider } from '@nestjs/common';
|
|
2
|
+
|
|
3
|
+
import { FILE_STORAGE_OPTIONS } from './constants';
|
|
4
|
+
import { FileStorageService } from './file-storage.service';
|
|
5
|
+
import { FileStorageAsyncOptions, FileStorageModuleOptions, FileStorageOptionsFactory } from './types';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@Module({})
|
|
9
|
+
export class NestFileStorageModule {
|
|
10
|
+
|
|
11
|
+
static forRoot(options: FileStorageModuleOptions): DynamicModule {
|
|
12
|
+
return {
|
|
13
|
+
module: NestFileStorageModule,
|
|
14
|
+
providers: [
|
|
15
|
+
{
|
|
16
|
+
provide: FILE_STORAGE_OPTIONS,
|
|
17
|
+
useFactory: async () => {
|
|
18
|
+
FileStorageService.setOptions(options); // ✅ Store globally
|
|
19
|
+
return options;
|
|
20
|
+
},
|
|
21
|
+
inject: [],
|
|
22
|
+
},
|
|
23
|
+
FileStorageService,
|
|
24
|
+
],
|
|
25
|
+
exports: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static forRootAsync(options: FileStorageAsyncOptions): DynamicModule {
|
|
30
|
+
const asyncProviders: Provider[] = this.createAsyncProviders(options);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
module: NestFileStorageModule,
|
|
34
|
+
imports: options.imports || [],
|
|
35
|
+
providers: [...asyncProviders, FileStorageService],
|
|
36
|
+
exports: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static createAsyncProviders(options: FileStorageAsyncOptions): Provider[] {
|
|
41
|
+
if (options.useExisting || options.useFactory) {
|
|
42
|
+
return [this.createAsyncOptionsProvider(options)];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return [
|
|
46
|
+
this.createAsyncOptionsProvider(options),
|
|
47
|
+
{
|
|
48
|
+
provide: options.useClass!,
|
|
49
|
+
useClass: options.useClass!,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private static createAsyncOptionsProvider(options: FileStorageAsyncOptions): Provider {
|
|
55
|
+
if (options.useFactory) {
|
|
56
|
+
return {
|
|
57
|
+
provide: FILE_STORAGE_OPTIONS,
|
|
58
|
+
useFactory: async (...args: any[]) => {
|
|
59
|
+
const fileStorageOptions = await options.useFactory!(...args);
|
|
60
|
+
FileStorageService.setOptions(fileStorageOptions);
|
|
61
|
+
return fileStorageOptions;
|
|
62
|
+
},
|
|
63
|
+
inject: options.inject || [],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
provide: FILE_STORAGE_OPTIONS,
|
|
69
|
+
useFactory: async (optionsFactory: FileStorageOptionsFactory) => {
|
|
70
|
+
const fileStorageOptions = await optionsFactory.createFileStorageOptions();
|
|
71
|
+
FileStorageService.setOptions(fileStorageOptions);
|
|
72
|
+
return fileStorageOptions;
|
|
73
|
+
},
|
|
74
|
+
inject: [options.useExisting || options.useClass!],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlobSASPermissions,
|
|
3
|
+
BlobSASSignatureValues,
|
|
4
|
+
BlobServiceClient,
|
|
5
|
+
generateBlobSASQueryParameters,
|
|
6
|
+
SASProtocol,
|
|
7
|
+
StorageSharedKeyCredential,
|
|
8
|
+
} from '@azure/storage-blob';
|
|
9
|
+
import concat from 'concat-stream';
|
|
10
|
+
import moment from 'moment';
|
|
11
|
+
import { StorageEngine } from 'multer';
|
|
12
|
+
import path, { basename, join } from 'path';
|
|
13
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
14
|
+
|
|
15
|
+
import { AzureStorageOptions, Storage, UploadedFile } from '../types';
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export class AzureStorage implements StorageEngine, Storage {
|
|
19
|
+
|
|
20
|
+
private blobServiceClient: BlobServiceClient;
|
|
21
|
+
|
|
22
|
+
private fileNameFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
23
|
+
private fileDistFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
constructor(private options: AzureStorageOptions) {
|
|
27
|
+
this.fileNameFunction = options.fileName || ((file, _req) => {
|
|
28
|
+
return `${uuidv4()}-${file.originalname}`;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.fileDistFunction = options.fileDist || ((_file, _req) => {
|
|
32
|
+
return path.join('uploads', moment().format('YYYY'), moment().format('MM'), moment().format('DD'));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const sharedKeyCredential = new StorageSharedKeyCredential(
|
|
36
|
+
options.account,
|
|
37
|
+
options.accountKey,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
this.blobServiceClient = new BlobServiceClient(
|
|
41
|
+
`https://${options.account}.blob.core.windows.net`,
|
|
42
|
+
sharedKeyCredential,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async _handleFile(
|
|
47
|
+
req: any,
|
|
48
|
+
file: any,
|
|
49
|
+
cb: (error?: any, info?: any) => void,
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
try {
|
|
52
|
+
const dist = await this.fileDistFunction(file, req);
|
|
53
|
+
const key = await this.fileNameFunction(file, req);
|
|
54
|
+
const filePath = join(dist, key);
|
|
55
|
+
|
|
56
|
+
file.stream.pipe(concat({ encoding: 'buffer' }, async (buffer) => {
|
|
57
|
+
const uploadedFile = await this.putFile(buffer, filePath);
|
|
58
|
+
|
|
59
|
+
const fileInfo: UploadedFile = {
|
|
60
|
+
...uploadedFile,
|
|
61
|
+
fieldName: file.fieldname,
|
|
62
|
+
originalName: file.originalname,
|
|
63
|
+
mimetype: file.mimetype,
|
|
64
|
+
};
|
|
65
|
+
let transformData = fileInfo;
|
|
66
|
+
|
|
67
|
+
if (this.options?.transformUploadedFileObject) {
|
|
68
|
+
transformData = await this.options.transformUploadedFileObject(fileInfo);
|
|
69
|
+
}
|
|
70
|
+
cb(null, transformData);
|
|
71
|
+
}));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
cb(error);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
file.stream.on('error', (err: any) => cb(err));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async _removeFile(
|
|
80
|
+
_req: any,
|
|
81
|
+
file: any,
|
|
82
|
+
cb: (error: Error | null) => void,
|
|
83
|
+
): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
const blobClient = this.blobServiceClient
|
|
86
|
+
.getContainerClient(this.options.container)
|
|
87
|
+
.getBlobClient(file.key);
|
|
88
|
+
|
|
89
|
+
await blobClient.delete();
|
|
90
|
+
cb(null);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
cb(error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getUrl(key: string): string {
|
|
97
|
+
return `https://${this.options.account}.blob.core.windows.net/${this.options.container}/${key}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
getSignedUrl(key: string, signatureValues: Partial<Omit<BlobSASSignatureValues, 'containerName'>> = {}): string {
|
|
102
|
+
if (!key) return '';
|
|
103
|
+
|
|
104
|
+
const cloudFrontDomain = process.env['AZURE_CDN_DOMAIN_NAME'];
|
|
105
|
+
|
|
106
|
+
if (cloudFrontDomain) {
|
|
107
|
+
return `${cloudFrontDomain}/${key}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
111
|
+
const blobClient = containerClient.getBlobClient(key);
|
|
112
|
+
|
|
113
|
+
const sharedKeyCredential = new StorageSharedKeyCredential(
|
|
114
|
+
this.options.account,
|
|
115
|
+
this.options.accountKey,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Set the SAS token options
|
|
119
|
+
const expiresOn = new Date();
|
|
120
|
+
expiresOn.setHours(expiresOn.getHours() + 1); // Set expiration time to 1 hour from now
|
|
121
|
+
|
|
122
|
+
const sasToken = generateBlobSASQueryParameters(
|
|
123
|
+
{
|
|
124
|
+
containerName: this.options.container,
|
|
125
|
+
blobName: key,
|
|
126
|
+
permissions: BlobSASPermissions.parse('r'), // Read permissions
|
|
127
|
+
protocol: SASProtocol.Https, // HTTPS only
|
|
128
|
+
startsOn: new Date(), // Start time (now)
|
|
129
|
+
expiresOn, // Expiration time
|
|
130
|
+
...signatureValues,
|
|
131
|
+
},
|
|
132
|
+
sharedKeyCredential,
|
|
133
|
+
).toString();
|
|
134
|
+
|
|
135
|
+
return `${blobClient.url}?${sasToken}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async getFile(key: string): Promise<Buffer> {
|
|
139
|
+
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
140
|
+
const blobClient = containerClient.getBlobClient(key);
|
|
141
|
+
const downloadResponse = await blobClient.download();
|
|
142
|
+
const stream = downloadResponse.readableStreamBody;
|
|
143
|
+
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const chunks: Buffer[] = [];
|
|
146
|
+
stream?.on('data', (chunk) => chunks.push(chunk));
|
|
147
|
+
stream?.on('end', () => resolve(Buffer.concat(chunks)));
|
|
148
|
+
stream?.on('error', reject);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async putFile(buffer: Buffer, key: string): Promise<UploadedFile> {
|
|
153
|
+
try {
|
|
154
|
+
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
155
|
+
const blockBlobClient = containerClient.getBlockBlobClient(key);
|
|
156
|
+
|
|
157
|
+
await blockBlobClient.uploadData(buffer);
|
|
158
|
+
|
|
159
|
+
const fileInfo: UploadedFile = {
|
|
160
|
+
originalName: basename(key),
|
|
161
|
+
fileName: basename(key),
|
|
162
|
+
size: buffer.length,
|
|
163
|
+
buffer,
|
|
164
|
+
key,
|
|
165
|
+
fullPath: key,
|
|
166
|
+
url: this.getUrl(key),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return fileInfo;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(`Error uploading file "${key}":`, error);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async deleteFile(key: string) {
|
|
177
|
+
try {
|
|
178
|
+
const blobClient = this.blobServiceClient
|
|
179
|
+
.getContainerClient(this.options.container)
|
|
180
|
+
.getBlobClient(key);
|
|
181
|
+
|
|
182
|
+
await blobClient.delete();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`Error deleting blob "${key}":`, error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async copyFile(oldKey: string, newKey: string): Promise<UploadedFile> {
|
|
189
|
+
try {
|
|
190
|
+
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
191
|
+
const sourceBlobClient = containerClient.getBlobClient(oldKey);
|
|
192
|
+
const destinationBlobClient = containerClient.getBlobClient(newKey);
|
|
193
|
+
|
|
194
|
+
const copyPoller = await destinationBlobClient.beginCopyFromURL(sourceBlobClient.url);
|
|
195
|
+
await copyPoller.pollUntilDone();
|
|
196
|
+
|
|
197
|
+
const properties = await destinationBlobClient.getProperties();
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
originalName: basename(newKey),
|
|
201
|
+
size: properties.contentLength || 0,
|
|
202
|
+
fileName: basename(newKey),
|
|
203
|
+
key: newKey,
|
|
204
|
+
fullPath: newKey,
|
|
205
|
+
url: this.getUrl(newKey),
|
|
206
|
+
};
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Error copying file in Azure Blob Storage:', error);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
}
|