@nxgt/shared-storage 1.0.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 +15 -0
- package/dist/env.d.ts +16 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/i18n/i18n.d.ts +2 -0
- package/dist/i18n/i18n.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +4 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/resources/index.d.ts +481 -0
- package/dist/i18n/resources/index.d.ts.map +1 -0
- package/dist/i18n/types.d.ts +4 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +548 -0
- package/dist/index.js.map +16 -0
- package/dist/services/create-lazy-storage.d.ts +11 -0
- package/dist/services/create-lazy-storage.d.ts.map +1 -0
- package/dist/services/gridfs.service.d.ts +46 -0
- package/dist/services/gridfs.service.d.ts.map +1 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/minio.service.d.ts +43 -0
- package/dist/services/minio.service.d.ts.map +1 -0
- package/dist/services/storage.service.d.ts +28 -0
- package/dist/services/storage.service.d.ts.map +1 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
// src/env.ts
|
|
2
|
+
import { logger } from "@nxgt/shared-logging";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var envSchema = z.object({
|
|
5
|
+
S3_ENDPOINT: z.string().default("http://host.docker.internal:9000"),
|
|
6
|
+
S3_USER: z.string().default("minio"),
|
|
7
|
+
S3_PASSWORD: z.string().default("minio123"),
|
|
8
|
+
S3_BUCKET: z.string().default("uploads")
|
|
9
|
+
});
|
|
10
|
+
var parseEnv = (value) => {
|
|
11
|
+
const result = envSchema.safeParse(value);
|
|
12
|
+
if (!result.success) {
|
|
13
|
+
logger.error("❌ Invalid environment variables:");
|
|
14
|
+
logger.error(result.error.issues);
|
|
15
|
+
throw new Error("Invalid environment variables");
|
|
16
|
+
}
|
|
17
|
+
return result.data;
|
|
18
|
+
};
|
|
19
|
+
var env = parseEnv({
|
|
20
|
+
S3_ENDPOINT: Bun.env.S3_ENDPOINT,
|
|
21
|
+
S3_USER: Bun.env.S3_USER,
|
|
22
|
+
S3_PASSWORD: Bun.env.S3_PASSWORD,
|
|
23
|
+
S3_BUCKET: Bun.env.S3_BUCKET
|
|
24
|
+
});
|
|
25
|
+
// src/i18n/i18n.ts
|
|
26
|
+
import { createTranslator } from "@nxgt/i18n";
|
|
27
|
+
|
|
28
|
+
// src/i18n/resources/index.ts
|
|
29
|
+
import { resources as shared } from "@nxgt/i18n";
|
|
30
|
+
// src/i18n/resources/en.json
|
|
31
|
+
var en_default = {
|
|
32
|
+
errors: {
|
|
33
|
+
"write-failed": "Failed to upload file to storage.",
|
|
34
|
+
"list-failed": "Failed to list storage objects.",
|
|
35
|
+
"file-failed": "Failed to retrieve storage file.",
|
|
36
|
+
"exists-failed": "Failed to check if file exists in storage.",
|
|
37
|
+
"presign-failed": "Failed to generate presigned URL for storage file.",
|
|
38
|
+
"delete-failed": "Failed to delete file from storage.",
|
|
39
|
+
"size-failed": "Failed to get file size from storage.",
|
|
40
|
+
"stat-failed": "Failed to get file stats from storage.",
|
|
41
|
+
"unlink-failed": "Failed to unlink file from storage.",
|
|
42
|
+
"fetch-failed": "Failed to fetch file from storage.",
|
|
43
|
+
"file-not-found": "File ''{key}'' not found in storage."
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// src/i18n/resources/fr.json
|
|
47
|
+
var fr_default = {
|
|
48
|
+
errors: {
|
|
49
|
+
"write-failed": "Failed to upload file to storage.",
|
|
50
|
+
"list-failed": "Failed to list storage objects.",
|
|
51
|
+
"file-failed": "Failed to retrieve storage file.",
|
|
52
|
+
"exists-failed": "Failed to check if file exists in storage.",
|
|
53
|
+
"presign-failed": "Failed to generate presigned URL for storage file.",
|
|
54
|
+
"delete-failed": "Failed to delete file from storage.",
|
|
55
|
+
"size-failed": "Failed to get file size from storage.",
|
|
56
|
+
"stat-failed": "Failed to get file stats from storage.",
|
|
57
|
+
"unlink-failed": "Failed to unlink file from storage.",
|
|
58
|
+
"fetch-failed": "Failed to fetch file from storage.",
|
|
59
|
+
"file-not-found": "Fichier ''{key}'' introuvable dans le stockage."
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/i18n/resources/index.ts
|
|
64
|
+
var resources = {
|
|
65
|
+
en: {
|
|
66
|
+
storage: en_default,
|
|
67
|
+
...shared.en
|
|
68
|
+
},
|
|
69
|
+
fr: {
|
|
70
|
+
storage: fr_default,
|
|
71
|
+
...shared.fr
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/i18n/i18n.ts
|
|
76
|
+
var translate = createTranslator(resources);
|
|
77
|
+
// src/services/storage.service.ts
|
|
78
|
+
import { omit, STRINGS_UTILS } from "@nxgt/shared/helpers";
|
|
79
|
+
import { CustomException } from "@nxgt/shared-exceptions";
|
|
80
|
+
import { getLogger as getLogger2 } from "@nxgt/shared-logging";
|
|
81
|
+
import {
|
|
82
|
+
fetch,
|
|
83
|
+
S3Client
|
|
84
|
+
} from "bun";
|
|
85
|
+
|
|
86
|
+
// src/services/minio.service.ts
|
|
87
|
+
import { getLogger } from "@nxgt/shared-logging";
|
|
88
|
+
import { Archive } from "bun";
|
|
89
|
+
import {
|
|
90
|
+
Client,
|
|
91
|
+
CopyDestinationOptions,
|
|
92
|
+
CopySourceOptions
|
|
93
|
+
} from "minio";
|
|
94
|
+
class MinioService {
|
|
95
|
+
constructor(bucket) {
|
|
96
|
+
this.logger = getLogger();
|
|
97
|
+
this.bucket = bucket ?? S3_CREDENTIALS.bucket;
|
|
98
|
+
this.minio = new Client({
|
|
99
|
+
endPoint: S3_CREDENTIALS.endpoint.replace(/https?:\/\//, "").replace(/:.*/, ""),
|
|
100
|
+
accessKey: S3_CREDENTIALS.accessKeyId,
|
|
101
|
+
secretKey: S3_CREDENTIALS.secretAccessKey,
|
|
102
|
+
useSSL: S3_CREDENTIALS.endpoint?.startsWith("https"),
|
|
103
|
+
port: S3_CREDENTIALS.endpoint ? parseInt(S3_CREDENTIALS.endpoint.split(":").pop() || "9000", 10) : 9000
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async ensureBucketExists(bucket) {
|
|
107
|
+
const exists = await this.bucketExists(bucket);
|
|
108
|
+
if (!exists) {
|
|
109
|
+
await this.makeBucket(bucket);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async convert(body) {
|
|
113
|
+
if (body instanceof Blob || body instanceof File || body instanceof Response || body instanceof Request) {
|
|
114
|
+
const arrayBuffer = await body.arrayBuffer();
|
|
115
|
+
return Buffer.from(arrayBuffer);
|
|
116
|
+
}
|
|
117
|
+
if (body instanceof Archive) {
|
|
118
|
+
const arrayBuffer = await (await body.blob()).arrayBuffer();
|
|
119
|
+
return Buffer.from(arrayBuffer);
|
|
120
|
+
}
|
|
121
|
+
if (body instanceof ArrayBuffer || body instanceof SharedArrayBuffer) {
|
|
122
|
+
return Buffer.from(body);
|
|
123
|
+
}
|
|
124
|
+
if (typeof body === "string" || body instanceof Buffer) {
|
|
125
|
+
return body;
|
|
126
|
+
}
|
|
127
|
+
return Buffer.from(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
|
|
128
|
+
}
|
|
129
|
+
async putObject(key, body, size, metadata = {}) {
|
|
130
|
+
this.logger.info(`Putting object ${key} to bucket ${this.bucket}`);
|
|
131
|
+
return this.minio.putObject(this.bucket, key, body, size, metadata);
|
|
132
|
+
}
|
|
133
|
+
async getObject(key, options = {}) {
|
|
134
|
+
this.logger.info(`Getting object ${key} from bucket ${this.bucket}`);
|
|
135
|
+
return this.minio.getObject(this.bucket, key, options);
|
|
136
|
+
}
|
|
137
|
+
async fPutObject(key, filePath, metadata = {}) {
|
|
138
|
+
this.logger.info(`Putting file ${filePath} as object ${key} to bucket ${this.bucket}`);
|
|
139
|
+
return this.minio.fPutObject(this.bucket, key, filePath, metadata);
|
|
140
|
+
}
|
|
141
|
+
async fGetObject(key, filePath, options = {}) {
|
|
142
|
+
this.logger.info(`Getting object ${key} from bucket ${this.bucket} to file ${filePath}`);
|
|
143
|
+
return this.minio.fGetObject(this.bucket, key, filePath, options);
|
|
144
|
+
}
|
|
145
|
+
async findUploadId(key) {
|
|
146
|
+
this.logger.info(`Finding upload ID for object ${key} in bucket ${this.bucket}`);
|
|
147
|
+
return this.minio.findUploadId(this.bucket, key);
|
|
148
|
+
}
|
|
149
|
+
async copyObject(source, destination) {
|
|
150
|
+
this.logger.info(`Copying object from ${source.Object} to ${destination.Object} in bucket ${this.bucket}`);
|
|
151
|
+
return this.minio.copyObject(new CopySourceOptions({
|
|
152
|
+
...source,
|
|
153
|
+
Bucket: source.Bucket ?? this.bucket
|
|
154
|
+
}), new CopyDestinationOptions({
|
|
155
|
+
...destination,
|
|
156
|
+
Bucket: destination.Bucket ?? this.bucket
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
async deleteObject(key, options) {
|
|
160
|
+
this.logger.info(`Deleting object ${key} from bucket ${this.bucket}`);
|
|
161
|
+
return this.minio.removeObject(this.bucket, key, options);
|
|
162
|
+
}
|
|
163
|
+
async listObjects(prefix = "", recursive = false, options) {
|
|
164
|
+
this.logger.info(`Listing objects in bucket ${this.bucket} with prefix ${prefix} and recursive ${recursive}`);
|
|
165
|
+
return this.minio.listObjects(this.bucket, prefix, recursive, options);
|
|
166
|
+
}
|
|
167
|
+
async listObjectsV2(prefix = "", recursive = false, startAfter) {
|
|
168
|
+
this.logger.info(`Listing objects (V2) in bucket ${this.bucket} with prefix ${prefix} and recursive ${recursive}`);
|
|
169
|
+
return this.minio.listObjectsV2(this.bucket, prefix, recursive, startAfter);
|
|
170
|
+
}
|
|
171
|
+
async presignedGetObject(key, expiresIn = 7 * 24 * 60 * 60, respHeaders = {}) {
|
|
172
|
+
this.logger.info(`Presigning GET for object ${key} in bucket ${this.bucket}`);
|
|
173
|
+
return this.minio.presignedGetObject(this.bucket, key, expiresIn, respHeaders);
|
|
174
|
+
}
|
|
175
|
+
async presignedPutObject(key, expiresIn = 7 * 24 * 60 * 60) {
|
|
176
|
+
this.logger.info(`Presigning PUT for object ${key} in bucket ${this.bucket}`);
|
|
177
|
+
return this.minio.presignedPutObject(this.bucket, key, expiresIn);
|
|
178
|
+
}
|
|
179
|
+
async bucketExists(bucket) {
|
|
180
|
+
this.logger.info(`Checking if bucket ${bucket ?? this.bucket} exists`);
|
|
181
|
+
return this.minio.bucketExists(bucket ?? this.bucket);
|
|
182
|
+
}
|
|
183
|
+
async makeBucket(bucket) {
|
|
184
|
+
this.logger.info(`Creating bucket ${bucket ?? this.bucket}`);
|
|
185
|
+
return this.minio.makeBucket(bucket ?? this.bucket);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/services/storage.service.ts
|
|
190
|
+
var S3_CREDENTIALS = {
|
|
191
|
+
endpoint: env.S3_ENDPOINT,
|
|
192
|
+
bucket: env.S3_BUCKET,
|
|
193
|
+
accessKeyId: env.S3_USER,
|
|
194
|
+
secretAccessKey: env.S3_PASSWORD
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
class StorageService {
|
|
198
|
+
constructor(bucket) {
|
|
199
|
+
this.logger = getLogger2();
|
|
200
|
+
this.bucket = bucket ?? S3_CREDENTIALS.bucket;
|
|
201
|
+
this.s3 = new S3Client({
|
|
202
|
+
...S3_CREDENTIALS,
|
|
203
|
+
bucket: this.bucket
|
|
204
|
+
});
|
|
205
|
+
this.minio = new MinioService(bucket);
|
|
206
|
+
this.minio.ensureBucketExists(bucket).catch((error) => {
|
|
207
|
+
this.logger.error(error);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async write(key, body, options = {}) {
|
|
211
|
+
try {
|
|
212
|
+
return this.s3.write(key, body, { ...options });
|
|
213
|
+
} catch (error) {
|
|
214
|
+
this.logger.error(error);
|
|
215
|
+
throw CustomException.internal({
|
|
216
|
+
message: "storage.errors.write-failed",
|
|
217
|
+
debugMessage: error.message
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
async list(input = {}, options = {}) {
|
|
222
|
+
try {
|
|
223
|
+
return this.s3.list(input, options);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.logger.error(error);
|
|
226
|
+
throw CustomException.internal({
|
|
227
|
+
message: "storage.errors.list-failed",
|
|
228
|
+
debugMessage: error.message
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
file(key, options = {}) {
|
|
233
|
+
try {
|
|
234
|
+
return this.s3.file(key, options);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
this.logger.error(error);
|
|
237
|
+
throw CustomException.internal({
|
|
238
|
+
message: "storage.errors.file-failed",
|
|
239
|
+
debugMessage: error.message
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async exists(key, options = {}) {
|
|
244
|
+
try {
|
|
245
|
+
return this.s3.exists(key, options);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
this.logger.error(error);
|
|
248
|
+
throw CustomException.internal({
|
|
249
|
+
message: "storage.errors.exists-failed",
|
|
250
|
+
debugMessage: error.message
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
presing(key, options = {}) {
|
|
255
|
+
try {
|
|
256
|
+
return this.s3.presign(key, options);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
this.logger.error(error);
|
|
259
|
+
throw CustomException.internal({
|
|
260
|
+
message: "storage.errors.presign-failed",
|
|
261
|
+
debugMessage: error.message
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async delete(key, options = {}) {
|
|
266
|
+
try {
|
|
267
|
+
await this.ensureExists(key, options);
|
|
268
|
+
return this.s3.delete(key, options);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
this.logger.error(error);
|
|
271
|
+
throw CustomException.internal({
|
|
272
|
+
message: "storage.errors.delete-failed",
|
|
273
|
+
debugMessage: error.message
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async size(key, options = {}) {
|
|
278
|
+
try {
|
|
279
|
+
await this.ensureExists(key, options);
|
|
280
|
+
return this.s3.size(key, options);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.logger.error(error);
|
|
283
|
+
throw CustomException.internal({
|
|
284
|
+
message: "storage.errors.size-failed",
|
|
285
|
+
debugMessage: error.message
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async stat(key, options = {}) {
|
|
290
|
+
try {
|
|
291
|
+
await this.ensureExists(key, options);
|
|
292
|
+
return this.s3.stat(key, options);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
this.logger.error(error);
|
|
295
|
+
throw CustomException.internal({
|
|
296
|
+
message: "storage.errors.stat-failed",
|
|
297
|
+
debugMessage: error.message
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async unlink(key, options = {}) {
|
|
302
|
+
try {
|
|
303
|
+
await this.ensureExists(key, options);
|
|
304
|
+
return this.s3.unlink(key, options);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
this.logger.error(error);
|
|
307
|
+
throw CustomException.internal({
|
|
308
|
+
message: "storage.errors.unlink-failed",
|
|
309
|
+
debugMessage: error.message
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async fetch(input, {
|
|
314
|
+
bucket,
|
|
315
|
+
...options
|
|
316
|
+
} = {}) {
|
|
317
|
+
try {
|
|
318
|
+
await this.ensureExists(input, { bucket });
|
|
319
|
+
return fetch(STRINGS_UTILS.normalizeUrl(`s3://${bucket ?? this.bucket}/${input}`), {
|
|
320
|
+
...options,
|
|
321
|
+
s3: omit(S3_CREDENTIALS, ["bucket"])
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
this.logger.error(error);
|
|
325
|
+
throw CustomException.internal({
|
|
326
|
+
message: "storage.errors.fetch-failed",
|
|
327
|
+
debugMessage: error.message
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async ensureExists(key, options = {}) {
|
|
332
|
+
if (!await this.s3.exists(key, options)) {
|
|
333
|
+
throw CustomException.notFound({
|
|
334
|
+
message: translate("storage.errors.file-not-found", { key })
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/services/create-lazy-storage.ts
|
|
341
|
+
function createLazyStorage(bucket) {
|
|
342
|
+
let storage;
|
|
343
|
+
return function getStorage() {
|
|
344
|
+
if (!storage) {
|
|
345
|
+
storage = new StorageService(bucket);
|
|
346
|
+
}
|
|
347
|
+
return storage;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
// src/services/gridfs.service.ts
|
|
351
|
+
import { createReadStream } from "node:fs";
|
|
352
|
+
import { CustomException as CustomException2 } from "@nxgt/shared-exceptions";
|
|
353
|
+
import { mongoose } from "@nxgt/shared-mongo";
|
|
354
|
+
import { ObjectId } from "bson";
|
|
355
|
+
import { pick } from "lodash";
|
|
356
|
+
var MAX_SIZE = 100;
|
|
357
|
+
|
|
358
|
+
class GridFSService {
|
|
359
|
+
constructor() {
|
|
360
|
+
if (!mongoose.connection.db) {
|
|
361
|
+
throw new Error("Database connection not established");
|
|
362
|
+
}
|
|
363
|
+
this.uploads = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
364
|
+
bucketName: "uploads"
|
|
365
|
+
});
|
|
366
|
+
this.avatars = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
367
|
+
bucketName: "avatars"
|
|
368
|
+
});
|
|
369
|
+
this.images = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
370
|
+
bucketName: "images"
|
|
371
|
+
});
|
|
372
|
+
this.videos = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
373
|
+
bucketName: "videos"
|
|
374
|
+
});
|
|
375
|
+
this.files = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
376
|
+
bucketName: "files"
|
|
377
|
+
});
|
|
378
|
+
this.buckets = {
|
|
379
|
+
avatars: this.avatars,
|
|
380
|
+
uploads: this.uploads,
|
|
381
|
+
images: this.images,
|
|
382
|
+
videos: this.videos,
|
|
383
|
+
files: this.files
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
async paginate(filter, options) {
|
|
387
|
+
const after = options?.after;
|
|
388
|
+
const before = options?.before;
|
|
389
|
+
const first = options?.first && options.first > 0 ? options.first : undefined;
|
|
390
|
+
const last = options?.last && options.last > 0 ? options.last : undefined;
|
|
391
|
+
if (first && last) {
|
|
392
|
+
throw CustomException2.badRequest({
|
|
393
|
+
message: "errors.could-not-use-first-and-last-together"
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const cursorFilter = {
|
|
397
|
+
...filter
|
|
398
|
+
};
|
|
399
|
+
if (after) {
|
|
400
|
+
cursorFilter._id = {
|
|
401
|
+
...cursorFilter._id ?? {},
|
|
402
|
+
$gt: new ObjectId(after)
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (before) {
|
|
406
|
+
cursorFilter._id = {
|
|
407
|
+
...cursorFilter._id ?? {},
|
|
408
|
+
$lt: new ObjectId(before)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const limit = Math.min(first ?? last ?? MAX_SIZE, MAX_SIZE);
|
|
412
|
+
const querySort = { _id: last ? -1 : 1 };
|
|
413
|
+
const bucket = this.bucket(options);
|
|
414
|
+
const query = bucket.find(cursorFilter).sort(querySort);
|
|
415
|
+
query.limit(Math.max(limit || 0, 1) + 1);
|
|
416
|
+
const docs = await query.toArray();
|
|
417
|
+
const hasExtraDoc = docs.length > limit;
|
|
418
|
+
const resultDocs = hasExtraDoc ? docs.slice(0, limit) : docs;
|
|
419
|
+
if (last) {
|
|
420
|
+
resultDocs.reverse();
|
|
421
|
+
}
|
|
422
|
+
const totalElements = await bucket.find(filter ?? {}).count();
|
|
423
|
+
const startCursor = resultDocs.length > 0 ? resultDocs?.[0]?._id.toString() : null;
|
|
424
|
+
const endCursor = resultDocs.length > 0 ? resultDocs?.[resultDocs.length - 1]?._id.toString() : null;
|
|
425
|
+
return {
|
|
426
|
+
data: resultDocs,
|
|
427
|
+
metadata: {
|
|
428
|
+
startCursor,
|
|
429
|
+
endCursor,
|
|
430
|
+
hasNextPage: hasExtraDoc,
|
|
431
|
+
totalElements
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
async findById(id, options) {
|
|
436
|
+
return this.bucket(options).find({ _id: new mongoose.mongo.ObjectId(id) }).limit(1).tryNext();
|
|
437
|
+
}
|
|
438
|
+
async find(filter, options) {
|
|
439
|
+
return this.bucket(pick(options, ["bucketName", "bucket"])).find(filter, options).toArray();
|
|
440
|
+
}
|
|
441
|
+
async delete(id, options) {
|
|
442
|
+
await this.bucket(options).delete(new mongoose.mongo.ObjectId(id));
|
|
443
|
+
}
|
|
444
|
+
async download(id, { user, ...options } = {}) {
|
|
445
|
+
const gridFSFile = await this.findById(id, options);
|
|
446
|
+
if (!gridFSFile) {
|
|
447
|
+
throw CustomException2.notFound({
|
|
448
|
+
message: "files.errors.file-not-found"
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
if (mongoose.connection.db) {
|
|
452
|
+
await mongoose.connection.db.collection(`${options.bucketName ?? "uploads"}.files`).findOneAndUpdate({ _id: new mongoose.mongo.ObjectId(id) }, {
|
|
453
|
+
$set: {
|
|
454
|
+
"metadata.lastOpenedBy": user,
|
|
455
|
+
"metadata.lastOpenedDate": new Date
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return this.bucket(options).openDownloadStream(new mongoose.mongo.ObjectId(id));
|
|
460
|
+
}
|
|
461
|
+
async rename(id, name, { user, ...options } = {}) {
|
|
462
|
+
const gridFSFile = await this.findById(id, options);
|
|
463
|
+
if (!gridFSFile) {
|
|
464
|
+
throw CustomException2.notFound({
|
|
465
|
+
message: "files.errors.file-not-found"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
await this.bucket(options).rename(new mongoose.mongo.ObjectId(id), name);
|
|
469
|
+
if (mongoose.connection.db) {
|
|
470
|
+
await mongoose.connection.db.collection(`${options.bucketName ?? "uploads"}.files`).findOneAndUpdate({ _id: new mongoose.mongo.ObjectId(id) }, {
|
|
471
|
+
$set: {
|
|
472
|
+
"metadata.lastModifiedBy": user,
|
|
473
|
+
"metadata.lastModifiedDate": new Date
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
const result = await this.findById(id, options);
|
|
478
|
+
if (!result) {
|
|
479
|
+
throw CustomException2.notFound({
|
|
480
|
+
message: "files.errors.file-not-found-after-rename"
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
async upload(file, options) {
|
|
486
|
+
const fileId = new ObjectId;
|
|
487
|
+
const filePath = `uploads/tmp/${fileId.toHexString()}-${file.name}`;
|
|
488
|
+
await Bun.write(filePath, file);
|
|
489
|
+
const uploaded = Bun.file(filePath);
|
|
490
|
+
const metadata = {
|
|
491
|
+
...options?.metadata ?? {},
|
|
492
|
+
contentType: uploaded.type,
|
|
493
|
+
mimetype: uploaded.type,
|
|
494
|
+
category: this.resolveFileTypeCategory(uploaded.type)
|
|
495
|
+
};
|
|
496
|
+
const stream = this.bucket(options).openUploadStream(file.name, {
|
|
497
|
+
id: fileId,
|
|
498
|
+
metadata
|
|
499
|
+
});
|
|
500
|
+
createReadStream(filePath).pipe(stream);
|
|
501
|
+
return new Promise((resolve, reject) => {
|
|
502
|
+
stream.on("finish", () => {
|
|
503
|
+
uploaded.delete();
|
|
504
|
+
resolve(fileId);
|
|
505
|
+
});
|
|
506
|
+
stream.on("error", () => {
|
|
507
|
+
uploaded.delete();
|
|
508
|
+
reject();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
bucket(options) {
|
|
513
|
+
if (!mongoose.connection.db) {
|
|
514
|
+
throw new Error("Database connection not established");
|
|
515
|
+
}
|
|
516
|
+
return options?.bucket ?? (options?.bucketName ? this.buckets[options.bucketName] ?? new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
|
|
517
|
+
bucketName: options.bucketName
|
|
518
|
+
}) : this.uploads);
|
|
519
|
+
}
|
|
520
|
+
resolveFileTypeCategory(mimetype) {
|
|
521
|
+
if (mimetype.startsWith("image/")) {
|
|
522
|
+
return "images";
|
|
523
|
+
}
|
|
524
|
+
if (mimetype.startsWith("video/")) {
|
|
525
|
+
return "videos";
|
|
526
|
+
}
|
|
527
|
+
if (mimetype.startsWith("audio/")) {
|
|
528
|
+
return "audio";
|
|
529
|
+
}
|
|
530
|
+
if (mimetype.startsWith("text/") || mimetype === "application/pdf" || mimetype === "application/msword" || mimetype === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || mimetype === "application/vnd.ms-excel" || mimetype === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || mimetype === "application/vnd.ms-powerpoint" || mimetype === "application/vnd.openxmlformats-officedocument.presentationml.presentation" || mimetype === "application/vnd.oasis.opendocument.text" || mimetype === "application/vnd.oasis.opendocument.spreadsheet" || mimetype === "application/vnd.oasis.opendocument.presentation" || mimetype === "application/rtf") {
|
|
531
|
+
return "documents";
|
|
532
|
+
}
|
|
533
|
+
return "others";
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
export {
|
|
537
|
+
GridFSService,
|
|
538
|
+
MinioService,
|
|
539
|
+
S3_CREDENTIALS,
|
|
540
|
+
StorageService,
|
|
541
|
+
createLazyStorage,
|
|
542
|
+
env,
|
|
543
|
+
resources,
|
|
544
|
+
translate
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
//# debugId=23CE4C057023254764756E2164756E21
|
|
548
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/env.ts", "../src/i18n/i18n.ts", "../src/i18n/resources/index.ts", "../src/services/storage.service.ts", "../src/services/minio.service.ts", "../src/services/create-lazy-storage.ts", "../src/services/gridfs.service.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { logger } from '@nxgt/shared-logging';\nimport { z } from 'zod';\n\n// Define schema\nconst envSchema = z.object({\n\t// S3\n\tS3_ENDPOINT: z.string().default('http://host.docker.internal:9000'),\n\tS3_USER: z.string().default('minio'),\n\tS3_PASSWORD: z.string().default('minio123'),\n\tS3_BUCKET: z.string().default('uploads'),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\n// Parse and validate environment variables\nconst parseEnv = (value: Record<string, unknown>): Env => {\n\tconst result = envSchema.safeParse(value);\n\n\tif (!result.success) {\n\t\tlogger.error('❌ Invalid environment variables:');\n\t\tlogger.error(result.error.issues);\n\t\tthrow new Error('Invalid environment variables');\n\t}\n\n\treturn result.data;\n};\n\n// Export validated and typed environment variables\nexport const env = parseEnv({\n\tS3_ENDPOINT: Bun.env.S3_ENDPOINT,\n\tS3_USER: Bun.env.S3_USER,\n\tS3_PASSWORD: Bun.env.S3_PASSWORD,\n\tS3_BUCKET: Bun.env.S3_BUCKET,\n});\n",
|
|
6
|
+
"import { createTranslator } from '@nxgt/i18n';\nimport { resources } from './resources';\nimport type { StorageLocaleKey } from './types';\n\nexport const translate = createTranslator<StorageLocaleKey>(resources);\n",
|
|
7
|
+
"import { resources as shared } from '@nxgt/i18n';\nimport en from './en.json';\nimport fr from './fr.json';\n\nexport const resources = {\n\ten: {\n\t\tstorage: en,\n\t\t...shared.en,\n\t},\n\tfr: {\n\t\tstorage: fr,\n\t\t...shared.fr,\n\t},\n};\n",
|
|
8
|
+
"import { omit, STRINGS_UTILS } from '@nxgt/shared/helpers';\nimport { CustomException } from '@nxgt/shared-exceptions';\nimport { getLogger } from '@nxgt/shared-logging';\nimport {\n\tfetch,\n\tS3Client,\n\ttype S3FilePresignOptions,\n\ttype S3ListObjectsOptions,\n\ttype S3Options,\n} from 'bun';\nimport { env } from '../env';\nimport { translate } from '../i18n';\nimport { MinioService } from './minio.service';\n\nexport const S3_CREDENTIALS = {\n\tendpoint: env.S3_ENDPOINT,\n\tbucket: env.S3_BUCKET,\n\taccessKeyId: env.S3_USER,\n\tsecretAccessKey: env.S3_PASSWORD,\n};\n\nexport type S3ClientWriteBody = Parameters<S3Client['write']>[1];\n\nexport class StorageService {\n\ts3: S3Client;\n\tprivate bucket: string;\n\tprivate logger = getLogger();\n\tminio: MinioService;\n\n\tconstructor(bucket?: string) {\n\t\tthis.bucket = bucket ?? S3_CREDENTIALS.bucket;\n\t\tthis.s3 = new S3Client({\n\t\t\t...S3_CREDENTIALS,\n\t\t\tbucket: this.bucket,\n\t\t});\n\t\tthis.minio = new MinioService(bucket);\n\t\t// Unawaited on purpose — the constructor cannot block — but an\n\t\t// unhandled rejection here would take the process down.\n\t\tthis.minio.ensureBucketExists(bucket).catch((error) => {\n\t\t\tthis.logger.error(error);\n\t\t});\n\t}\n\n\tasync write(key: string, body: S3ClientWriteBody, options: S3Options = {}) {\n\t\ttry {\n\t\t\treturn this.s3.write(key, body, { ...options });\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.write-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync list(input: S3ListObjectsOptions = {}, options: S3Options = {}) {\n\t\ttry {\n\t\t\treturn this.s3.list(input, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.list-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tfile(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\treturn this.s3.file(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.file-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync exists(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\treturn this.s3.exists(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.exists-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tpresing(key: string, options: S3FilePresignOptions = {}) {\n\t\ttry {\n\t\t\treturn this.s3.presign(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.presign-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync delete(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\tawait this.ensureExists(key, options);\n\t\t\treturn this.s3.delete(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.delete-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync size(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\tawait this.ensureExists(key, options);\n\t\t\treturn this.s3.size(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.size-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync stat(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\tawait this.ensureExists(key, options);\n\t\t\treturn this.s3.stat(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.stat-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync unlink(key: string, options: S3Options = {}) {\n\t\ttry {\n\t\t\tawait this.ensureExists(key, options);\n\t\t\treturn this.s3.unlink(key, options);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.unlink-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tasync fetch(\n\t\tinput: string,\n\t\t{\n\t\t\tbucket,\n\t\t\t...options\n\t\t}: BunFetchRequestInit & Pick<S3Options, 'bucket'> = {},\n\t) {\n\t\ttry {\n\t\t\tawait this.ensureExists(input, { bucket });\n\t\t\treturn fetch(\n\t\t\t\tSTRINGS_UTILS.normalizeUrl(`s3://${bucket ?? this.bucket}/${input}`),\n\t\t\t\t{\n\t\t\t\t\t...options,\n\t\t\t\t\ts3: omit(S3_CREDENTIALS, ['bucket']),\n\t\t\t\t},\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tthis.logger.error(error);\n\t\t\tthrow CustomException.internal({\n\t\t\t\tmessage: 'storage.errors.fetch-failed',\n\t\t\t\tdebugMessage: (error as Error).message,\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate async ensureExists(key: string, options: S3Options = {}) {\n\t\tif (!(await this.s3.exists(key, options))) {\n\t\t\tthrow CustomException.notFound({\n\t\t\t\tmessage: translate('storage.errors.file-not-found', { key }) as any,\n\t\t\t});\n\t\t}\n\t}\n}\n",
|
|
9
|
+
"import { getLogger } from '@nxgt/shared-logging';\nimport { Archive } from 'bun';\nimport {\n\tClient,\n\tCopyDestinationOptions,\n\tCopySourceOptions,\n\ttype ICopyDestinationOptions,\n\ttype ICopySourceOptions,\n\ttype ItemBucketMetadata,\n\ttype RemoveOptions,\n} from 'minio';\n\n// Not re-exported from the `minio` package root (only from its internal\n// type module), so mirrored here from its actual shape.\ntype PreSignRequestParams = Record<string, string>;\n\n// Same problem, one step further: these result shapes are only reachable\n// through minio's internal type module, so an inferred return type makes `tsc`\n// write `node_modules/minio/dist/main/internal/type` into the emitted `.d.ts`\n// and fail with TS2883 — that path does not exist for a consumer installing\n// from the registry. Naming them through the public `Client` keeps the\n// reference on minio's entry point instead of duplicating the shapes.\ntype PutObjectResult = Awaited<ReturnType<Client['putObject']>>;\ntype CopyObjectResult = Awaited<ReturnType<Client['copyObject']>>;\ntype ListObjectsResult = ReturnType<Client['listObjects']>;\n\nimport { S3_CREDENTIALS, type S3ClientWriteBody } from './storage.service';\n\nexport type MinioPutBody = Parameters<Client['putObject']>[2];\nexport type GetObjectOptions = Parameters<Client['getObject']>[2];\nexport type ListObjectQueryOptions = Parameters<Client['listObjects']>[3];\n\nexport class MinioService {\n\tminio: Client;\n\tprivate bucket: string;\n\n\tprivate logger = getLogger();\n\n\tconstructor(bucket?: string) {\n\t\tthis.bucket = bucket ?? S3_CREDENTIALS.bucket;\n\t\tthis.minio = new Client({\n\t\t\tendPoint: S3_CREDENTIALS.endpoint\n\t\t\t\t.replace(/https?:\\/\\//, '')\n\t\t\t\t.replace(/:.*/, ''),\n\t\t\taccessKey: S3_CREDENTIALS.accessKeyId,\n\t\t\tsecretKey: S3_CREDENTIALS.secretAccessKey,\n\t\t\tuseSSL: S3_CREDENTIALS.endpoint?.startsWith('https'),\n\t\t\tport: S3_CREDENTIALS.endpoint\n\t\t\t\t? parseInt(S3_CREDENTIALS.endpoint.split(':').pop() || '9000', 10)\n\t\t\t\t: 9000,\n\t\t});\n\t}\n\n\tasync ensureBucketExists(bucket?: string) {\n\t\tconst exists = await this.bucketExists(bucket);\n\t\tif (!exists) {\n\t\t\tawait this.makeBucket(bucket);\n\t\t}\n\t}\n\n\tasync convert(body: S3ClientWriteBody): Promise<MinioPutBody> {\n\t\tif (\n\t\t\tbody instanceof Blob ||\n\t\t\tbody instanceof File ||\n\t\t\tbody instanceof Response ||\n\t\t\tbody instanceof Request\n\t\t) {\n\t\t\tconst arrayBuffer = await body.arrayBuffer();\n\t\t\treturn Buffer.from(arrayBuffer);\n\t\t}\n\t\tif (body instanceof Archive) {\n\t\t\tconst arrayBuffer = await (await body.blob()).arrayBuffer();\n\t\t\treturn Buffer.from(arrayBuffer);\n\t\t}\n\t\tif (body instanceof ArrayBuffer || body instanceof SharedArrayBuffer) {\n\t\t\treturn Buffer.from(body);\n\t\t}\n\t\tif (typeof body === 'string' || body instanceof Buffer) {\n\t\t\treturn body;\n\t\t}\n\t\treturn Buffer.from(\n\t\t\tbody.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength),\n\t\t);\n\t}\n\n\tasync putObject(\n\t\tkey: string,\n\t\tbody: MinioPutBody,\n\t\tsize?: number,\n\t\tmetadata: ItemBucketMetadata = {},\n\t): Promise<PutObjectResult> {\n\t\tthis.logger.info(`Putting object ${key} to bucket ${this.bucket}`);\n\t\treturn this.minio.putObject(this.bucket, key, body, size, metadata);\n\t}\n\n\tasync getObject(key: string, options: GetObjectOptions = {}) {\n\t\tthis.logger.info(`Getting object ${key} from bucket ${this.bucket}`);\n\t\treturn this.minio.getObject(this.bucket, key, options);\n\t}\n\n\tasync fPutObject(\n\t\tkey: string,\n\t\tfilePath: string,\n\t\tmetadata: ItemBucketMetadata = {},\n\t): Promise<PutObjectResult> {\n\t\tthis.logger.info(\n\t\t\t`Putting file ${filePath} as object ${key} to bucket ${this.bucket}`,\n\t\t);\n\t\treturn this.minio.fPutObject(this.bucket, key, filePath, metadata);\n\t}\n\n\tasync fGetObject(\n\t\tkey: string,\n\t\tfilePath: string,\n\t\toptions: GetObjectOptions = {},\n\t) {\n\t\tthis.logger.info(\n\t\t\t`Getting object ${key} from bucket ${this.bucket} to file ${filePath}`,\n\t\t);\n\t\treturn this.minio.fGetObject(this.bucket, key, filePath, options);\n\t}\n\n\tasync findUploadId(key: string) {\n\t\tthis.logger.info(\n\t\t\t`Finding upload ID for object ${key} in bucket ${this.bucket}`,\n\t\t);\n\t\treturn this.minio.findUploadId(this.bucket, key);\n\t}\n\n\tasync copyObject(\n\t\tsource: Omit<ICopySourceOptions, 'Bucket'> & { Bucket?: string },\n\t\tdestination: Omit<ICopyDestinationOptions, 'Bucket'> & { Bucket?: string },\n\t): Promise<CopyObjectResult> {\n\t\tthis.logger.info(\n\t\t\t`Copying object from ${source.Object} to ${destination.Object} in bucket ${this.bucket}`,\n\t\t);\n\t\treturn this.minio.copyObject(\n\t\t\tnew CopySourceOptions({\n\t\t\t\t...source,\n\t\t\t\tBucket: source.Bucket ?? this.bucket,\n\t\t\t}),\n\t\t\tnew CopyDestinationOptions({\n\t\t\t\t...destination,\n\t\t\t\tBucket: destination.Bucket ?? this.bucket,\n\t\t\t}),\n\t\t);\n\t}\n\n\tasync deleteObject(key: string, options?: RemoveOptions) {\n\t\tthis.logger.info(`Deleting object ${key} from bucket ${this.bucket}`);\n\t\treturn this.minio.removeObject(this.bucket, key, options);\n\t}\n\n\tasync listObjects(\n\t\tprefix: string = '',\n\t\trecursive: boolean = false,\n\t\toptions?: ListObjectQueryOptions,\n\t): Promise<ListObjectsResult> {\n\t\tthis.logger.info(\n\t\t\t`Listing objects in bucket ${this.bucket} with prefix ${prefix} and recursive ${recursive}`,\n\t\t);\n\t\treturn this.minio.listObjects(this.bucket, prefix, recursive, options);\n\t}\n\n\tasync listObjectsV2(\n\t\tprefix: string = '',\n\t\trecursive: boolean = false,\n\t\tstartAfter?: string,\n\t) {\n\t\tthis.logger.info(\n\t\t\t`Listing objects (V2) in bucket ${this.bucket} with prefix ${prefix} and recursive ${recursive}`,\n\t\t);\n\t\treturn this.minio.listObjectsV2(this.bucket, prefix, recursive, startAfter);\n\t}\n\n\t/**\n\t * Presigned GET URL supporting response-header overrides (e.g.\n\t * `response-content-disposition`) — Bun's native `S3Client.presign()`\n\t * (used by `StorageService.presing()`) has no equivalent, so callers that\n\t * need per-request Content-Disposition/Content-Type overrides (attachment\n\t * vs inline downloads) must go through this instead.\n\t */\n\tasync presignedGetObject(\n\t\tkey: string,\n\t\texpiresIn = 7 * 24 * 60 * 60,\n\t\trespHeaders: PreSignRequestParams = {},\n\t) {\n\t\tthis.logger.info(\n\t\t\t`Presigning GET for object ${key} in bucket ${this.bucket}`,\n\t\t);\n\t\treturn this.minio.presignedGetObject(\n\t\t\tthis.bucket,\n\t\t\tkey,\n\t\t\texpiresIn,\n\t\t\trespHeaders,\n\t\t);\n\t}\n\n\tasync presignedPutObject(key: string, expiresIn = 7 * 24 * 60 * 60) {\n\t\tthis.logger.info(\n\t\t\t`Presigning PUT for object ${key} in bucket ${this.bucket}`,\n\t\t);\n\t\treturn this.minio.presignedPutObject(this.bucket, key, expiresIn);\n\t}\n\n\tasync bucketExists(bucket?: string) {\n\t\tthis.logger.info(`Checking if bucket ${bucket ?? this.bucket} exists`);\n\t\treturn this.minio.bucketExists(bucket ?? this.bucket);\n\t}\n\n\tasync makeBucket(bucket?: string) {\n\t\tthis.logger.info(`Creating bucket ${bucket ?? this.bucket}`);\n\t\treturn this.minio.makeBucket(bucket ?? this.bucket);\n\t}\n}\n",
|
|
10
|
+
"import { StorageService } from './storage.service';\n\n/**\n * StorageService's constructor fires an unawaited bucket-existence check\n * against S3/MinIO; constructing it eagerly would pay that cost (and risk\n * an unhandled rejection if storage is unreachable) even when no upload\n * feature backed by this bucket is ever touched. Returns a process-lifetime\n * singleton getter — must be bound to a module-level `const`, not a class\n * field, so it survives across per-request service instances.\n */\nexport function createLazyStorage(bucket: string) {\n\tlet storage: StorageService | undefined;\n\treturn function getStorage() {\n\t\tif (!storage) {\n\t\t\tstorage = new StorageService(bucket);\n\t\t}\n\t\treturn storage;\n\t};\n}\n",
|
|
11
|
+
"import { createReadStream } from 'node:fs';\nimport type { CursorPaginationParams } from '@nxgt/shared/helpers';\nimport { CustomException } from '@nxgt/shared-exceptions';\nimport { type mongo, mongoose } from '@nxgt/shared-mongo';\nimport { ObjectId } from 'bson';\nimport { pick } from 'lodash';\n\nconst MAX_SIZE = 100;\n\nexport type GridFSBucketNames =\n\t| 'avatars'\n\t| 'uploads'\n\t| 'images'\n\t| 'videos'\n\t| 'files'\n\t| string;\n\nexport interface IBucketOptions {\n\tbucketName?: GridFSBucketNames;\n\tbucket?: mongoose.mongo.GridFSBucket;\n}\n\nexport class GridFSService {\n\tuploads: mongoose.mongo.GridFSBucket;\n\tavatars: mongoose.mongo.GridFSBucket;\n\timages: mongoose.mongo.GridFSBucket;\n\tvideos: mongoose.mongo.GridFSBucket;\n\tfiles: mongoose.mongo.GridFSBucket;\n\n\tbuckets: Record<GridFSBucketNames, mongoose.mongo.GridFSBucket>;\n\n\tconstructor() {\n\t\tif (!mongoose.connection.db) {\n\t\t\tthrow new Error('Database connection not established');\n\t\t}\n\t\tthis.uploads = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\tbucketName: 'uploads',\n\t\t});\n\t\tthis.avatars = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\tbucketName: 'avatars',\n\t\t});\n\t\tthis.images = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\tbucketName: 'images',\n\t\t});\n\t\tthis.videos = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\tbucketName: 'videos',\n\t\t});\n\t\tthis.files = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\tbucketName: 'files',\n\t\t});\n\n\t\tthis.buckets = {\n\t\t\tavatars: this.avatars,\n\t\t\tuploads: this.uploads,\n\t\t\timages: this.images,\n\t\t\tvideos: this.videos,\n\t\t\tfiles: this.files,\n\t\t};\n\t}\n\n\tasync paginate(\n\t\tfilter?: mongoose.mongo.Filter<mongoose.mongo.GridFSFile>,\n\t\toptions?: CursorPaginationParams & IBucketOptions,\n\t) {\n\t\tconst after = options?.after;\n\t\tconst before = options?.before;\n\n\t\t// Validate first and last\n\t\tconst first =\n\t\t\toptions?.first && options.first > 0 ? options.first : undefined;\n\t\tconst last = options?.last && options.last > 0 ? options.last : undefined;\n\n\t\tif (first && last) {\n\t\t\tthrow CustomException.badRequest({\n\t\t\t\tmessage: 'errors.could-not-use-first-and-last-together',\n\t\t\t});\n\t\t}\n\n\t\t// Build cursor filter\n\t\tconst cursorFilter: mongoose.mongo.Filter<mongoose.mongo.GridFSFile> = {\n\t\t\t...filter,\n\t\t};\n\n\t\tif (after) {\n\t\t\tcursorFilter._id = {\n\t\t\t\t...(cursorFilter._id ?? {}),\n\t\t\t\t$gt: new ObjectId(after),\n\t\t\t};\n\t\t}\n\n\t\tif (before) {\n\t\t\tcursorFilter._id = {\n\t\t\t\t...(cursorFilter._id ?? {}),\n\t\t\t\t$lt: new ObjectId(before),\n\t\t\t};\n\t\t}\n\n\t\t// Determine limit and sort order\n\t\tconst limit = Math.min(first ?? last ?? MAX_SIZE, MAX_SIZE);\n\t\t// Only `last` flips the read order. sellix-monorepo flipped it for\n\t\t// `before` too and then never reversed the result back, so a\n\t\t// `before`-only page came out newest-first while every other page came\n\t\t// out oldest-first.\n\t\tconst querySort: { _id: 1 | -1 } = { _id: last ? -1 : 1 };\n\n\t\t// Build query\n\t\tconst bucket = this.bucket(options);\n\t\tconst query = bucket.find(cursorFilter).sort(querySort);\n\n\t\t// Apply limit only if specified (fetch one extra to determine if there are more pages)\n\t\tquery.limit(Math.max(limit || 0, 1) + 1);\n\n\t\tconst docs = await query.toArray();\n\n\t\t// If using 'last', reverse the results back to normal order\n\t\tconst hasExtraDoc = docs.length > limit;\n\t\tconst resultDocs = hasExtraDoc ? docs.slice(0, limit) : docs;\n\t\tif (last) {\n\t\t\tresultDocs.reverse();\n\t\t}\n\n\t\t// Get total count\n\t\tconst totalElements = await bucket.find(filter ?? {}).count();\n\n\t\t// Determine cursors and page info\n\t\tconst startCursor =\n\t\t\tresultDocs.length > 0 ? resultDocs?.[0]?._id.toString() : null;\n\t\tconst endCursor =\n\t\t\tresultDocs.length > 0\n\t\t\t\t? resultDocs?.[resultDocs.length - 1]?._id.toString()\n\t\t\t\t: null;\n\n\t\treturn {\n\t\t\tdata: resultDocs,\n\t\t\tmetadata: {\n\t\t\t\tstartCursor,\n\t\t\t\tendCursor,\n\t\t\t\thasNextPage: hasExtraDoc,\n\t\t\t\ttotalElements,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync findById(\n\t\tid: string,\n\t\toptions?: IBucketOptions,\n\t): Promise<mongoose.mongo.GridFSFile | null> {\n\t\treturn this.bucket(options)\n\t\t\t.find({ _id: new mongoose.mongo.ObjectId(id) })\n\t\t\t.limit(1)\n\t\t\t.tryNext();\n\t}\n\n\tasync find(\n\t\tfilter?: mongoose.mongo.Filter<mongoose.mongo.GridFSFile>,\n\t\toptions?: mongoose.mongo.FindOptions & IBucketOptions,\n\t) {\n\t\treturn this.bucket(pick(options, ['bucketName', 'bucket']))\n\t\t\t.find(filter, options)\n\t\t\t.toArray();\n\t}\n\n\tasync delete(id: string, options?: IBucketOptions): Promise<void> {\n\t\tawait this.bucket(options).delete(new mongoose.mongo.ObjectId(id));\n\t}\n\n\tasync download(\n\t\tid: string,\n\t\t{ user, ...options }: IBucketOptions & { user?: string } = {},\n\t) {\n\t\tconst gridFSFile = await this.findById(id, options);\n\t\tif (!gridFSFile) {\n\t\t\tthrow CustomException.notFound({\n\t\t\t\tmessage: 'files.errors.file-not-found',\n\t\t\t});\n\t\t}\n\t\tif (mongoose.connection.db) {\n\t\t\tawait mongoose.connection.db\n\t\t\t\t.collection(`${options.bucketName ?? 'uploads'}.files`)\n\t\t\t\t.findOneAndUpdate(\n\t\t\t\t\t{ _id: new mongoose.mongo.ObjectId(id) },\n\t\t\t\t\t{\n\t\t\t\t\t\t$set: {\n\t\t\t\t\t\t\t'metadata.lastOpenedBy': user,\n\t\t\t\t\t\t\t'metadata.lastOpenedDate': new Date(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t\treturn this.bucket(options).openDownloadStream(\n\t\t\tnew mongoose.mongo.ObjectId(id),\n\t\t);\n\t}\n\n\tasync rename(\n\t\tid: string,\n\t\tname: string,\n\t\t{ user, ...options }: IBucketOptions & { user?: string } = {},\n\t): Promise<mongo.GridFSFile> {\n\t\tconst gridFSFile = await this.findById(id, options);\n\t\tif (!gridFSFile) {\n\t\t\tthrow CustomException.notFound({\n\t\t\t\tmessage: 'files.errors.file-not-found',\n\t\t\t});\n\t\t}\n\t\tawait this.bucket(options).rename(new mongoose.mongo.ObjectId(id), name);\n\t\tif (mongoose.connection.db) {\n\t\t\tawait mongoose.connection.db\n\t\t\t\t.collection(`${options.bucketName ?? 'uploads'}.files`)\n\t\t\t\t.findOneAndUpdate(\n\t\t\t\t\t{ _id: new mongoose.mongo.ObjectId(id) },\n\t\t\t\t\t{\n\t\t\t\t\t\t$set: {\n\t\t\t\t\t\t\t'metadata.lastModifiedBy': user,\n\t\t\t\t\t\t\t'metadata.lastModifiedDate': new Date(),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t\tconst result = await this.findById(id, options);\n\t\tif (!result) {\n\t\t\tthrow CustomException.notFound({\n\t\t\t\tmessage: 'files.errors.file-not-found-after-rename',\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync upload(file: File, options?: IBucketOptions & { metadata?: object }) {\n\t\tconst fileId = new ObjectId();\n\n\t\tconst filePath = `uploads/tmp/${fileId.toHexString()}-${file.name}`;\n\n\t\tawait Bun.write(filePath, file);\n\n\t\tconst uploaded = Bun.file(filePath);\n\n\t\tconst metadata = {\n\t\t\t...(options?.metadata ?? {}),\n\t\t\tcontentType: uploaded.type,\n\t\t\tmimetype: uploaded.type,\n\t\t\tcategory: this.resolveFileTypeCategory(uploaded.type),\n\t\t};\n\n\t\tconst stream = this.bucket(options).openUploadStream(file.name, {\n\t\t\tid: fileId as any,\n\t\t\tmetadata,\n\t\t});\n\n\t\tcreateReadStream(filePath).pipe(stream);\n\n\t\treturn new Promise<ObjectId>((resolve, reject) => {\n\t\t\tstream.on('finish', () => {\n\t\t\t\tuploaded.delete();\n\t\t\t\tresolve(fileId);\n\t\t\t});\n\t\t\tstream.on('error', () => {\n\t\t\t\tuploaded.delete();\n\t\t\t\treject();\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate bucket(options?: IBucketOptions) {\n\t\tif (!mongoose.connection.db) {\n\t\t\tthrow new Error('Database connection not established');\n\t\t}\n\t\treturn (\n\t\t\toptions?.bucket ??\n\t\t\t(options?.bucketName\n\t\t\t\t? (this.buckets[options.bucketName] ??\n\t\t\t\t\tnew mongoose.mongo.GridFSBucket(mongoose.connection.db, {\n\t\t\t\t\t\tbucketName: options.bucketName,\n\t\t\t\t\t}))\n\t\t\t\t: this.uploads)\n\t\t);\n\t}\n\n\t/**\n\t * Determines the category for a given file MIME type.\n\t * This is a private helper method.\n\t * @param mimetype The MIME type of the file.\n\t * @returns A string representing the file type category (e.g., 'images', 'documents', 'other').\n\t */\n\tprivate resolveFileTypeCategory(mimetype: string): string {\n\t\tif (mimetype.startsWith('image/')) {\n\t\t\treturn 'images';\n\t\t}\n\t\tif (mimetype.startsWith('video/')) {\n\t\t\treturn 'videos';\n\t\t}\n\t\tif (mimetype.startsWith('audio/')) {\n\t\t\treturn 'audio';\n\t\t}\n\t\tif (\n\t\t\tmimetype.startsWith('text/') ||\n\t\t\tmimetype === 'application/pdf' ||\n\t\t\tmimetype === 'application/msword' ||\n\t\t\tmimetype ===\n\t\t\t\t'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||\n\t\t\tmimetype === 'application/vnd.ms-excel' ||\n\t\t\tmimetype ===\n\t\t\t\t'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||\n\t\t\tmimetype === 'application/vnd.ms-powerpoint' ||\n\t\t\tmimetype ===\n\t\t\t\t'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||\n\t\t\tmimetype === 'application/vnd.oasis.opendocument.text' ||\n\t\t\tmimetype === 'application/vnd.oasis.opendocument.spreadsheet' ||\n\t\t\tmimetype === 'application/vnd.oasis.opendocument.presentation' ||\n\t\t\tmimetype === 'application/rtf'\n\t\t) {\n\t\t\treturn 'documents';\n\t\t}\n\t\treturn 'others';\n\t}\n}\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "23CE4C057023254764756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { StorageService } from './storage.service';
|
|
2
|
+
/**
|
|
3
|
+
* StorageService's constructor fires an unawaited bucket-existence check
|
|
4
|
+
* against S3/MinIO; constructing it eagerly would pay that cost (and risk
|
|
5
|
+
* an unhandled rejection if storage is unreachable) even when no upload
|
|
6
|
+
* feature backed by this bucket is ever touched. Returns a process-lifetime
|
|
7
|
+
* singleton getter — must be bound to a module-level `const`, not a class
|
|
8
|
+
* field, so it survives across per-request service instances.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createLazyStorage(bucket: string): () => StorageService;
|
|
11
|
+
//# sourceMappingURL=create-lazy-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-lazy-storage.d.ts","sourceRoot":"","sources":["../../src/services/create-lazy-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,wBAQ/C"}
|