1 import { FindAndCountOptions, json, QueryTypes } from 'sequelize'
2 import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3 import { MPlugin, MPluginFormattable } from '@server/types/models'
4 import { PeerTubePlugin, PluginType, RegisterServerSettingOptions } from '../../../shared/models'
6 isPluginDescriptionValid,
11 } from '../../helpers/custom-validators/plugins'
12 import { getSort, throwIfNotValid } from '../utils'
14 @DefaultScope(() => ({
16 exclude: [ 'storage' ]
24 fields: [ 'name', 'type' ],
29 export class PluginModel extends Model {
32 @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name'))
37 @Is('PluginType', value => throwIfNotValid(value, isPluginTypeValid, 'type'))
42 @Is('PluginVersion', value => throwIfNotValid(value, isPluginVersionValid, 'version'))
47 @Is('PluginLatestVersion', value => throwIfNotValid(value, isPluginVersionValid, 'version'))
61 peertubeEngine: string
64 @Is('PluginDescription', value => throwIfNotValid(value, isPluginDescriptionValid, 'description'))
69 @Is('PluginHomepage', value => throwIfNotValid(value, isPluginHomepage, 'homepage'))
74 @Column(DataType.JSONB)
78 @Column(DataType.JSONB)
87 static listEnabledPluginsAndThemes (): Promise<MPlugin[]> {
95 return PluginModel.findAll(query)
98 static loadByNpmName (npmName: string): Promise<MPlugin> {
99 const name = this.normalizePluginName(npmName)
100 const type = this.getTypeFromNpmName(npmName)
109 return PluginModel.findOne(query)
112 static getSetting (pluginName: string, pluginType: PluginType, settingName: string, registeredSettings: RegisterServerSettingOptions[]) {
114 attributes: [ 'settings' ],
121 return PluginModel.findOne(query)
123 if (!p || !p.settings || p.settings === undefined) {
124 const registered = registeredSettings.find(s => s.name === settingName)
125 if (!registered || registered.default === undefined) return undefined
127 return registered.default
130 return p.settings[settingName]
136 pluginType: PluginType,
137 settingNames: string[],
138 registeredSettings: RegisterServerSettingOptions[]
141 attributes: [ 'settings' ],
148 return PluginModel.findOne(query)
150 const result: { [settingName: string ]: string | boolean } = {}
152 for (const name of settingNames) {
153 if (!p || !p.settings || p.settings[name] === undefined) {
154 const registered = registeredSettings.find(s => s.name === name)
156 if (registered?.default !== undefined) {
157 result[name] = registered.default
160 result[name] = p.settings[name]
168 static setSetting (pluginName: string, pluginType: PluginType, settingName: string, settingValue: string) {
177 [`settings.${settingName}`]: settingValue
180 return PluginModel.update(toSave, query)
181 .then(() => undefined)
184 static getData (pluginName: string, pluginType: PluginType, key: string) {
187 attributes: [ [ json('storage.' + key), 'value' ] as any ], // FIXME: typings
194 return PluginModel.findOne(query)
196 if (!c) return undefined
197 const value = c.value
199 if (typeof value === 'string' && value.startsWith('{')) {
201 return JSON.parse(value)
211 static storeData (pluginName: string, pluginType: PluginType, key: string, data: any) {
212 const query = 'UPDATE "plugin" SET "storage" = jsonb_set(coalesce("storage", \'{}\'), :key, :data::jsonb) ' +
213 'WHERE "name" = :pluginName AND "type" = :pluginType'
215 const jsonPath = '{' + key + '}'
218 replacements: { pluginName, pluginType, key: jsonPath, data: JSON.stringify(data) },
219 type: QueryTypes.UPDATE
222 return PluginModel.sequelize.query(query, options)
223 .then(() => undefined)
226 static listForApi (options: {
227 pluginType?: PluginType
228 uninstalled?: boolean
233 const { uninstalled = false } = options
234 const query: FindAndCountOptions = {
235 offset: options.start,
236 limit: options.count,
237 order: getSort(options.sort),
243 if (options.pluginType) query.where['type'] = options.pluginType
246 .findAndCountAll<MPlugin>(query)
247 .then(({ rows, count }) => {
248 return { total: count, data: rows }
252 static listInstalled (): Promise<MPlugin[]> {
259 return PluginModel.findAll(query)
262 static normalizePluginName (npmName: string) {
263 return npmName.replace(/^peertube-((theme)|(plugin))-/, '')
266 static getTypeFromNpmName (npmName: string) {
267 return npmName.startsWith('peertube-plugin-')
272 static buildNpmName (name: string, type: PluginType) {
273 if (type === PluginType.THEME) return 'peertube-theme-' + name
275 return 'peertube-plugin-' + name
278 getPublicSettings (registeredSettings: RegisterServerSettingOptions[]) {
279 const result: { [ name: string ]: string } = {}
280 const settings = this.settings || {}
282 for (const r of registeredSettings) {
283 if (r.private !== false) continue
285 result[r.name] = settings[r.name] || r.default || null
291 toFormattedJSON (this: MPluginFormattable): PeerTubePlugin {
295 version: this.version,
296 latestVersion: this.latestVersion,
297 enabled: this.enabled,
298 uninstalled: this.uninstalled,
299 peertubeEngine: this.peertubeEngine,
300 description: this.description,
301 homepage: this.homepage,
302 settings: this.settings,
303 createdAt: this.createdAt,
304 updatedAt: this.updatedAt