attachmentStorageSettings.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { Meteor } from 'meteor/meteor';
  3. import { SimpleSchema } from 'meteor/aldeed:simple-schema';
  4. import { STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS } from '/models/lib/fileStoreStrategy';
  5. // DISABLED: S3 storage removed due to Node.js compatibility
  6. // import { STORAGE_NAME_S3 } from '/models/lib/fileStoreStrategy';
  7. // Attachment Storage Settings Collection
  8. AttachmentStorageSettings = new Mongo.Collection('attachmentStorageSettings');
  9. // Schema for attachment storage settings
  10. AttachmentStorageSettings.attachSchema(
  11. new SimpleSchema({
  12. // Default storage backend for new uploads
  13. defaultStorage: {
  14. type: String,
  15. allowedValues: [STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS],
  16. defaultValue: STORAGE_NAME_FILESYSTEM,
  17. label: 'Default Storage Backend'
  18. },
  19. // Storage backend configuration
  20. storageConfig: {
  21. type: Object,
  22. optional: true,
  23. label: 'Storage Configuration'
  24. },
  25. 'storageConfig.filesystem': {
  26. type: Object,
  27. optional: true,
  28. label: 'Filesystem Configuration'
  29. },
  30. 'storageConfig.filesystem.enabled': {
  31. type: Boolean,
  32. defaultValue: true,
  33. label: 'Filesystem Storage Enabled'
  34. },
  35. 'storageConfig.filesystem.path': {
  36. type: String,
  37. optional: true,
  38. label: 'Filesystem Storage Path'
  39. },
  40. 'storageConfig.gridfs': {
  41. type: Object,
  42. optional: true,
  43. label: 'GridFS Configuration'
  44. },
  45. 'storageConfig.gridfs.enabled': {
  46. type: Boolean,
  47. defaultValue: true,
  48. label: 'GridFS Storage Enabled'
  49. },
  50. // DISABLED: S3 storage configuration removed due to Node.js compatibility
  51. /*
  52. 'storageConfig.s3': {
  53. type: Object,
  54. optional: true,
  55. label: 'S3 Configuration'
  56. },
  57. 'storageConfig.s3.enabled': {
  58. type: Boolean,
  59. defaultValue: false,
  60. label: 'S3 Storage Enabled'
  61. },
  62. 'storageConfig.s3.endpoint': {
  63. type: String,
  64. optional: true,
  65. label: 'S3 Endpoint'
  66. },
  67. 'storageConfig.s3.bucket': {
  68. type: String,
  69. optional: true,
  70. label: 'S3 Bucket'
  71. },
  72. 'storageConfig.s3.region': {
  73. type: String,
  74. optional: true,
  75. label: 'S3 Region'
  76. },
  77. 'storageConfig.s3.sslEnabled': {
  78. type: Boolean,
  79. defaultValue: true,
  80. label: 'S3 SSL Enabled'
  81. },
  82. 'storageConfig.s3.port': {
  83. type: Number,
  84. defaultValue: 443,
  85. label: 'S3 Port'
  86. },
  87. */
  88. // Upload settings
  89. uploadSettings: {
  90. type: Object,
  91. optional: true,
  92. label: 'Upload Settings'
  93. },
  94. 'uploadSettings.maxFileSize': {
  95. type: Number,
  96. optional: true,
  97. label: 'Maximum File Size (bytes)'
  98. },
  99. 'uploadSettings.allowedMimeTypes': {
  100. type: Array,
  101. optional: true,
  102. label: 'Allowed MIME Types'
  103. },
  104. 'uploadSettings.allowedMimeTypes.$': {
  105. type: String,
  106. label: 'MIME Type'
  107. },
  108. // Migration settings
  109. migrationSettings: {
  110. type: Object,
  111. optional: true,
  112. label: 'Migration Settings'
  113. },
  114. 'migrationSettings.autoMigrate': {
  115. type: Boolean,
  116. defaultValue: false,
  117. label: 'Auto Migrate to Default Storage'
  118. },
  119. 'migrationSettings.batchSize': {
  120. type: Number,
  121. defaultValue: 10,
  122. min: 1,
  123. max: 100,
  124. label: 'Migration Batch Size'
  125. },
  126. 'migrationSettings.delayMs': {
  127. type: Number,
  128. defaultValue: 1000,
  129. min: 100,
  130. max: 10000,
  131. label: 'Migration Delay (ms)'
  132. },
  133. 'migrationSettings.cpuThreshold': {
  134. type: Number,
  135. defaultValue: 70,
  136. min: 10,
  137. max: 90,
  138. label: 'CPU Threshold (%)'
  139. },
  140. // Metadata
  141. createdAt: {
  142. type: Date,
  143. autoValue() {
  144. if (this.isInsert) {
  145. return new Date();
  146. } else if (this.isUpsert) {
  147. return { $setOnInsert: new Date() };
  148. } else {
  149. this.unset();
  150. }
  151. },
  152. label: 'Created At'
  153. },
  154. updatedAt: {
  155. type: Date,
  156. autoValue() {
  157. if (this.isUpdate || this.isUpsert) {
  158. return new Date();
  159. }
  160. },
  161. label: 'Updated At'
  162. },
  163. createdBy: {
  164. type: String,
  165. optional: true,
  166. label: 'Created By'
  167. },
  168. updatedBy: {
  169. type: String,
  170. optional: true,
  171. label: 'Updated By'
  172. }
  173. })
  174. );
  175. // Helper methods
  176. AttachmentStorageSettings.helpers({
  177. // Get default storage backend
  178. getDefaultStorage() {
  179. return this.defaultStorage || STORAGE_NAME_FILESYSTEM;
  180. },
  181. // Check if storage backend is enabled
  182. isStorageEnabled(storageName) {
  183. if (!this.storageConfig) return false;
  184. switch (storageName) {
  185. case STORAGE_NAME_FILESYSTEM:
  186. return this.storageConfig.filesystem?.enabled !== false;
  187. case STORAGE_NAME_GRIDFS:
  188. return this.storageConfig.gridfs?.enabled !== false;
  189. // DISABLED: S3 storage removed due to Node.js compatibility
  190. // case STORAGE_NAME_S3:
  191. // return this.storageConfig.s3?.enabled === true;
  192. default:
  193. return false;
  194. }
  195. },
  196. // Get storage configuration
  197. getStorageConfig(storageName) {
  198. if (!this.storageConfig) return null;
  199. switch (storageName) {
  200. case STORAGE_NAME_FILESYSTEM:
  201. return this.storageConfig.filesystem;
  202. case STORAGE_NAME_GRIDFS:
  203. return this.storageConfig.gridfs;
  204. // DISABLED: S3 storage removed due to Node.js compatibility
  205. // case STORAGE_NAME_S3:
  206. // return this.storageConfig.s3;
  207. default:
  208. return null;
  209. }
  210. },
  211. // Get upload settings
  212. getUploadSettings() {
  213. return this.uploadSettings || {};
  214. },
  215. // Get migration settings
  216. getMigrationSettings() {
  217. return this.migrationSettings || {};
  218. }
  219. });
  220. // Server-side methods
  221. if (Meteor.isServer) {
  222. // Get or create default settings
  223. Meteor.methods({
  224. 'getAttachmentStorageSettings'() {
  225. if (!this.userId) {
  226. throw new Meteor.Error('not-authorized', 'Must be logged in');
  227. }
  228. const user = ReactiveCache.getUser(this.userId);
  229. if (!user || !user.isAdmin) {
  230. throw new Meteor.Error('not-authorized', 'Admin access required');
  231. }
  232. let settings = AttachmentStorageSettings.findOne({});
  233. if (!settings) {
  234. // Create default settings
  235. settings = {
  236. defaultStorage: STORAGE_NAME_FILESYSTEM,
  237. storageConfig: {
  238. filesystem: {
  239. enabled: true,
  240. path: process.env.WRITABLE_PATH ? `${process.env.WRITABLE_PATH}/attachments` : '/data/attachments'
  241. },
  242. gridfs: {
  243. enabled: true
  244. },
  245. // DISABLED: S3 storage removed due to Node.js compatibility
  246. // s3: {
  247. // enabled: false
  248. // }
  249. },
  250. uploadSettings: {
  251. maxFileSize: process.env.ATTACHMENTS_UPLOAD_MAX_SIZE ? parseInt(process.env.ATTACHMENTS_UPLOAD_MAX_SIZE) : 0,
  252. allowedMimeTypes: process.env.ATTACHMENTS_UPLOAD_MIME_TYPES ? process.env.ATTACHMENTS_UPLOAD_MIME_TYPES.split(',').map(t => t.trim()) : []
  253. },
  254. migrationSettings: {
  255. autoMigrate: false,
  256. batchSize: 10,
  257. delayMs: 1000,
  258. cpuThreshold: 70
  259. },
  260. createdBy: this.userId,
  261. updatedBy: this.userId
  262. };
  263. AttachmentStorageSettings.insert(settings);
  264. settings = AttachmentStorageSettings.findOne({});
  265. }
  266. return settings;
  267. },
  268. 'updateAttachmentStorageSettings'(settings) {
  269. if (!this.userId) {
  270. throw new Meteor.Error('not-authorized', 'Must be logged in');
  271. }
  272. const user = ReactiveCache.getUser(this.userId);
  273. if (!user || !user.isAdmin) {
  274. throw new Meteor.Error('not-authorized', 'Admin access required');
  275. }
  276. // Validate settings
  277. const schema = AttachmentStorageSettings.simpleSchema();
  278. schema.validate(settings);
  279. // Update settings
  280. const result = AttachmentStorageSettings.upsert(
  281. {},
  282. {
  283. $set: {
  284. ...settings,
  285. updatedBy: this.userId,
  286. updatedAt: new Date()
  287. }
  288. }
  289. );
  290. return result;
  291. },
  292. 'getDefaultAttachmentStorage'() {
  293. if (!this.userId) {
  294. throw new Meteor.Error('not-authorized', 'Must be logged in');
  295. }
  296. const settings = AttachmentStorageSettings.findOne({});
  297. return settings ? settings.getDefaultStorage() : STORAGE_NAME_FILESYSTEM;
  298. },
  299. 'setDefaultAttachmentStorage'(storageName) {
  300. if (!this.userId) {
  301. throw new Meteor.Error('not-authorized', 'Must be logged in');
  302. }
  303. const user = ReactiveCache.getUser(this.userId);
  304. if (!user || !user.isAdmin) {
  305. throw new Meteor.Error('not-authorized', 'Admin access required');
  306. }
  307. if (![STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS].includes(storageName)) {
  308. throw new Meteor.Error('invalid-storage', 'Invalid storage backend');
  309. }
  310. const result = AttachmentStorageSettings.upsert(
  311. {},
  312. {
  313. $set: {
  314. defaultStorage: storageName,
  315. updatedBy: this.userId,
  316. updatedAt: new Date()
  317. }
  318. }
  319. );
  320. return result;
  321. }
  322. });
  323. // Publication for settings
  324. Meteor.publish('attachmentStorageSettings', function() {
  325. if (!this.userId) {
  326. return this.ready();
  327. }
  328. const user = ReactiveCache.getUser(this.userId);
  329. if (!user || !user.isAdmin) {
  330. return this.ready();
  331. }
  332. return AttachmentStorageSettings.find({});
  333. });
  334. }
  335. export default AttachmentStorageSettings;