@ackplus/nest-file-storage 1.1.1 → 1.1.5
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/package.json +2 -2
- package/src/{index.ts → index.d.ts} +1 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +7 -0
- package/src/lib/constants.d.ts +2 -0
- package/src/lib/constants.d.ts.map +1 -0
- package/src/lib/constants.js +4 -0
- package/src/lib/file-storage.service.d.ts +8 -0
- package/src/lib/file-storage.service.d.ts.map +1 -0
- package/src/lib/file-storage.service.js +29 -0
- package/src/lib/{index.ts → index.d.ts} +1 -0
- package/src/lib/index.d.ts.map +1 -0
- package/src/lib/index.js +8 -0
- package/src/lib/interceptor/file-storage.interceptor.d.ts +25 -0
- package/src/lib/interceptor/file-storage.interceptor.d.ts.map +1 -0
- package/src/lib/interceptor/{file-storage.interceptor.ts → file-storage.interceptor.js} +35 -71
- package/src/lib/nest-file-storage.module.d.ts +9 -0
- package/src/lib/nest-file-storage.module.d.ts.map +1 -0
- package/src/lib/nest-file-storage.module.js +74 -0
- package/src/lib/storage/azure.storage.d.ts +19 -0
- package/src/lib/storage/azure.storage.d.ts.map +1 -0
- package/src/lib/storage/{azure.storage.ts → azure.storage.js} +57 -118
- package/src/lib/storage/local.storage.d.ts +35 -0
- package/src/lib/storage/local.storage.d.ts.map +1 -0
- package/src/lib/storage/{local.storage.ts → local.storage.js} +44 -94
- package/src/lib/storage/s3.storage.d.ts +20 -0
- package/src/lib/storage/s3.storage.d.ts.map +1 -0
- package/src/lib/storage/{s3.storage.ts → s3.storage.js} +58 -105
- package/src/lib/storage.factory.d.ts +9 -0
- package/src/lib/storage.factory.d.ts.map +1 -0
- package/src/lib/storage.factory.js +81 -0
- package/src/lib/{types.ts → types.d.ts} +23 -35
- package/src/lib/types.d.ts.map +1 -0
- package/src/lib/types.js +9 -0
- package/eslint.config.mjs +0 -22
- package/jest.config.ts +0 -10
- package/project.json +0 -38
- package/src/lib/constants.ts +0 -1
- package/src/lib/file-storage.service.ts +0 -36
- package/src/lib/nest-file-storage.module.ts +0 -78
- package/src/lib/storage.factory.ts +0 -58
- package/tsconfig.json +0 -17
- package/tsconfig.lib.json +0 -14
- package/tsconfig.spec.json +0 -15
|
@@ -1,214 +1,153 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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) {
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AzureStorage = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const storage_blob_1 = require("@azure/storage-blob");
|
|
6
|
+
const concat_stream_1 = tslib_1.__importDefault(require("concat-stream"));
|
|
7
|
+
const moment_1 = tslib_1.__importDefault(require("moment"));
|
|
8
|
+
const path_1 = tslib_1.__importStar(require("path"));
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
class AzureStorage {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
27
13
|
this.fileNameFunction = options.fileName || ((file, _req) => {
|
|
28
|
-
return `${
|
|
14
|
+
return `${(0, uuid_1.v4)()}-${file.originalname}`;
|
|
29
15
|
});
|
|
30
|
-
|
|
31
16
|
this.fileDistFunction = options.fileDist || ((_file, _req) => {
|
|
32
|
-
return
|
|
17
|
+
return path_1.default.join('uploads', (0, moment_1.default)().format('YYYY'), (0, moment_1.default)().format('MM'), (0, moment_1.default)().format('DD'));
|
|
33
18
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
options.account,
|
|
37
|
-
options.accountKey,
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
this.blobServiceClient = new BlobServiceClient(
|
|
41
|
-
`https://${options.account}.blob.core.windows.net`,
|
|
42
|
-
sharedKeyCredential,
|
|
43
|
-
);
|
|
19
|
+
const sharedKeyCredential = new storage_blob_1.StorageSharedKeyCredential(options.account, options.accountKey);
|
|
20
|
+
this.blobServiceClient = new storage_blob_1.BlobServiceClient(`https://${options.account}.blob.core.windows.net`, sharedKeyCredential);
|
|
44
21
|
}
|
|
45
|
-
|
|
46
|
-
async _handleFile(
|
|
47
|
-
req: any,
|
|
48
|
-
file: any,
|
|
49
|
-
cb: (error?: any, info?: any) => void,
|
|
50
|
-
): Promise<void> {
|
|
22
|
+
async _handleFile(req, file, cb) {
|
|
51
23
|
try {
|
|
52
24
|
const dist = await this.fileDistFunction(file, req);
|
|
53
25
|
const key = await this.fileNameFunction(file, req);
|
|
54
|
-
const filePath = join(dist, key);
|
|
55
|
-
|
|
56
|
-
file.stream.pipe(concat({ encoding: 'buffer' }, async (buffer) => {
|
|
26
|
+
const filePath = (0, path_1.join)(dist, key);
|
|
27
|
+
file.stream.pipe((0, concat_stream_1.default)({ encoding: 'buffer' }, async (buffer) => {
|
|
57
28
|
const uploadedFile = await this.putFile(buffer, filePath);
|
|
58
|
-
|
|
59
|
-
const fileInfo: UploadedFile = {
|
|
29
|
+
const fileInfo = {
|
|
60
30
|
...uploadedFile,
|
|
61
31
|
fieldName: file.fieldname,
|
|
62
32
|
originalName: file.originalname,
|
|
63
33
|
mimetype: file.mimetype,
|
|
64
34
|
};
|
|
65
35
|
let transformData = fileInfo;
|
|
66
|
-
|
|
67
36
|
if (this.options?.transformUploadedFileObject) {
|
|
68
37
|
transformData = await this.options.transformUploadedFileObject(fileInfo);
|
|
69
38
|
}
|
|
70
39
|
cb(null, transformData);
|
|
71
40
|
}));
|
|
72
|
-
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
73
43
|
cb(error);
|
|
74
44
|
}
|
|
75
|
-
|
|
76
|
-
file.stream.on('error', (err: any) => cb(err));
|
|
45
|
+
file.stream.on('error', (err) => cb(err));
|
|
77
46
|
}
|
|
78
|
-
|
|
79
|
-
async _removeFile(
|
|
80
|
-
_req: any,
|
|
81
|
-
file: any,
|
|
82
|
-
cb: (error: Error | null) => void,
|
|
83
|
-
): Promise<void> {
|
|
47
|
+
async _removeFile(_req, file, cb) {
|
|
84
48
|
try {
|
|
85
49
|
const blobClient = this.blobServiceClient
|
|
86
50
|
.getContainerClient(this.options.container)
|
|
87
51
|
.getBlobClient(file.key);
|
|
88
|
-
|
|
89
52
|
await blobClient.delete();
|
|
90
53
|
cb(null);
|
|
91
|
-
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
92
56
|
cb(error);
|
|
93
57
|
}
|
|
94
58
|
}
|
|
95
|
-
|
|
96
|
-
getUrl(key: string): string {
|
|
59
|
+
getUrl(key) {
|
|
97
60
|
return `https://${this.options.account}.blob.core.windows.net/${this.options.container}/${key}`;
|
|
98
61
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!key) return '';
|
|
103
|
-
|
|
62
|
+
getSignedUrl(key, signatureValues = {}) {
|
|
63
|
+
if (!key)
|
|
64
|
+
return '';
|
|
104
65
|
const cloudFrontDomain = process.env['AZURE_CDN_DOMAIN_NAME'];
|
|
105
|
-
|
|
106
66
|
if (cloudFrontDomain) {
|
|
107
67
|
return `${cloudFrontDomain}/${key}`;
|
|
108
68
|
}
|
|
109
|
-
|
|
110
69
|
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
111
70
|
const blobClient = containerClient.getBlobClient(key);
|
|
112
|
-
|
|
113
|
-
const sharedKeyCredential = new StorageSharedKeyCredential(
|
|
114
|
-
this.options.account,
|
|
115
|
-
this.options.accountKey,
|
|
116
|
-
);
|
|
117
|
-
|
|
71
|
+
const sharedKeyCredential = new storage_blob_1.StorageSharedKeyCredential(this.options.account, this.options.accountKey);
|
|
118
72
|
// Set the SAS token options
|
|
119
73
|
const expiresOn = new Date();
|
|
120
74
|
expiresOn.setHours(expiresOn.getHours() + 1); // Set expiration time to 1 hour from now
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
...signatureValues,
|
|
131
|
-
},
|
|
132
|
-
sharedKeyCredential,
|
|
133
|
-
).toString();
|
|
134
|
-
|
|
75
|
+
const sasToken = (0, storage_blob_1.generateBlobSASQueryParameters)({
|
|
76
|
+
containerName: this.options.container,
|
|
77
|
+
blobName: key,
|
|
78
|
+
permissions: storage_blob_1.BlobSASPermissions.parse('r'), // Read permissions
|
|
79
|
+
protocol: storage_blob_1.SASProtocol.Https, // HTTPS only
|
|
80
|
+
startsOn: new Date(), // Start time (now)
|
|
81
|
+
expiresOn, // Expiration time
|
|
82
|
+
...signatureValues,
|
|
83
|
+
}, sharedKeyCredential).toString();
|
|
135
84
|
return `${blobClient.url}?${sasToken}`;
|
|
136
85
|
}
|
|
137
|
-
|
|
138
|
-
async getFile(key: string): Promise<Buffer> {
|
|
86
|
+
async getFile(key) {
|
|
139
87
|
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
140
88
|
const blobClient = containerClient.getBlobClient(key);
|
|
141
89
|
const downloadResponse = await blobClient.download();
|
|
142
90
|
const stream = downloadResponse.readableStreamBody;
|
|
143
|
-
|
|
144
91
|
return new Promise((resolve, reject) => {
|
|
145
|
-
const chunks
|
|
92
|
+
const chunks = [];
|
|
146
93
|
stream?.on('data', (chunk) => chunks.push(chunk));
|
|
147
94
|
stream?.on('end', () => resolve(Buffer.concat(chunks)));
|
|
148
95
|
stream?.on('error', reject);
|
|
149
96
|
});
|
|
150
97
|
}
|
|
151
|
-
|
|
152
|
-
async putFile(buffer: Buffer, key: string): Promise<UploadedFile> {
|
|
98
|
+
async putFile(buffer, key) {
|
|
153
99
|
try {
|
|
154
100
|
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
155
101
|
const blockBlobClient = containerClient.getBlockBlobClient(key);
|
|
156
|
-
|
|
157
102
|
await blockBlobClient.uploadData(buffer);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
fileName: basename(key),
|
|
103
|
+
const fileInfo = {
|
|
104
|
+
originalName: (0, path_1.basename)(key),
|
|
105
|
+
fileName: (0, path_1.basename)(key),
|
|
162
106
|
size: buffer.length,
|
|
163
107
|
buffer,
|
|
164
108
|
key,
|
|
165
109
|
fullPath: key,
|
|
166
110
|
url: this.getUrl(key),
|
|
167
111
|
};
|
|
168
|
-
|
|
169
112
|
return fileInfo;
|
|
170
|
-
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
171
115
|
console.error(`Error uploading file "${key}":`, error);
|
|
172
116
|
throw error;
|
|
173
117
|
}
|
|
174
118
|
}
|
|
175
|
-
|
|
176
|
-
async deleteFile(key: string) {
|
|
119
|
+
async deleteFile(key) {
|
|
177
120
|
try {
|
|
178
121
|
const blobClient = this.blobServiceClient
|
|
179
122
|
.getContainerClient(this.options.container)
|
|
180
123
|
.getBlobClient(key);
|
|
181
|
-
|
|
182
124
|
await blobClient.delete();
|
|
183
|
-
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
184
127
|
console.error(`Error deleting blob "${key}":`, error);
|
|
185
128
|
}
|
|
186
129
|
}
|
|
187
|
-
|
|
188
|
-
async copyFile(oldKey: string, newKey: string): Promise<UploadedFile> {
|
|
130
|
+
async copyFile(oldKey, newKey) {
|
|
189
131
|
try {
|
|
190
132
|
const containerClient = this.blobServiceClient.getContainerClient(this.options.container);
|
|
191
133
|
const sourceBlobClient = containerClient.getBlobClient(oldKey);
|
|
192
134
|
const destinationBlobClient = containerClient.getBlobClient(newKey);
|
|
193
|
-
|
|
194
135
|
const copyPoller = await destinationBlobClient.beginCopyFromURL(sourceBlobClient.url);
|
|
195
136
|
await copyPoller.pollUntilDone();
|
|
196
|
-
|
|
197
137
|
const properties = await destinationBlobClient.getProperties();
|
|
198
|
-
|
|
199
138
|
return {
|
|
200
|
-
originalName: basename(newKey),
|
|
139
|
+
originalName: (0, path_1.basename)(newKey),
|
|
201
140
|
size: properties.contentLength || 0,
|
|
202
|
-
fileName: basename(newKey),
|
|
141
|
+
fileName: (0, path_1.basename)(newKey),
|
|
203
142
|
key: newKey,
|
|
204
143
|
fullPath: newKey,
|
|
205
144
|
url: this.getUrl(newKey),
|
|
206
145
|
};
|
|
207
|
-
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
208
148
|
console.error('Error copying file in Azure Blob Storage:', error);
|
|
209
149
|
throw error;
|
|
210
150
|
}
|
|
211
151
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
152
|
}
|
|
153
|
+
exports.AzureStorage = AzureStorage;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { StorageEngine } from 'multer';
|
|
2
|
+
import { LocalStorageOptions, Storage, UploadedFile } from '../types';
|
|
3
|
+
export declare class LocalStorage implements StorageEngine, Storage {
|
|
4
|
+
private options;
|
|
5
|
+
private rootPath;
|
|
6
|
+
private fileNameFunction;
|
|
7
|
+
private fileDistFunction;
|
|
8
|
+
/**
|
|
9
|
+
* Convert OS-specific file path to URL-friendly key
|
|
10
|
+
* This ensures keys are consistent across all platforms
|
|
11
|
+
* Windows: C:\uploads\2024\01\file.jpg -> 2024/01/file.jpg
|
|
12
|
+
* Unix: /uploads/2024/01/file.jpg -> 2024/01/file.jpg
|
|
13
|
+
*/
|
|
14
|
+
private pathToUrl;
|
|
15
|
+
/**
|
|
16
|
+
* Convert URL-friendly key to OS-specific file path
|
|
17
|
+
* This converts stored keys back to valid file system paths
|
|
18
|
+
* 2024/01/file.jpg -> Windows: 2024\01\file.jpg, Unix: 2024/01/file.jpg
|
|
19
|
+
*/
|
|
20
|
+
private urlToPath;
|
|
21
|
+
/**
|
|
22
|
+
* Get full file system path from URL-friendly key
|
|
23
|
+
*/
|
|
24
|
+
private getFullPath;
|
|
25
|
+
constructor(options: LocalStorageOptions);
|
|
26
|
+
_handleFile(req: any, file: Express.Multer.File, cb: (error?: any, info?: any) => void): Promise<void>;
|
|
27
|
+
_removeFile(_req: any, file: any, cb: (error: Error | null) => void): void;
|
|
28
|
+
getUrl(urlKey: string): string;
|
|
29
|
+
getFile(urlKey: string): Promise<Buffer>;
|
|
30
|
+
deleteFile(urlKey: string): Promise<void>;
|
|
31
|
+
putFile(fileContent: Buffer, urlKey: string): Promise<UploadedFile>;
|
|
32
|
+
path(urlKey: string): string;
|
|
33
|
+
copyFile(oldUrlKey: string, newUrlKey: string): Promise<UploadedFile>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=local.storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.storage.d.ts","sourceRoot":"","sources":["../../../../../../packages/nest-file-storage/src/lib/storage/local.storage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAMvC,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGtE,qBAAa,YAAa,YAAW,aAAa,EAAE,OAAO;IA8C3C,OAAO,CAAC,OAAO;IA5C3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,gBAAgB,CAAqE;IAC7F,OAAO,CAAC,gBAAgB,CAAqE;IAE7F;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAajB;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAQjB;;OAEG;IACH,OAAO,CAAC,WAAW;gBAKC,OAAO,EAAE,mBAAmB;IAmB1C,WAAW,CACb,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EACzB,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI;IAgCzC,WAAW,CACP,IAAI,EAAE,GAAG,EACT,IAAI,EAAE,GAAG,EACT,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI;IAarC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAgBxB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMxC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMzC,OAAO,CACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC;IAiCxB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAQtB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAgC9E"}
|
|
@@ -1,182 +1,140 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export class LocalStorage implements StorageEngine, Storage {
|
|
14
|
-
|
|
15
|
-
private rootPath: string;
|
|
16
|
-
private fileNameFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
17
|
-
private fileDistFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
18
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalStorage = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const concat_stream_1 = tslib_1.__importDefault(require("concat-stream"));
|
|
6
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
7
|
+
const moment_1 = tslib_1.__importDefault(require("moment"));
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const uuid_1 = require("uuid");
|
|
10
|
+
class LocalStorage {
|
|
19
11
|
/**
|
|
20
12
|
* Convert OS-specific file path to URL-friendly key
|
|
21
13
|
* This ensures keys are consistent across all platforms
|
|
22
14
|
* Windows: C:\uploads\2024\01\file.jpg -> 2024/01/file.jpg
|
|
23
15
|
* Unix: /uploads/2024/01/file.jpg -> 2024/01/file.jpg
|
|
24
16
|
*/
|
|
25
|
-
|
|
26
|
-
if (!filePath)
|
|
27
|
-
|
|
17
|
+
pathToUrl(filePath) {
|
|
18
|
+
if (!filePath)
|
|
19
|
+
return '';
|
|
28
20
|
// Remove rootPath if present
|
|
29
21
|
let relativePath = filePath;
|
|
30
22
|
if (filePath.startsWith(this.rootPath)) {
|
|
31
23
|
relativePath = filePath.substring(this.rootPath.length);
|
|
32
24
|
}
|
|
33
|
-
|
|
34
25
|
// Convert backslashes to forward slashes and remove leading slash
|
|
35
26
|
return relativePath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
36
27
|
}
|
|
37
|
-
|
|
38
28
|
/**
|
|
39
29
|
* Convert URL-friendly key to OS-specific file path
|
|
40
30
|
* This converts stored keys back to valid file system paths
|
|
41
31
|
* 2024/01/file.jpg -> Windows: 2024\01\file.jpg, Unix: 2024/01/file.jpg
|
|
42
32
|
*/
|
|
43
|
-
|
|
44
|
-
if (!urlKey)
|
|
45
|
-
|
|
33
|
+
urlToPath(urlKey) {
|
|
34
|
+
if (!urlKey)
|
|
35
|
+
return '';
|
|
46
36
|
// Split by forward slashes and rejoin with OS-specific separator
|
|
47
37
|
const parts = urlKey.split('/').filter(part => part.length > 0);
|
|
48
|
-
return parts.join(sep);
|
|
38
|
+
return parts.join(path_1.sep);
|
|
49
39
|
}
|
|
50
|
-
|
|
51
40
|
/**
|
|
52
41
|
* Get full file system path from URL-friendly key
|
|
53
42
|
*/
|
|
54
|
-
|
|
43
|
+
getFullPath(urlKey) {
|
|
55
44
|
const osPath = this.urlToPath(urlKey);
|
|
56
|
-
return join(this.rootPath, osPath);
|
|
45
|
+
return (0, path_1.join)(this.rootPath, osPath);
|
|
57
46
|
}
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
60
49
|
// Normalize path for the current OS
|
|
61
|
-
this.rootPath = normalize(options.rootPath || join(process.cwd(), 'public'));
|
|
62
|
-
|
|
50
|
+
this.rootPath = (0, path_1.normalize)(options.rootPath || (0, path_1.join)(process.cwd(), 'public'));
|
|
63
51
|
this.fileNameFunction = options.fileName || ((file, _req) => {
|
|
64
|
-
return `${
|
|
52
|
+
return `${(0, uuid_1.v4)()}-${file.originalname}`;
|
|
65
53
|
});
|
|
66
|
-
|
|
67
54
|
this.fileDistFunction = options.fileDist || ((_file, _req) => {
|
|
68
|
-
return join(this.rootPath,
|
|
55
|
+
return (0, path_1.join)(this.rootPath, (0, moment_1.default)().format('YYYY'), (0, moment_1.default)().format('MM'), (0, moment_1.default)().format('DD'));
|
|
69
56
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
57
|
// Ensure the rootPath directory exists
|
|
73
58
|
if (!fs.existsSync(this.rootPath)) {
|
|
74
59
|
fs.mkdirSync(this.rootPath, { recursive: true });
|
|
75
60
|
}
|
|
76
61
|
}
|
|
77
|
-
|
|
78
|
-
async _handleFile(
|
|
79
|
-
req: any,
|
|
80
|
-
file: Express.Multer.File,
|
|
81
|
-
cb: (error?: any, info?: any) => void,
|
|
82
|
-
) {
|
|
62
|
+
async _handleFile(req, file, cb) {
|
|
83
63
|
try {
|
|
84
64
|
const dist = await this.fileDistFunction(file, req);
|
|
85
65
|
const fileName = await this.fileNameFunction(file, req);
|
|
86
|
-
|
|
87
|
-
const filePath = join(dist, fileName);
|
|
66
|
+
const filePath = (0, path_1.join)(dist, fileName);
|
|
88
67
|
// Convert to URL-friendly key for storage
|
|
89
68
|
const urlKey = this.pathToUrl(filePath);
|
|
90
|
-
|
|
91
|
-
file.stream.pipe(concat({ encoding: 'buffer' }, async (buffer) => {
|
|
69
|
+
file.stream.pipe((0, concat_stream_1.default)({ encoding: 'buffer' }, async (buffer) => {
|
|
92
70
|
const uploadedFile = await this.putFile(buffer, urlKey);
|
|
93
|
-
|
|
94
|
-
const fileInfo: UploadedFile = {
|
|
71
|
+
const fileInfo = {
|
|
95
72
|
...uploadedFile,
|
|
96
73
|
fieldName: file.fieldname,
|
|
97
74
|
originalName: file.originalname,
|
|
98
75
|
mimetype: file.mimetype,
|
|
99
76
|
};
|
|
100
77
|
let transformData = fileInfo;
|
|
101
|
-
|
|
102
78
|
if (this.options?.transformUploadedFileObject) {
|
|
103
79
|
transformData = await this.options.transformUploadedFileObject(fileInfo);
|
|
104
80
|
}
|
|
105
81
|
cb(null, transformData);
|
|
106
82
|
}));
|
|
107
|
-
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
108
85
|
console.error('error', error);
|
|
109
86
|
cb(error);
|
|
110
87
|
}
|
|
111
88
|
}
|
|
112
|
-
|
|
113
|
-
_removeFile(
|
|
114
|
-
_req: any,
|
|
115
|
-
file: any,
|
|
116
|
-
cb: (error: Error | null) => void,
|
|
117
|
-
) {
|
|
89
|
+
_removeFile(_req, file, cb) {
|
|
118
90
|
const filePath = file.path;
|
|
119
|
-
|
|
120
91
|
fs.unlink(filePath, (err) => {
|
|
121
92
|
if (err) {
|
|
122
93
|
cb(err);
|
|
123
|
-
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
124
96
|
cb(null);
|
|
125
97
|
}
|
|
126
98
|
});
|
|
127
99
|
}
|
|
128
|
-
|
|
129
|
-
getUrl(urlKey: string): string {
|
|
100
|
+
getUrl(urlKey) {
|
|
130
101
|
if (urlKey && urlKey.startsWith('http')) {
|
|
131
102
|
return urlKey;
|
|
132
103
|
}
|
|
133
104
|
if (!urlKey) {
|
|
134
105
|
return '';
|
|
135
106
|
}
|
|
136
|
-
|
|
137
107
|
// Key is already in URL format (forward slashes)
|
|
138
108
|
// Ensure baseUrl doesn't end with slash and key doesn't start with slash
|
|
139
109
|
const baseUrl = this.options.baseUrl.replace(/\/$/, '');
|
|
140
110
|
const cleanKey = urlKey.replace(/^\//, '');
|
|
141
|
-
|
|
142
111
|
return `${baseUrl}/${cleanKey}`;
|
|
143
112
|
}
|
|
144
|
-
|
|
145
|
-
async getFile(urlKey: string): Promise<Buffer> {
|
|
113
|
+
async getFile(urlKey) {
|
|
146
114
|
// Convert URL key to OS-specific path
|
|
147
115
|
const fullPath = this.getFullPath(urlKey);
|
|
148
116
|
return fs.promises.readFile(fullPath);
|
|
149
117
|
}
|
|
150
|
-
|
|
151
|
-
async deleteFile(urlKey: string): Promise<void> {
|
|
118
|
+
async deleteFile(urlKey) {
|
|
152
119
|
// Convert URL key to OS-specific path
|
|
153
120
|
const fullPath = this.getFullPath(urlKey);
|
|
154
121
|
return fs.promises.unlink(fullPath);
|
|
155
122
|
}
|
|
156
|
-
|
|
157
|
-
async putFile(
|
|
158
|
-
fileContent: Buffer,
|
|
159
|
-
urlKey: string,
|
|
160
|
-
): Promise<UploadedFile> {
|
|
123
|
+
async putFile(fileContent, urlKey) {
|
|
161
124
|
return new Promise((putFileResolve, reject) => {
|
|
162
125
|
// Convert URL key to OS-specific path
|
|
163
126
|
const filePath = this.getFullPath(urlKey);
|
|
164
|
-
|
|
165
|
-
const directoryPath = dirname(filePath);
|
|
166
|
-
|
|
127
|
+
const directoryPath = (0, path_1.dirname)(filePath);
|
|
167
128
|
// Create the directory if it doesn't exist
|
|
168
129
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
169
|
-
|
|
170
130
|
fs.writeFile(filePath, fileContent, (err) => {
|
|
171
131
|
if (err) {
|
|
172
132
|
reject(err);
|
|
173
133
|
return;
|
|
174
134
|
}
|
|
175
|
-
|
|
176
135
|
const stats = fs.statSync(filePath);
|
|
177
136
|
const fileName = urlKey.split('/').pop() || urlKey;
|
|
178
|
-
|
|
179
|
-
const fileInfo: UploadedFile = {
|
|
137
|
+
const fileInfo = {
|
|
180
138
|
originalName: fileName,
|
|
181
139
|
size: stats.size,
|
|
182
140
|
fileName: fileName,
|
|
@@ -188,35 +146,28 @@ export class LocalStorage implements StorageEngine, Storage {
|
|
|
188
146
|
});
|
|
189
147
|
});
|
|
190
148
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
path(urlKey: string): string {
|
|
149
|
+
path(urlKey) {
|
|
194
150
|
if (!urlKey) {
|
|
195
151
|
return '';
|
|
196
152
|
}
|
|
197
153
|
// Convert URL key to full OS-specific path
|
|
198
154
|
return this.getFullPath(urlKey);
|
|
199
155
|
}
|
|
200
|
-
|
|
201
|
-
async copyFile(oldUrlKey: string, newUrlKey: string): Promise<UploadedFile> {
|
|
156
|
+
async copyFile(oldUrlKey, newUrlKey) {
|
|
202
157
|
return new Promise((resolve, reject) => {
|
|
203
158
|
// Convert URL keys to OS-specific paths
|
|
204
159
|
const oldPath = this.getFullPath(oldUrlKey);
|
|
205
160
|
const newPath = this.getFullPath(newUrlKey);
|
|
206
|
-
|
|
207
|
-
const directoryPath = dirname(newPath);
|
|
161
|
+
const directoryPath = (0, path_1.dirname)(newPath);
|
|
208
162
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
209
|
-
|
|
210
163
|
fs.copyFile(oldPath, newPath, (err) => {
|
|
211
164
|
if (err) {
|
|
212
165
|
reject(err);
|
|
213
166
|
return;
|
|
214
167
|
}
|
|
215
|
-
|
|
216
168
|
const stats = fs.statSync(newPath);
|
|
217
169
|
const fileName = newUrlKey.split('/').pop() || newUrlKey;
|
|
218
|
-
|
|
219
|
-
const fileInfo: UploadedFile = {
|
|
170
|
+
const fileInfo = {
|
|
220
171
|
originalName: fileName,
|
|
221
172
|
size: stats.size,
|
|
222
173
|
fileName: fileName,
|
|
@@ -228,6 +179,5 @@ export class LocalStorage implements StorageEngine, Storage {
|
|
|
228
179
|
});
|
|
229
180
|
});
|
|
230
181
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
182
|
}
|
|
183
|
+
exports.LocalStorage = LocalStorage;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { GetObjectCommandInput } from '@aws-sdk/client-s3';
|
|
2
|
+
import { StorageEngine } from 'multer';
|
|
3
|
+
import { S3StorageOptions, Storage, UploadedFile } from '../types';
|
|
4
|
+
export declare class S3Storage implements StorageEngine, Storage {
|
|
5
|
+
private options;
|
|
6
|
+
private s3;
|
|
7
|
+
private fileNameFunction;
|
|
8
|
+
private fileDistFunction;
|
|
9
|
+
constructor(options: S3StorageOptions);
|
|
10
|
+
_handleFile(req: any, file: Express.Multer.File, cb: (error?: any, info?: any) => void): Promise<void>;
|
|
11
|
+
_removeFile(_req: any, file: any, cb: (error: Error | null) => void): void;
|
|
12
|
+
getUrl(key: string): string;
|
|
13
|
+
getSignedUrl(key: string, objectConfig?: Partial<GetObjectCommandInput>): Promise<string>;
|
|
14
|
+
getFile(key: string): Promise<Buffer>;
|
|
15
|
+
putFile(fileContent: Buffer, key: string): Promise<any>;
|
|
16
|
+
deleteFile(key: string): Promise<void>;
|
|
17
|
+
private streamToBuffer;
|
|
18
|
+
copyFile(oldKey: string, newKey: string): Promise<UploadedFile>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=s3.storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.storage.d.ts","sourceRoot":"","sources":["../../../../../../packages/nest-file-storage/src/lib/storage/s3.storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,qBAAqB,EAAM,MAAM,oBAAoB,CAAC;AAIlF,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAMvC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGnE,qBAAa,SAAU,YAAW,aAAa,EAAE,OAAO;IAMxC,OAAO,CAAC,OAAO;IAJ3B,OAAO,CAAC,EAAE,CAAK;IACf,OAAO,CAAC,gBAAgB,CAAqE;IAC7F,OAAO,CAAC,gBAAgB,CAAqE;gBAEzE,OAAO,EAAE,gBAAgB;IAmBvC,WAAW,CACb,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EACzB,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,GACtC,OAAO,CAAC,IAAI,CAAC;IAkChB,WAAW,CACP,IAAI,EAAE,GAAG,EACT,IAAI,EAAE,GAAG,EACT,EAAE,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAClC,IAAI;IAYP,MAAM,CAAC,GAAG,EAAE,MAAM;IAOZ,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAezF,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8BrC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IA0CvD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAmB9B,cAAc;IAQtB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CA4BxE"}
|