|
|
|
import { Model } from 'objection'
|
|
|
|
import path from 'node:path'
|
|
|
|
import fs from 'node:fs/promises'
|
|
|
|
import { capitalize, find, has, hasIn, remove, uniq } from 'lodash-es'
|
|
|
|
import yaml from 'js-yaml'
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Storage model
|
|
|
|
*/
|
|
|
|
export class Storage extends Model {
|
|
|
|
static get tableName() { return 'storage' }
|
|
|
|
static get idColumn() { return 'id' }
|
|
|
|
|
|
|
|
static get jsonSchema () {
|
|
|
|
return {
|
|
|
|
type: 'object',
|
|
|
|
required: ['module', 'isEnabled', 'siteId'],
|
|
|
|
|
|
|
|
properties: {
|
|
|
|
module: {type: 'string'},
|
|
|
|
isEnabled: {type: 'boolean'}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static get jsonAttributes() {
|
|
|
|
return ['contentTypes', 'assetDelivery', 'versioning', 'schedule', 'config', 'state']
|
|
|
|
}
|
|
|
|
|
|
|
|
static async getTargets ({ siteId, enabledOnly = false } = {}) {
|
|
|
|
return WIKI.db.storage.query().where(builder => {
|
|
|
|
if (siteId) {
|
|
|
|
builder.where('siteId', siteId)
|
|
|
|
}
|
|
|
|
if (enabledOnly) {
|
|
|
|
builder.where('isEnabled', true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
static async refreshTargetsFromDisk () {
|
|
|
|
let trx
|
|
|
|
try {
|
|
|
|
// -> Fetch definitions from disk
|
|
|
|
const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
|
|
|
|
WIKI.storage.defs = []
|
|
|
|
for (const dir of storageDirs) {
|
|
|
|
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
|
|
|
|
const defParsed = yaml.load(def)
|
|
|
|
defParsed.key = dir
|
|
|
|
defParsed.isLoaded = false
|
|
|
|
WIKI.storage.defs.push(defParsed)
|
|
|
|
WIKI.logger.debug(`Loaded storage module definition ${dir}: [ OK ]`)
|
|
|
|
}
|
|
|
|
WIKI.logger.info(`Loaded ${WIKI.storage.defs.length} storage module definitions: [ OK ]`)
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.error('Failed to scan or load new storage providers: [ FAILED ]')
|
|
|
|
WIKI.logger.error(err)
|
|
|
|
if (trx) {
|
|
|
|
trx.rollback()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure a storage module is loaded
|
|
|
|
*/
|
|
|
|
static async ensureModule (moduleName) {
|
|
|
|
if (!has(WIKI.storage.modules, moduleName)) {
|
|
|
|
try {
|
|
|
|
WIKI.storage.modules[moduleName] = (await import(`../modules/storage/${moduleName}/storage.mjs`)).default
|
|
|
|
WIKI.logger.debug(`Activated storage module ${moduleName}: [ OK ]`)
|
|
|
|
return true
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(`Failed to load storage module ${moduleName}: [ FAILED ]`)
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize active storage targets
|
|
|
|
*/
|
|
|
|
static async initTargets () {
|
|
|
|
const dbTargets = await WIKI.db.storage.query().where('isEnabled', true)
|
|
|
|
const activeModules = uniq(dbTargets.map(t => t.module))
|
|
|
|
try {
|
|
|
|
// -> Stop and delete existing jobs
|
|
|
|
// const prevjobs = remove(WIKI.scheduler.jobs, job => job.name === 'sync-storage')
|
|
|
|
// if (prevjobs.length > 0) {
|
|
|
|
// prevjobs.forEach(job => job.stop())
|
|
|
|
// }
|
|
|
|
|
|
|
|
// -> Load active modules
|
|
|
|
for (const md of activeModules) {
|
|
|
|
this.ensureModule(md)
|
|
|
|
}
|
|
|
|
|
|
|
|
// -> Initialize targets
|
|
|
|
// for (const target of this.targets) {
|
|
|
|
// const targetDef = find(WIKI.data.storage, ['key', target.key])
|
|
|
|
// target.fn = require(`../modules/storage/${target.key}/storage`)
|
|
|
|
// target.fn.config = target.config
|
|
|
|
// target.fn.mode = target.mode
|
|
|
|
// try {
|
|
|
|
// await target.fn.init()
|
|
|
|
|
|
|
|
// // -> Save succeeded init state
|
|
|
|
// await WIKI.db.storage.query().patch({
|
|
|
|
// state: {
|
|
|
|
// status: 'operational',
|
|
|
|
// message: '',
|
|
|
|
// lastAttempt: new Date().toISOString()
|
|
|
|
// }
|
|
|
|
// }).where('key', target.key)
|
|
|
|
|
|
|
|
// // -> Set recurring sync job
|
|
|
|
// if (targetDef.schedule && target.syncInterval !== 'P0D') {
|
|
|
|
// WIKI.scheduler.registerJob({
|
|
|
|
// name: 'sync-storage',
|
|
|
|
// immediate: false,
|
|
|
|
// schedule: target.syncInterval,
|
|
|
|
// repeat: true
|
|
|
|
// }, target.key)
|
|
|
|
// }
|
|
|
|
|
|
|
|
// // -> Set internal recurring sync job
|
|
|
|
// if (targetDef.internalSchedule && targetDef.internalSchedule !== 'P0D') {
|
|
|
|
// WIKI.scheduler.registerJob({
|
|
|
|
// name: 'sync-storage',
|
|
|
|
// immediate: false,
|
|
|
|
// schedule: target.internalSchedule,
|
|
|
|
// repeat: true
|
|
|
|
// }, target.key)
|
|
|
|
// }
|
|
|
|
// } catch (err) {
|
|
|
|
// // -> Save initialization error
|
|
|
|
// await WIKI.db.storage.query().patch({
|
|
|
|
// state: {
|
|
|
|
// status: 'error',
|
|
|
|
// message: err.message,
|
|
|
|
// lastAttempt: new Date().toISOString()
|
|
|
|
// }
|
|
|
|
// }).where('key', target.key)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static async pageEvent({ event, page }) {
|
|
|
|
try {
|
|
|
|
for (let target of this.targets) {
|
|
|
|
await target.fn[event](page)
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static async assetEvent({ event, asset }) {
|
|
|
|
try {
|
|
|
|
for (let target of this.targets) {
|
|
|
|
await target.fn[`asset${capitalize(event)}`](asset)
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static async getLocalLocations({ asset }) {
|
|
|
|
const locations = []
|
|
|
|
const promises = this.targets.map(async (target) => {
|
|
|
|
try {
|
|
|
|
const path = await target.fn.getLocalLocation(asset)
|
|
|
|
locations.push({
|
|
|
|
path,
|
|
|
|
key: target.key
|
|
|
|
})
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
await Promise.all(promises)
|
|
|
|
return locations
|
|
|
|
}
|
|
|
|
|
|
|
|
static async executeAction(targetKey, handler) {
|
|
|
|
try {
|
|
|
|
const target = find(this.targets, ['key', targetKey])
|
|
|
|
if (target) {
|
|
|
|
if (hasIn(target.fn, handler)) {
|
|
|
|
await target.fn[handler]()
|
|
|
|
} else {
|
|
|
|
throw new Error('Invalid Handler for Storage Target')
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error('Invalid or Inactive Storage Target')
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
WIKI.logger.warn(err)
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|