storage.mjs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Model } from 'objection'
  2. import path from 'node:path'
  3. import fs from 'node:fs/promises'
  4. import { capitalize, find, has, hasIn, remove, uniq } from 'lodash-es'
  5. import yaml from 'js-yaml'
  6. /**
  7. * Storage model
  8. */
  9. export class Storage extends Model {
  10. static get tableName() { return 'storage' }
  11. static get idColumn() { return 'id' }
  12. static get jsonSchema () {
  13. return {
  14. type: 'object',
  15. required: ['module', 'isEnabled', 'siteId'],
  16. properties: {
  17. module: {type: 'string'},
  18. isEnabled: {type: 'boolean'}
  19. }
  20. }
  21. }
  22. static get jsonAttributes() {
  23. return ['contentTypes', 'assetDelivery', 'versioning', 'schedule', 'config', 'state']
  24. }
  25. static async getTargets ({ siteId, enabledOnly = false } = {}) {
  26. return WIKI.db.storage.query().where(builder => {
  27. if (siteId) {
  28. builder.where('siteId', siteId)
  29. }
  30. if (enabledOnly) {
  31. builder.where('isEnabled', true)
  32. }
  33. })
  34. }
  35. static async refreshTargetsFromDisk () {
  36. let trx
  37. try {
  38. // -> Fetch definitions from disk
  39. const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
  40. WIKI.storage.defs = []
  41. for (const dir of storageDirs) {
  42. const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
  43. const defParsed = yaml.load(def)
  44. defParsed.key = dir
  45. defParsed.isLoaded = false
  46. WIKI.storage.defs.push(defParsed)
  47. WIKI.logger.debug(`Loaded storage module definition ${dir}: [ OK ]`)
  48. }
  49. WIKI.logger.info(`Loaded ${WIKI.storage.defs.length} storage module definitions: [ OK ]`)
  50. } catch (err) {
  51. WIKI.logger.error('Failed to scan or load new storage providers: [ FAILED ]')
  52. WIKI.logger.error(err)
  53. if (trx) {
  54. trx.rollback()
  55. }
  56. }
  57. }
  58. /**
  59. * Ensure a storage module is loaded
  60. */
  61. static async ensureModule (moduleName) {
  62. if (!has(WIKI.storage.modules, moduleName)) {
  63. try {
  64. WIKI.storage.modules[moduleName] = (await import(`../modules/storage/${moduleName}/storage.mjs`)).default
  65. WIKI.logger.debug(`Activated storage module ${moduleName}: [ OK ]`)
  66. return true
  67. } catch (err) {
  68. WIKI.logger.warn(`Failed to load storage module ${moduleName}: [ FAILED ]`)
  69. WIKI.logger.warn(err)
  70. return false
  71. }
  72. } else {
  73. return true
  74. }
  75. }
  76. /**
  77. * Initialize active storage targets
  78. */
  79. static async initTargets () {
  80. const dbTargets = await WIKI.db.storage.query().where('isEnabled', true)
  81. const activeModules = uniq(dbTargets.map(t => t.module))
  82. try {
  83. // -> Stop and delete existing jobs
  84. // const prevjobs = remove(WIKI.scheduler.jobs, job => job.name === 'sync-storage')
  85. // if (prevjobs.length > 0) {
  86. // prevjobs.forEach(job => job.stop())
  87. // }
  88. // -> Load active modules
  89. for (const md of activeModules) {
  90. this.ensureModule(md)
  91. }
  92. // -> Initialize targets
  93. // for (const target of this.targets) {
  94. // const targetDef = find(WIKI.data.storage, ['key', target.key])
  95. // target.fn = require(`../modules/storage/${target.key}/storage`)
  96. // target.fn.config = target.config
  97. // target.fn.mode = target.mode
  98. // try {
  99. // await target.fn.init()
  100. // // -> Save succeeded init state
  101. // await WIKI.db.storage.query().patch({
  102. // state: {
  103. // status: 'operational',
  104. // message: '',
  105. // lastAttempt: new Date().toISOString()
  106. // }
  107. // }).where('key', target.key)
  108. // // -> Set recurring sync job
  109. // if (targetDef.schedule && target.syncInterval !== 'P0D') {
  110. // WIKI.scheduler.registerJob({
  111. // name: 'sync-storage',
  112. // immediate: false,
  113. // schedule: target.syncInterval,
  114. // repeat: true
  115. // }, target.key)
  116. // }
  117. // // -> Set internal recurring sync job
  118. // if (targetDef.internalSchedule && targetDef.internalSchedule !== 'P0D') {
  119. // WIKI.scheduler.registerJob({
  120. // name: 'sync-storage',
  121. // immediate: false,
  122. // schedule: target.internalSchedule,
  123. // repeat: true
  124. // }, target.key)
  125. // }
  126. // } catch (err) {
  127. // // -> Save initialization error
  128. // await WIKI.db.storage.query().patch({
  129. // state: {
  130. // status: 'error',
  131. // message: err.message,
  132. // lastAttempt: new Date().toISOString()
  133. // }
  134. // }).where('key', target.key)
  135. // }
  136. // }
  137. } catch (err) {
  138. WIKI.logger.warn(err)
  139. throw err
  140. }
  141. }
  142. static async pageEvent({ event, page }) {
  143. try {
  144. for (let target of this.targets) {
  145. await target.fn[event](page)
  146. }
  147. } catch (err) {
  148. WIKI.logger.warn(err)
  149. throw err
  150. }
  151. }
  152. static async assetEvent({ event, asset }) {
  153. try {
  154. for (let target of this.targets) {
  155. await target.fn[`asset${capitalize(event)}`](asset)
  156. }
  157. } catch (err) {
  158. WIKI.logger.warn(err)
  159. throw err
  160. }
  161. }
  162. static async getLocalLocations({ asset }) {
  163. const locations = []
  164. const promises = this.targets.map(async (target) => {
  165. try {
  166. const path = await target.fn.getLocalLocation(asset)
  167. locations.push({
  168. path,
  169. key: target.key
  170. })
  171. } catch (err) {
  172. WIKI.logger.warn(err)
  173. }
  174. })
  175. await Promise.all(promises)
  176. return locations
  177. }
  178. static async executeAction(targetKey, handler) {
  179. try {
  180. const target = find(this.targets, ['key', targetKey])
  181. if (target) {
  182. if (hasIn(target.fn, handler)) {
  183. await target.fn[handler]()
  184. } else {
  185. throw new Error('Invalid Handler for Storage Target')
  186. }
  187. } else {
  188. throw new Error('Invalid or Inactive Storage Target')
  189. }
  190. } catch (err) {
  191. WIKI.logger.warn(err)
  192. throw err
  193. }
  194. }
  195. }