attachmentStorageSettings.js 9.3 KB

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