gcs.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. const logger = require('@alias/logger')('growi:service:fileUploaderAws');
  2. const urljoin = require('url-join');
  3. const { Storage } = require('@google-cloud/storage');
  4. let _instance;
  5. module.exports = function(crowi) {
  6. const Uploader = require('./uploader');
  7. const { configManager } = crowi;
  8. const lib = new Uploader(crowi);
  9. function getGcsBucket() {
  10. return configManager.getConfig('crowi', 'gcs:bucket');
  11. }
  12. function getGcsInstance() {
  13. if (_instance == null) {
  14. const keyFilename = configManager.getConfig('crowi', 'gcs:apiKeyJsonPath');
  15. // see https://googleapis.dev/nodejs/storage/latest/Storage.html
  16. _instance = new Storage({ keyFilename });
  17. }
  18. return _instance;
  19. }
  20. function getFilePathOnStorage(attachment) {
  21. const namespace = configManager.getConfig('crowi', 'gcs:uploadNamespace');
  22. // const namespace = null;
  23. const dirName = (attachment.page != null)
  24. ? 'attachment'
  25. : 'user';
  26. const filePath = urljoin(namespace || '', dirName, attachment.fileName);
  27. return filePath;
  28. }
  29. /**
  30. * check file existence
  31. * @param {File} file https://googleapis.dev/nodejs/storage/latest/File.html
  32. */
  33. async function isFileExists(file) {
  34. // check file exists
  35. const res = await file.exists();
  36. return res[0];
  37. }
  38. lib.isValidUploadSettings = function() {
  39. return this.configManager.getConfig('crowi', 'gcs:apiKeyJsonPath') != null
  40. && this.configManager.getConfig('crowi', 'gcs:bucket') != null;
  41. };
  42. lib.canRespond = function() {
  43. // TODO retrieve bool by getConfig
  44. return true;
  45. };
  46. lib.respond = async function(res, attachment) {
  47. if (!this.getIsUploadable()) {
  48. throw new Error('GCS is not configured.');
  49. }
  50. const gcs = getGcsInstance();
  51. const myBucket = gcs.bucket(getGcsBucket());
  52. const filePath = getFilePathOnStorage(attachment);
  53. const file = myBucket.file(filePath);
  54. // issue signed url for 30 seconds
  55. // https://cloud.google.com/storage/docs/access-control/signed-urls
  56. const signedUrl = await file.getSignedUrl({
  57. action: 'read',
  58. expires: Date.now() + 30 * 1000,
  59. });
  60. return res.redirect(signedUrl);
  61. };
  62. lib.deleteFile = async function(attachment) {
  63. const filePath = getFilePathOnStorage(attachment);
  64. return lib.deleteFileByFilePath(filePath);
  65. };
  66. lib.deleteFileByFilePath = async function(filePath) {
  67. if (!this.getIsUploadable()) {
  68. throw new Error('GCS is not configured.');
  69. }
  70. const gcs = getGcsInstance();
  71. const myBucket = gcs.bucket(getGcsBucket());
  72. const file = myBucket.file(filePath);
  73. // check file exists
  74. const isExists = await isFileExists(file);
  75. if (!isExists) {
  76. logger.warn(`Any object that relate to the Attachment (${filePath}) does not exist in GCS`);
  77. return;
  78. }
  79. return file.delete();
  80. };
  81. lib.uploadFile = function(fileStream, attachment) {
  82. if (!this.getIsUploadable()) {
  83. throw new Error('GCS is not configured.');
  84. }
  85. logger.debug(`File uploading: fileName=${attachment.fileName}`);
  86. const gcs = getGcsInstance();
  87. const myBucket = gcs.bucket(getGcsBucket());
  88. const filePath = getFilePathOnStorage(attachment);
  89. const options = {
  90. destination: filePath,
  91. };
  92. return myBucket.upload(fileStream.path, options);
  93. };
  94. /**
  95. * Find data substance
  96. *
  97. * @param {Attachment} attachment
  98. * @return {stream.Readable} readable stream
  99. */
  100. lib.findDeliveryFile = async function(attachment) {
  101. if (!this.getIsReadable()) {
  102. throw new Error('GCS is not configured.');
  103. }
  104. const gcs = getGcsInstance();
  105. const myBucket = gcs.bucket(getGcsBucket());
  106. const filePath = getFilePathOnStorage(attachment);
  107. const file = myBucket.file(filePath);
  108. // check file exists
  109. const isExists = await isFileExists(file);
  110. if (!isExists) {
  111. throw new Error(`Any object that relate to the Attachment (${filePath}) does not exist in GCS`);
  112. }
  113. let stream;
  114. try {
  115. stream = file.createReadStream();
  116. }
  117. catch (err) {
  118. logger.error(err);
  119. throw new Error(`Coudn't get file from AWS for the Attachment (${attachment._id.toString()})`);
  120. }
  121. // return stream.Readable
  122. return stream;
  123. };
  124. /**
  125. * check the file size limit
  126. *
  127. * In detail, the followings are checked.
  128. * - per-file size limit (specified by MAX_FILE_SIZE)
  129. */
  130. lib.checkLimit = async(uploadFileSize) => {
  131. const maxFileSize = crowi.configManager.getConfig('crowi', 'app:maxFileSize');
  132. const gcsTotalLimit = crowi.configManager.getConfig('crowi', 'app:fileUploadTotalLimit');
  133. return lib.doCheckLimit(uploadFileSize, maxFileSize, gcsTotalLimit);
  134. };
  135. return lib;
  136. };