avatars.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { Meteor } from 'meteor/meteor';
  3. import { FilesCollection } from 'meteor/ostrio:files';
  4. import { formatFleURL } from 'meteor/ostrio:files/lib';
  5. import { isFileValid } from './fileValidation';
  6. import { createBucket } from './lib/grid/createBucket';
  7. import { TAPi18n } from '/imports/i18n';
  8. import fs from 'fs';
  9. import path from 'path';
  10. import FileStoreStrategyFactory, { FileStoreStrategyFilesystem, FileStoreStrategyGridFs, STORAGE_NAME_FILESYSTEM } from '/models/lib/fileStoreStrategy';
  11. const filesize = require('filesize');
  12. let avatarsUploadExternalProgram;
  13. let avatarsUploadMimeTypes = [];
  14. let avatarsUploadSize = 72000;
  15. let avatarsBucket;
  16. let storagePath;
  17. if (Meteor.isServer) {
  18. if (process.env.AVATARS_UPLOAD_MIME_TYPES) {
  19. avatarsUploadMimeTypes = process.env.AVATARS_UPLOAD_MIME_TYPES.split(',');
  20. avatarsUploadMimeTypes = avatarsUploadMimeTypes.map(value => value.trim());
  21. }
  22. if (process.env.AVATARS_UPLOAD_MAX_SIZE) {
  23. avatarsUploadSize_ = parseInt(process.env.AVATARS_UPLOAD_MAX_SIZE);
  24. if (_.isNumber(avatarsUploadSize_) && avatarsUploadSize_ > 0) {
  25. avatarsUploadSize = avatarsUploadSize_;
  26. }
  27. }
  28. if (process.env.AVATARS_UPLOAD_EXTERNAL_PROGRAM) {
  29. avatarsUploadExternalProgram = process.env.AVATARS_UPLOAD_EXTERNAL_PROGRAM;
  30. if (!avatarsUploadExternalProgram.includes("{file}")) {
  31. avatarsUploadExternalProgram = undefined;
  32. }
  33. }
  34. avatarsBucket = createBucket('avatars');
  35. storagePath = path.join(process.env.WRITABLE_PATH, 'avatars');
  36. }
  37. const fileStoreStrategyFactory = new FileStoreStrategyFactory(FileStoreStrategyFilesystem, storagePath, FileStoreStrategyGridFs, avatarsBucket);
  38. Avatars = new FilesCollection({
  39. debug: false, // Change to `true` for debugging
  40. collectionName: 'avatars',
  41. allowClientCode: true,
  42. namingFunction(opts) {
  43. let filenameWithoutExtension = ""
  44. let fileId = "";
  45. if (opts?.name) {
  46. // Client
  47. filenameWithoutExtension = opts.name.replace(/(.+)\..+/, "$1");
  48. fileId = opts.meta.fileId;
  49. delete opts.meta.fileId;
  50. } else if (opts?.file?.name) {
  51. // Server
  52. if (opts.file.extension) {
  53. filenameWithoutExtension = opts.file.name.replace(new RegExp(opts.file.extensionWithDot + "$"), "")
  54. } else {
  55. // file has no extension, so don't replace anything, otherwise the last character is removed (because extensionWithDot = '.')
  56. filenameWithoutExtension = opts.file.name;
  57. }
  58. fileId = opts.fileId;
  59. }
  60. else {
  61. // should never reach here
  62. filenameWithoutExtension = Math.random().toString(36).slice(2);
  63. fileId = Math.random().toString(36).slice(2);
  64. }
  65. const ret = fileId + "-original-" + filenameWithoutExtension;
  66. // remove fileId from meta, it was only stored there to have this information here in the namingFunction function
  67. return ret;
  68. },
  69. sanitize(str, max, replacement) {
  70. // keep the original filename
  71. return str;
  72. },
  73. storagePath() {
  74. const ret = fileStoreStrategyFactory.storagePath;
  75. return ret;
  76. },
  77. onBeforeUpload(file) {
  78. if (file.size <= avatarsUploadSize && file.type.startsWith('image/')) {
  79. return true;
  80. }
  81. return TAPi18n.__('avatar-too-big', {size: filesize(avatarsUploadSize)});
  82. },
  83. onAfterUpload(fileObj) {
  84. // current storage is the filesystem, update object and database
  85. Object.keys(fileObj.versions).forEach(versionName => {
  86. fileObj.versions[versionName].storage = STORAGE_NAME_FILESYSTEM;
  87. });
  88. Avatars.update({ _id: fileObj._id }, { $set: { "versions": fileObj.versions } });
  89. const isValid = Promise.await(isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram));
  90. if (isValid) {
  91. ReactiveCache.getUser(fileObj.userId).setAvatarUrl(`${formatFleURL(fileObj)}?auth=false&brokenIsFine=true`);
  92. } else {
  93. Avatars.remove(fileObj._id);
  94. }
  95. },
  96. interceptDownload(http, fileObj, versionName) {
  97. const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl);
  98. return ret;
  99. },
  100. onBeforeRemove(files) {
  101. files.forEach(fileObj => {
  102. if (fileObj.userId) {
  103. ReactiveCache.getUser(fileObj.userId).setAvatarUrl('');
  104. }
  105. });
  106. return true;
  107. },
  108. onAfterRemove(files) {
  109. files.forEach(fileObj => {
  110. Object.keys(fileObj.versions).forEach(versionName => {
  111. fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).onAfterRemove();
  112. });
  113. });
  114. },
  115. });
  116. function isOwner(userId, doc) {
  117. return userId && userId === doc.userId;
  118. }
  119. if (Meteor.isServer) {
  120. Avatars.allow({
  121. insert: isOwner,
  122. update: isOwner,
  123. remove: isOwner,
  124. fetch: ['userId'],
  125. });
  126. Meteor.startup(() => {
  127. const storagePath = fileStoreStrategyFactory.storagePath;
  128. if (!fs.existsSync(storagePath)) {
  129. console.log("create storagePath because it doesn't exist: " + storagePath);
  130. fs.mkdirSync(storagePath, { recursive: true });
  131. }
  132. });
  133. }
  134. export default Avatars;