@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,175 +1,182 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.LocalStorage = void 0;
|
|
40
|
-
const concat_stream_1 = __importDefault(require("concat-stream"));
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
|
-
const moment_1 = __importDefault(require("moment"));
|
|
43
|
-
const path_1 = require("path");
|
|
44
|
-
const uuid_1 = require("uuid");
|
|
45
|
-
class LocalStorage {
|
|
1
|
+
import concat from 'concat-stream';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import { StorageEngine } from 'multer';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { basename, dirname, join, normalize, sep } from 'path';
|
|
7
|
+
import { join as posixJoin } from 'path/posix';
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
|
|
10
|
+
import { LocalStorageOptions, Storage, UploadedFile } from '../types';
|
|
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
|
+
|
|
46
19
|
/**
|
|
47
20
|
* Convert OS-specific file path to URL-friendly key
|
|
48
21
|
* This ensures keys are consistent across all platforms
|
|
49
22
|
* Windows: C:\uploads\2024\01\file.jpg -> 2024/01/file.jpg
|
|
50
23
|
* Unix: /uploads/2024/01/file.jpg -> 2024/01/file.jpg
|
|
51
24
|
*/
|
|
52
|
-
pathToUrl(filePath) {
|
|
53
|
-
if (!filePath)
|
|
54
|
-
|
|
25
|
+
private pathToUrl(filePath: string): string {
|
|
26
|
+
if (!filePath) return '';
|
|
27
|
+
|
|
55
28
|
// Remove rootPath if present
|
|
56
29
|
let relativePath = filePath;
|
|
57
30
|
if (filePath.startsWith(this.rootPath)) {
|
|
58
31
|
relativePath = filePath.substring(this.rootPath.length);
|
|
59
32
|
}
|
|
33
|
+
|
|
60
34
|
// Convert backslashes to forward slashes and remove leading slash
|
|
61
35
|
return relativePath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
62
36
|
}
|
|
37
|
+
|
|
63
38
|
/**
|
|
64
39
|
* Convert URL-friendly key to OS-specific file path
|
|
65
40
|
* This converts stored keys back to valid file system paths
|
|
66
41
|
* 2024/01/file.jpg -> Windows: 2024\01\file.jpg, Unix: 2024/01/file.jpg
|
|
67
42
|
*/
|
|
68
|
-
urlToPath(urlKey) {
|
|
69
|
-
if (!urlKey)
|
|
70
|
-
|
|
43
|
+
private urlToPath(urlKey: string): string {
|
|
44
|
+
if (!urlKey) return '';
|
|
45
|
+
|
|
71
46
|
// Split by forward slashes and rejoin with OS-specific separator
|
|
72
47
|
const parts = urlKey.split('/').filter(part => part.length > 0);
|
|
73
|
-
return parts.join(
|
|
48
|
+
return parts.join(sep);
|
|
74
49
|
}
|
|
50
|
+
|
|
75
51
|
/**
|
|
76
52
|
* Get full file system path from URL-friendly key
|
|
77
53
|
*/
|
|
78
|
-
getFullPath(urlKey) {
|
|
54
|
+
private getFullPath(urlKey: string): string {
|
|
79
55
|
const osPath = this.urlToPath(urlKey);
|
|
80
|
-
return
|
|
56
|
+
return join(this.rootPath, osPath);
|
|
81
57
|
}
|
|
82
|
-
|
|
83
|
-
|
|
58
|
+
|
|
59
|
+
constructor(private options: LocalStorageOptions) {
|
|
84
60
|
// Normalize path for the current OS
|
|
85
|
-
this.rootPath =
|
|
61
|
+
this.rootPath = normalize(options.rootPath || join(process.cwd(), 'public'));
|
|
62
|
+
|
|
86
63
|
this.fileNameFunction = options.fileName || ((file, _req) => {
|
|
87
|
-
return `${(
|
|
64
|
+
return `${uuidv4()}-${file.originalname}`;
|
|
88
65
|
});
|
|
66
|
+
|
|
89
67
|
this.fileDistFunction = options.fileDist || ((_file, _req) => {
|
|
90
|
-
return
|
|
68
|
+
return join(this.rootPath, moment().format('YYYY'), moment().format('MM'), moment().format('DD'));
|
|
91
69
|
});
|
|
70
|
+
|
|
71
|
+
|
|
92
72
|
// Ensure the rootPath directory exists
|
|
93
73
|
if (!fs.existsSync(this.rootPath)) {
|
|
94
74
|
fs.mkdirSync(this.rootPath, { recursive: true });
|
|
95
75
|
}
|
|
96
76
|
}
|
|
97
|
-
|
|
77
|
+
|
|
78
|
+
async _handleFile(
|
|
79
|
+
req: any,
|
|
80
|
+
file: Express.Multer.File,
|
|
81
|
+
cb: (error?: any, info?: any) => void,
|
|
82
|
+
) {
|
|
98
83
|
try {
|
|
99
84
|
const dist = await this.fileDistFunction(file, req);
|
|
100
85
|
const fileName = await this.fileNameFunction(file, req);
|
|
101
|
-
|
|
86
|
+
|
|
87
|
+
const filePath = join(dist, fileName);
|
|
102
88
|
// Convert to URL-friendly key for storage
|
|
103
89
|
const urlKey = this.pathToUrl(filePath);
|
|
104
|
-
|
|
90
|
+
|
|
91
|
+
file.stream.pipe(concat({ encoding: 'buffer' }, async (buffer) => {
|
|
105
92
|
const uploadedFile = await this.putFile(buffer, urlKey);
|
|
106
|
-
|
|
93
|
+
|
|
94
|
+
const fileInfo: UploadedFile = {
|
|
107
95
|
...uploadedFile,
|
|
108
96
|
fieldName: file.fieldname,
|
|
109
97
|
originalName: file.originalname,
|
|
110
98
|
mimetype: file.mimetype,
|
|
111
99
|
};
|
|
112
100
|
let transformData = fileInfo;
|
|
101
|
+
|
|
113
102
|
if (this.options?.transformUploadedFileObject) {
|
|
114
103
|
transformData = await this.options.transformUploadedFileObject(fileInfo);
|
|
115
104
|
}
|
|
116
105
|
cb(null, transformData);
|
|
117
106
|
}));
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
107
|
+
} catch (error) {
|
|
120
108
|
console.error('error', error);
|
|
121
109
|
cb(error);
|
|
122
110
|
}
|
|
123
111
|
}
|
|
124
|
-
|
|
112
|
+
|
|
113
|
+
_removeFile(
|
|
114
|
+
_req: any,
|
|
115
|
+
file: any,
|
|
116
|
+
cb: (error: Error | null) => void,
|
|
117
|
+
) {
|
|
125
118
|
const filePath = file.path;
|
|
119
|
+
|
|
126
120
|
fs.unlink(filePath, (err) => {
|
|
127
121
|
if (err) {
|
|
128
122
|
cb(err);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
123
|
+
} else {
|
|
131
124
|
cb(null);
|
|
132
125
|
}
|
|
133
126
|
});
|
|
134
127
|
}
|
|
135
|
-
|
|
128
|
+
|
|
129
|
+
getUrl(urlKey: string): string {
|
|
136
130
|
if (urlKey && urlKey.startsWith('http')) {
|
|
137
131
|
return urlKey;
|
|
138
132
|
}
|
|
139
133
|
if (!urlKey) {
|
|
140
|
-
return
|
|
134
|
+
return '';
|
|
141
135
|
}
|
|
136
|
+
|
|
142
137
|
// Key is already in URL format (forward slashes)
|
|
143
138
|
// Ensure baseUrl doesn't end with slash and key doesn't start with slash
|
|
144
139
|
const baseUrl = this.options.baseUrl.replace(/\/$/, '');
|
|
145
140
|
const cleanKey = urlKey.replace(/^\//, '');
|
|
141
|
+
|
|
146
142
|
return `${baseUrl}/${cleanKey}`;
|
|
147
143
|
}
|
|
148
|
-
|
|
144
|
+
|
|
145
|
+
async getFile(urlKey: string): Promise<Buffer> {
|
|
149
146
|
// Convert URL key to OS-specific path
|
|
150
147
|
const fullPath = this.getFullPath(urlKey);
|
|
151
148
|
return fs.promises.readFile(fullPath);
|
|
152
149
|
}
|
|
153
|
-
|
|
150
|
+
|
|
151
|
+
async deleteFile(urlKey: string): Promise<void> {
|
|
154
152
|
// Convert URL key to OS-specific path
|
|
155
153
|
const fullPath = this.getFullPath(urlKey);
|
|
156
154
|
return fs.promises.unlink(fullPath);
|
|
157
155
|
}
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
async putFile(
|
|
158
|
+
fileContent: Buffer,
|
|
159
|
+
urlKey: string,
|
|
160
|
+
): Promise<UploadedFile> {
|
|
159
161
|
return new Promise((putFileResolve, reject) => {
|
|
160
162
|
// Convert URL key to OS-specific path
|
|
161
163
|
const filePath = this.getFullPath(urlKey);
|
|
162
|
-
|
|
164
|
+
|
|
165
|
+
const directoryPath = dirname(filePath);
|
|
166
|
+
|
|
163
167
|
// Create the directory if it doesn't exist
|
|
164
168
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
169
|
+
|
|
165
170
|
fs.writeFile(filePath, fileContent, (err) => {
|
|
166
171
|
if (err) {
|
|
167
172
|
reject(err);
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
175
|
+
|
|
170
176
|
const stats = fs.statSync(filePath);
|
|
171
177
|
const fileName = urlKey.split('/').pop() || urlKey;
|
|
172
|
-
|
|
178
|
+
|
|
179
|
+
const fileInfo: UploadedFile = {
|
|
173
180
|
originalName: fileName,
|
|
174
181
|
size: stats.size,
|
|
175
182
|
fileName: fileName,
|
|
@@ -181,28 +188,35 @@ class LocalStorage {
|
|
|
181
188
|
});
|
|
182
189
|
});
|
|
183
190
|
}
|
|
184
|
-
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
path(urlKey: string): string {
|
|
185
194
|
if (!urlKey) {
|
|
186
|
-
return
|
|
195
|
+
return '';
|
|
187
196
|
}
|
|
188
197
|
// Convert URL key to full OS-specific path
|
|
189
198
|
return this.getFullPath(urlKey);
|
|
190
199
|
}
|
|
191
|
-
|
|
200
|
+
|
|
201
|
+
async copyFile(oldUrlKey: string, newUrlKey: string): Promise<UploadedFile> {
|
|
192
202
|
return new Promise((resolve, reject) => {
|
|
193
203
|
// Convert URL keys to OS-specific paths
|
|
194
204
|
const oldPath = this.getFullPath(oldUrlKey);
|
|
195
205
|
const newPath = this.getFullPath(newUrlKey);
|
|
196
|
-
|
|
206
|
+
|
|
207
|
+
const directoryPath = dirname(newPath);
|
|
197
208
|
fs.mkdirSync(directoryPath, { recursive: true });
|
|
209
|
+
|
|
198
210
|
fs.copyFile(oldPath, newPath, (err) => {
|
|
199
211
|
if (err) {
|
|
200
212
|
reject(err);
|
|
201
213
|
return;
|
|
202
214
|
}
|
|
215
|
+
|
|
203
216
|
const stats = fs.statSync(newPath);
|
|
204
217
|
const fileName = newUrlKey.split('/').pop() || newUrlKey;
|
|
205
|
-
|
|
218
|
+
|
|
219
|
+
const fileInfo: UploadedFile = {
|
|
206
220
|
originalName: fileName,
|
|
207
221
|
size: stats.size,
|
|
208
222
|
fileName: fileName,
|
|
@@ -214,6 +228,6 @@ class LocalStorage {
|
|
|
214
228
|
});
|
|
215
229
|
});
|
|
216
230
|
}
|
|
231
|
+
|
|
232
|
+
|
|
217
233
|
}
|
|
218
|
-
exports.LocalStorage = LocalStorage;
|
|
219
|
-
//# sourceMappingURL=local.storage.js.map
|
|
@@ -1,60 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.S3Storage = void 0;
|
|
40
|
-
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
41
|
-
const client_s3_2 = require("@aws-sdk/client-s3");
|
|
42
|
-
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
43
|
-
const moment_1 = __importDefault(require("moment"));
|
|
44
|
-
const path_1 = __importStar(require("path"));
|
|
45
|
-
const stream_1 = require("stream");
|
|
46
|
-
const uuid_1 = require("uuid");
|
|
47
|
-
class S3Storage {
|
|
48
|
-
constructor(options) {
|
|
49
|
-
this.options = options;
|
|
1
|
+
import { CopyObjectCommand, GetObjectCommandInput, S3 } from '@aws-sdk/client-s3';
|
|
2
|
+
import { DeleteObjectCommand, GetObjectCommand, GetObjectCommandOutput, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
3
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
4
|
+
import moment from 'moment';
|
|
5
|
+
import { StorageEngine } from 'multer';
|
|
6
|
+
import path, { basename, join } from 'path';
|
|
7
|
+
import { Readable } from 'stream';
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
import { S3StorageOptions, Storage, UploadedFile } from '../types';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export class S3Storage implements StorageEngine, Storage {
|
|
15
|
+
|
|
16
|
+
private s3: S3;
|
|
17
|
+
private fileNameFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
18
|
+
private fileDistFunction: (file: Express.Multer.File, req?: any) => string | Promise<string>;
|
|
19
|
+
|
|
20
|
+
constructor(private options: S3StorageOptions) {
|
|
50
21
|
this.fileNameFunction = options.fileName || ((file, _req) => {
|
|
51
|
-
return `${(
|
|
22
|
+
return `${uuidv4()}-${file.originalname}`;
|
|
52
23
|
});
|
|
24
|
+
|
|
53
25
|
this.fileDistFunction = options.fileDist || ((_file, _req) => {
|
|
54
|
-
return
|
|
26
|
+
return path.join('uploads', moment().format('YYYY'), moment().format('MM'), moment().format('DD'));
|
|
55
27
|
});
|
|
56
|
-
|
|
57
|
-
|
|
28
|
+
|
|
29
|
+
this.s3 = new S3({
|
|
30
|
+
...(this.options.endpoint ? { endpoint: this.options.endpoint } : {}),
|
|
58
31
|
region: this.options.region,
|
|
59
32
|
credentials: {
|
|
60
33
|
accessKeyId: this.options.accessKeyId,
|
|
@@ -62,90 +35,118 @@ class S3Storage {
|
|
|
62
35
|
},
|
|
63
36
|
});
|
|
64
37
|
}
|
|
65
|
-
|
|
38
|
+
|
|
39
|
+
async _handleFile(
|
|
40
|
+
req: any,
|
|
41
|
+
file: Express.Multer.File,
|
|
42
|
+
cb: (error?: any, info?: any) => void,
|
|
43
|
+
): Promise<void> {
|
|
66
44
|
// Collect file chunks to determine size
|
|
67
|
-
const chunks = [];
|
|
45
|
+
const chunks: Uint8Array[] = [];
|
|
68
46
|
file.stream.on('data', (chunk) => chunks.push(chunk));
|
|
69
47
|
file.stream.on('end', async () => {
|
|
70
48
|
const buffer = Buffer.concat(chunks);
|
|
49
|
+
|
|
71
50
|
try {
|
|
72
51
|
const dist = await this.fileDistFunction(file, req);
|
|
73
52
|
const key = await this.fileNameFunction(file, req);
|
|
74
|
-
const filePath =
|
|
53
|
+
const filePath = join(dist, key);
|
|
54
|
+
|
|
75
55
|
const uploadedFile = await this.putFile(buffer, filePath);
|
|
76
|
-
|
|
56
|
+
|
|
57
|
+
const fileInfo: UploadedFile = {
|
|
77
58
|
...uploadedFile,
|
|
78
59
|
fieldName: file.fieldname,
|
|
79
60
|
originalName: file.originalname,
|
|
80
61
|
mimetype: file.mimetype,
|
|
81
62
|
};
|
|
82
63
|
let transformData = fileInfo;
|
|
64
|
+
|
|
83
65
|
if (this.options?.transformUploadedFileObject) {
|
|
84
66
|
transformData = await this.options.transformUploadedFileObject(fileInfo);
|
|
85
67
|
}
|
|
86
68
|
cb(null, transformData);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
69
|
+
} catch (err) {
|
|
89
70
|
cb(err);
|
|
90
71
|
}
|
|
91
72
|
});
|
|
73
|
+
|
|
92
74
|
file.stream.on('error', (err) => cb(err));
|
|
93
75
|
}
|
|
94
|
-
|
|
76
|
+
|
|
77
|
+
_removeFile(
|
|
78
|
+
_req: any,
|
|
79
|
+
file: any,
|
|
80
|
+
cb: (error: Error | null) => void,
|
|
81
|
+
): void {
|
|
95
82
|
const params = {
|
|
96
83
|
Bucket: this.options.bucket,
|
|
97
84
|
Key: file.key,
|
|
98
85
|
};
|
|
86
|
+
|
|
99
87
|
this.s3
|
|
100
88
|
.deleteObject(params)
|
|
101
89
|
.then(() => cb(null))
|
|
102
90
|
.catch((err) => cb(err));
|
|
103
91
|
}
|
|
104
|
-
|
|
92
|
+
|
|
93
|
+
getUrl(key: string) {
|
|
105
94
|
if (this.options?.cloudFrontUrl) {
|
|
106
95
|
return `${this.options.cloudFrontUrl}/${key}`;
|
|
107
96
|
}
|
|
108
97
|
return `https://${this.options.bucket}.s3.amazonaws.com/${key}`;
|
|
109
98
|
}
|
|
110
|
-
|
|
99
|
+
|
|
100
|
+
async getSignedUrl(key: string, objectConfig?: Partial<GetObjectCommandInput>): Promise<string> {
|
|
111
101
|
if (key) {
|
|
112
|
-
const url = await
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
const url = await getSignedUrl(
|
|
103
|
+
this.s3 as any,
|
|
104
|
+
new GetObjectCommand({
|
|
105
|
+
Bucket: this.options.bucket,
|
|
106
|
+
Key: key,
|
|
107
|
+
...(objectConfig && { ...objectConfig }),
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
117
110
|
return url;
|
|
118
111
|
}
|
|
119
|
-
return
|
|
112
|
+
return '';
|
|
120
113
|
}
|
|
121
|
-
|
|
114
|
+
|
|
115
|
+
async getFile(key: string): Promise<Buffer> {
|
|
122
116
|
if (!key) {
|
|
123
117
|
throw new Error('Key is required to fetch the file from S3.');
|
|
124
118
|
}
|
|
119
|
+
|
|
125
120
|
try {
|
|
126
|
-
const command = new
|
|
121
|
+
const command = new GetObjectCommand({
|
|
127
122
|
Bucket: this.options.bucket,
|
|
128
123
|
Key: key,
|
|
129
124
|
});
|
|
130
|
-
|
|
125
|
+
|
|
126
|
+
const data: GetObjectCommandOutput = await this.s3.send(command);
|
|
127
|
+
|
|
131
128
|
if (!data.Body) {
|
|
132
129
|
throw new Error('Empty response received from S3.');
|
|
133
130
|
}
|
|
131
|
+
|
|
134
132
|
// Handle both Buffer and Readable Stream responses
|
|
135
|
-
if (data.Body instanceof
|
|
133
|
+
if (data.Body instanceof Readable) {
|
|
136
134
|
return await this.streamToBuffer(data.Body);
|
|
137
135
|
}
|
|
136
|
+
|
|
138
137
|
throw new Error('Unexpected data.Body type received from S3.');
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
138
|
+
} catch (error) {
|
|
141
139
|
console.error(`Error fetching file (${key}) from S3:`, error);
|
|
142
140
|
throw error;
|
|
143
141
|
}
|
|
144
142
|
}
|
|
145
|
-
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async putFile(fileContent: Buffer, key: string): Promise<any> {
|
|
146
146
|
try {
|
|
147
|
-
const fileName =
|
|
147
|
+
const fileName = basename(key);
|
|
148
148
|
const fileKey = key || (Math.random() + 1).toString(36).substring(12);
|
|
149
|
+
|
|
149
150
|
// Upload the file
|
|
150
151
|
const putParams = {
|
|
151
152
|
Bucket: this.options.bucket,
|
|
@@ -153,16 +154,20 @@ class S3Storage {
|
|
|
153
154
|
Body: fileContent,
|
|
154
155
|
ContentDisposition: `inline; ${fileName}`,
|
|
155
156
|
};
|
|
156
|
-
|
|
157
|
+
|
|
158
|
+
await this.s3.send(new PutObjectCommand(putParams));
|
|
159
|
+
|
|
157
160
|
// Fetch file metadata to get size
|
|
158
161
|
const headParams = {
|
|
159
162
|
Bucket: this.options.bucket,
|
|
160
163
|
Key: fileKey,
|
|
161
164
|
};
|
|
162
|
-
|
|
165
|
+
|
|
166
|
+
const headObject = await this.s3.send(new HeadObjectCommand(headParams));
|
|
163
167
|
const fileSize = headObject.ContentLength || 0;
|
|
168
|
+
|
|
164
169
|
// Construct response object
|
|
165
|
-
const fileData = {
|
|
170
|
+
const fileData: UploadedFile = {
|
|
166
171
|
originalName: fileName,
|
|
167
172
|
fileName: fileName,
|
|
168
173
|
size: fileSize,
|
|
@@ -172,60 +177,66 @@ class S3Storage {
|
|
|
172
177
|
url: this.getUrl(fileKey),
|
|
173
178
|
};
|
|
174
179
|
return fileData;
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
180
|
+
} catch (error) {
|
|
177
181
|
console.error('Error uploading file to S3:', error);
|
|
178
182
|
throw error;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
|
-
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async deleteFile(key: string): Promise<void> {
|
|
182
188
|
if (!key) {
|
|
183
189
|
throw new Error('File key is required for deletion.');
|
|
184
190
|
}
|
|
191
|
+
|
|
185
192
|
try {
|
|
186
193
|
const deleteParams = {
|
|
187
194
|
Bucket: this.options.bucket,
|
|
188
195
|
Key: key,
|
|
189
196
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
catch (error) {
|
|
197
|
+
|
|
198
|
+
await this.s3.send(new DeleteObjectCommand(deleteParams));
|
|
199
|
+
} catch (error) {
|
|
193
200
|
console.error(`Error deleting file (${key}) from S3:`, error);
|
|
194
201
|
throw error;
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
private async streamToBuffer(stream: Readable): Promise<Buffer> {
|
|
207
|
+
const chunks: Uint8Array[] = [];
|
|
199
208
|
for await (const chunk of stream) {
|
|
200
209
|
chunks.push(chunk);
|
|
201
210
|
}
|
|
202
211
|
return Buffer.concat(chunks);
|
|
203
212
|
}
|
|
204
|
-
|
|
213
|
+
|
|
214
|
+
async copyFile(oldKey: string, newKey: string): Promise<UploadedFile> {
|
|
205
215
|
try {
|
|
206
|
-
await this.s3.send(new
|
|
216
|
+
await this.s3.send(new CopyObjectCommand({
|
|
207
217
|
Bucket: this.options.bucket,
|
|
208
218
|
CopySource: `/${this.options.bucket}/${oldKey}`,
|
|
209
219
|
Key: newKey,
|
|
210
220
|
}));
|
|
211
|
-
|
|
221
|
+
|
|
222
|
+
const headObject = await this.s3.send(new HeadObjectCommand({
|
|
212
223
|
Bucket: this.options.bucket,
|
|
213
224
|
Key: newKey,
|
|
214
225
|
}));
|
|
226
|
+
|
|
215
227
|
return {
|
|
216
|
-
originalName:
|
|
228
|
+
originalName: basename(newKey),
|
|
217
229
|
size: headObject.ContentLength || 0,
|
|
218
|
-
fileName:
|
|
230
|
+
fileName: basename(newKey),
|
|
219
231
|
key: newKey,
|
|
220
232
|
fullPath: newKey,
|
|
221
233
|
url: this.getUrl(newKey),
|
|
222
234
|
};
|
|
223
|
-
}
|
|
224
|
-
catch (error) {
|
|
235
|
+
} catch (error) {
|
|
225
236
|
console.error('Error copying file in S3:', error);
|
|
226
237
|
throw error;
|
|
227
238
|
}
|
|
228
239
|
}
|
|
240
|
+
|
|
241
|
+
|
|
229
242
|
}
|
|
230
|
-
exports.S3Storage = S3Storage;
|
|
231
|
-
//# sourceMappingURL=s3.storage.js.map
|