-
Notifications
You must be signed in to change notification settings - Fork 2
/
mongodb-gridfs.ts
108 lines (90 loc) · 2.89 KB
/
mongodb-gridfs.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
* This example shows how to implement a storage using MongoDB/GridFS as a datasource
*
* See ./mongodb-gridfs-util.ts to have an example of file/directory upload to MongoDB/GridFS
*/
import type { Readable } from 'node:stream';
import { posix } from 'node:path';
// eslint-disable-next-line n/prefer-global/url
import { URL } from 'node:url';
import { fastify } from 'fastify';
import type { GridFSFile, FindCursor } from 'mongodb';
import { MongoClient, GridFSBucket } from 'mongodb';
import type { StorageOptions, StorageInfo, StreamRange } from '../src/send-stream';
import { Storage, StorageError } from '../src/send-stream';
const uri = 'mongodb://localhost:27017';
const dbName = 'test';
interface FileWithMetadata {
metadata?: {
mtimeMs?: number;
mimeType?: string;
mimeTypeCharset?: string;
etag?: string;
lastModified?: string;
};
}
type File = GridFSFile & FileWithMetadata;
class GridFSStorage extends Storage<string, File> {
constructor(readonly bucket: GridFSBucket, readonly opts?: StorageOptions) {
super(opts);
}
async open(path: string) {
const filename = posix.relative('/', decodeURIComponent(new URL(`http://localhost${ path }`).pathname));
const files = await (<FindCursor<File>> this.bucket.find({ filename }, { limit: 1 })).toArray();
if (files.length === 0) {
throw new StorageError(`filename ${ filename } not found`, path);
}
const [file] = files;
return {
attachedData: file,
fileName: file.filename,
mtimeMs: file.metadata?.mtimeMs ?? file.uploadDate.getTime(),
size: file.length,
mimeType: file.metadata?.mimeType,
mimeTypeCharset: file.metadata?.mimeTypeCharset,
lastModified: file.metadata?.lastModified,
etag: file.metadata?.etag,
};
}
createReadableStream(storageInfo: StorageInfo<File>, range: StreamRange | undefined, autoClose: boolean): Readable {
const result = this.bucket.openDownloadStream(
// eslint-disable-next-line no-underscore-dangle
storageInfo.attachedData._id,
range ? { start: range.start, end: range.end + 1 } : undefined,
);
if (autoClose) {
const onClose = () => {
result.off('end', onClose);
result.off('error', onClose);
result.destroy();
};
result.on('end', onClose);
result.on('error', onClose);
}
return result;
}
async close() {
// noop
}
}
const client = new MongoClient(uri);
const app = fastify({ exposeHeadRoutes: true });
client.connect()
.then(async () => {
const db = client.db(dbName);
const bucket = new GridFSBucket(db);
const storage = new GridFSStorage(bucket);
app.get('*', async (request, reply) => {
const result = await storage.prepareResponse(request.url, request.raw);
if (result.statusCode === 404) {
reply.callNotFound();
return;
}
await result.send(reply.raw);
});
await app.listen({ port: 3000 });
console.info('listening on http://localhost:3000');
})
.catch((err: unknown) => {
console.error(err);
});