gridfs.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { Readable } from 'stream';
  2. import util from 'util';
  3. import mongoose from 'mongoose';
  4. import loggerFactory from '~/utils/logger';
  5. import { configManager } from '../config-manager';
  6. import { AbstractFileUploader, type SaveFileParam } from './file-uploader';
  7. const logger = loggerFactory('growi:service:fileUploaderGridfs');
  8. // TODO: rewrite this module to be a type-safe implementation
  9. class GridfsFileUploader extends AbstractFileUploader {
  10. /**
  11. * @inheritdoc
  12. */
  13. override isValidUploadSettings(): boolean {
  14. throw new Error('Method not implemented.');
  15. }
  16. /**
  17. * @inheritdoc
  18. */
  19. override saveFile(param: SaveFileParam) {
  20. throw new Error('Method not implemented.');
  21. }
  22. /**
  23. * @inheritdoc
  24. */
  25. override deleteFiles() {
  26. throw new Error('Method not implemented.');
  27. }
  28. /**
  29. * @inheritdoc
  30. */
  31. override respond(res: Response, attachment: Response): void {
  32. throw new Error('Method not implemented.');
  33. }
  34. }
  35. module.exports = function(crowi) {
  36. const lib = new GridfsFileUploader(crowi);
  37. const COLLECTION_NAME = 'attachmentFiles';
  38. const CHUNK_COLLECTION_NAME = `${COLLECTION_NAME}.chunks`;
  39. // instantiate mongoose-gridfs
  40. const { createModel } = require('mongoose-gridfs');
  41. const AttachmentFile = createModel({
  42. modelName: COLLECTION_NAME,
  43. bucketName: COLLECTION_NAME,
  44. connection: mongoose.connection,
  45. });
  46. // get Collection instance of chunk
  47. const chunkCollection = mongoose.connection.collection(CHUNK_COLLECTION_NAME);
  48. // create promisified method
  49. AttachmentFile.promisifiedWrite = util.promisify(AttachmentFile.write).bind(AttachmentFile);
  50. AttachmentFile.promisifiedUnlink = util.promisify(AttachmentFile.unlink).bind(AttachmentFile);
  51. lib.isValidUploadSettings = function() {
  52. return true;
  53. };
  54. (lib as any).deleteFile = async function(attachment) {
  55. let filenameValue = attachment.fileName;
  56. if (attachment.filePath != null) { // backward compatibility for v3.3.x or below
  57. filenameValue = attachment.filePath;
  58. }
  59. const attachmentFile = await AttachmentFile.findOne({ filename: filenameValue });
  60. if (attachmentFile == null) {
  61. logger.warn(`Any AttachmentFile that relate to the Attachment (${attachment._id.toString()}) does not exist in GridFS`);
  62. return;
  63. }
  64. return AttachmentFile.promisifiedUnlink({ _id: attachmentFile._id });
  65. };
  66. (lib as any).deleteFiles = async function(attachments) {
  67. const filenameValues = attachments.map((attachment) => {
  68. return attachment.fileName;
  69. });
  70. const fileIdObjects = await AttachmentFile.find({ filename: { $in: filenameValues } }, { _id: 1 });
  71. const idsRelatedFiles = fileIdObjects.map((obj) => { return obj._id });
  72. return Promise.all([
  73. AttachmentFile.deleteMany({ filename: { $in: filenameValues } }),
  74. chunkCollection.deleteMany({ files_id: { $in: idsRelatedFiles } }),
  75. ]);
  76. };
  77. /**
  78. * get size of data uploaded files using (Promise wrapper)
  79. */
  80. // const getCollectionSize = () => {
  81. // return new Promise((resolve, reject) => {
  82. // chunkCollection.stats((err, data) => {
  83. // if (err) {
  84. // // return 0 if not exist
  85. // if (err.errmsg.includes('not found')) {
  86. // return resolve(0);
  87. // }
  88. // return reject(err);
  89. // }
  90. // return resolve(data.size);
  91. // });
  92. // });
  93. // };
  94. /**
  95. * check the file size limit
  96. *
  97. * In detail, the followings are checked.
  98. * - per-file size limit (specified by MAX_FILE_SIZE)
  99. * - mongodb(gridfs) size limit (specified by MONGO_GRIDFS_TOTAL_LIMIT)
  100. */
  101. (lib as any).checkLimit = async function(uploadFileSize) {
  102. const maxFileSize = configManager.getConfig('crowi', 'app:maxFileSize');
  103. const totalLimit = lib.getFileUploadTotalLimit();
  104. return lib.doCheckLimit(uploadFileSize, maxFileSize, totalLimit);
  105. };
  106. (lib as any).uploadAttachment = async function(fileStream, attachment) {
  107. logger.debug(`File uploading: fileName=${attachment.fileName}`);
  108. return AttachmentFile.promisifiedWrite(
  109. {
  110. filename: attachment.fileName,
  111. contentType: attachment.fileFormat,
  112. },
  113. fileStream,
  114. );
  115. };
  116. lib.saveFile = async function({ filePath, contentType, data }) {
  117. const readable = new Readable();
  118. readable.push(data);
  119. readable.push(null); // EOF
  120. return AttachmentFile.promisifiedWrite(
  121. {
  122. filename: filePath,
  123. contentType,
  124. },
  125. readable,
  126. );
  127. };
  128. /**
  129. * Find data substance
  130. *
  131. * @param {Attachment} attachment
  132. * @return {stream.Readable} readable stream
  133. */
  134. (lib as any).findDeliveryFile = async function(attachment) {
  135. let filenameValue = attachment.fileName;
  136. if (attachment.filePath != null) { // backward compatibility for v3.3.x or below
  137. filenameValue = attachment.filePath;
  138. }
  139. const attachmentFile = await AttachmentFile.findOne({ filename: filenameValue });
  140. if (attachmentFile == null) {
  141. throw new Error(`Any AttachmentFile that relate to the Attachment (${attachment._id.toString()}) does not exist in GridFS`);
  142. }
  143. // return stream.Readable
  144. return AttachmentFile.read({ _id: attachmentFile._id });
  145. };
  146. /**
  147. * List files in storage
  148. */
  149. (lib as any).listFiles = async function() {
  150. const attachmentFiles = await AttachmentFile.find();
  151. return attachmentFiles.map(({ filename: name, length: size }) => ({
  152. name, size,
  153. }));
  154. };
  155. return lib;
  156. };