Merge branch 'release/2.1.0' into develop
[oweals/peertube.git] / server / models / server / plugin.ts
1 import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2 import { getSort, throwIfNotValid } from '../utils'
3 import {
4   isPluginDescriptionValid,
5   isPluginHomepage,
6   isPluginNameValid,
7   isPluginTypeValid,
8   isPluginVersionValid
9 } from '../../helpers/custom-validators/plugins'
10 import { PluginType } from '../../../shared/models/plugins/plugin.type'
11 import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
12 import { FindAndCountOptions, json } from 'sequelize'
13 import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
14 import * as Bluebird from 'bluebird'
15 import { MPlugin, MPluginFormattable } from '@server/typings/models'
16
17 @DefaultScope(() => ({
18   attributes: {
19     exclude: [ 'storage' ]
20   }
21 }))
22
23 @Table({
24   tableName: 'plugin',
25   indexes: [
26     {
27       fields: [ 'name', 'type' ],
28       unique: true
29     }
30   ]
31 })
32 export class PluginModel extends Model<PluginModel> {
33
34   @AllowNull(false)
35   @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name'))
36   @Column
37   name: string
38
39   @AllowNull(false)
40   @Is('PluginType', value => throwIfNotValid(value, isPluginTypeValid, 'type'))
41   @Column
42   type: number
43
44   @AllowNull(false)
45   @Is('PluginVersion', value => throwIfNotValid(value, isPluginVersionValid, 'version'))
46   @Column
47   version: string
48
49   @AllowNull(true)
50   @Is('PluginLatestVersion', value => throwIfNotValid(value, isPluginVersionValid, 'version'))
51   @Column
52   latestVersion: string
53
54   @AllowNull(false)
55   @Column
56   enabled: boolean
57
58   @AllowNull(false)
59   @Column
60   uninstalled: boolean
61
62   @AllowNull(false)
63   @Column
64   peertubeEngine: string
65
66   @AllowNull(true)
67   @Is('PluginDescription', value => throwIfNotValid(value, isPluginDescriptionValid, 'description'))
68   @Column
69   description: string
70
71   @AllowNull(false)
72   @Is('PluginHomepage', value => throwIfNotValid(value, isPluginHomepage, 'homepage'))
73   @Column
74   homepage: string
75
76   @AllowNull(true)
77   @Column(DataType.JSONB)
78   settings: any
79
80   @AllowNull(true)
81   @Column(DataType.JSONB)
82   storage: any
83
84   @CreatedAt
85   createdAt: Date
86
87   @UpdatedAt
88   updatedAt: Date
89
90   static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> {
91     const query = {
92       where: {
93         enabled: true,
94         uninstalled: false
95       }
96     }
97
98     return PluginModel.findAll(query)
99   }
100
101   static loadByNpmName (npmName: string): Bluebird<MPlugin> {
102     const name = this.normalizePluginName(npmName)
103     const type = this.getTypeFromNpmName(npmName)
104
105     const query = {
106       where: {
107         name,
108         type
109       }
110     }
111
112     return PluginModel.findOne(query)
113   }
114
115   static getSetting (pluginName: string, pluginType: PluginType, settingName: string) {
116     const query = {
117       attributes: [ 'settings' ],
118       where: {
119         name: pluginName,
120         type: pluginType
121       }
122     }
123
124     return PluginModel.findOne(query)
125       .then(p => {
126         if (!p || !p.settings) return undefined
127
128         return p.settings[settingName]
129       })
130   }
131
132   static setSetting (pluginName: string, pluginType: PluginType, settingName: string, settingValue: string) {
133     const query = {
134       where: {
135         name: pluginName,
136         type: pluginType
137       }
138     }
139
140     const toSave = {
141       [`settings.${settingName}`]: settingValue
142     }
143
144     return PluginModel.update(toSave, query)
145       .then(() => undefined)
146   }
147
148   static getData (pluginName: string, pluginType: PluginType, key: string) {
149     const query = {
150       raw: true,
151       attributes: [ [ json('storage.' + key), 'value' ] as any ], // FIXME: typings
152       where: {
153         name: pluginName,
154         type: pluginType
155       }
156     }
157
158     return PluginModel.findOne(query)
159       .then((c: any) => {
160         if (!c) return undefined
161         const value = c.value
162
163         if (typeof value === 'string' && value.startsWith('{')) {
164           try {
165             return JSON.parse(value)
166           } catch {
167             return value
168           }
169         }
170
171         return c.value
172       })
173   }
174
175   static storeData (pluginName: string, pluginType: PluginType, key: string, data: any) {
176     const query = {
177       where: {
178         name: pluginName,
179         type: pluginType
180       }
181     }
182
183     const toSave = {
184       [`storage.${key}`]: data
185     }
186
187     return PluginModel.update(toSave, query)
188                       .then(() => undefined)
189   }
190
191   static listForApi (options: {
192     pluginType?: PluginType
193     uninstalled?: boolean
194     start: number
195     count: number
196     sort: string
197   }) {
198     const { uninstalled = false } = options
199     const query: FindAndCountOptions = {
200       offset: options.start,
201       limit: options.count,
202       order: getSort(options.sort),
203       where: {
204         uninstalled
205       }
206     }
207
208     if (options.pluginType) query.where['type'] = options.pluginType
209
210     return PluginModel
211       .findAndCountAll<MPlugin>(query)
212       .then(({ rows, count }) => {
213         return { total: count, data: rows }
214       })
215   }
216
217   static listInstalled (): Bluebird<MPlugin[]> {
218     const query = {
219       where: {
220         uninstalled: false
221       }
222     }
223
224     return PluginModel.findAll(query)
225   }
226
227   static normalizePluginName (npmName: string) {
228     return npmName.replace(/^peertube-((theme)|(plugin))-/, '')
229   }
230
231   static getTypeFromNpmName (npmName: string) {
232     return npmName.startsWith('peertube-plugin-')
233       ? PluginType.PLUGIN
234       : PluginType.THEME
235   }
236
237   static buildNpmName (name: string, type: PluginType) {
238     if (type === PluginType.THEME) return 'peertube-theme-' + name
239
240     return 'peertube-plugin-' + name
241   }
242
243   getPublicSettings (registeredSettings: RegisterServerSettingOptions[]) {
244     const result: { [ name: string ]: string } = {}
245     const settings = this.settings || {}
246
247     for (const r of registeredSettings) {
248       if (r.private !== false) continue
249
250       result[r.name] = settings[r.name] || r.default || null
251     }
252
253     return result
254   }
255
256   toFormattedJSON (this: MPluginFormattable): PeerTubePlugin {
257     return {
258       name: this.name,
259       type: this.type,
260       version: this.version,
261       latestVersion: this.latestVersion,
262       enabled: this.enabled,
263       uninstalled: this.uninstalled,
264       peertubeEngine: this.peertubeEngine,
265       description: this.description,
266       homepage: this.homepage,
267       settings: this.settings,
268       createdAt: this.createdAt,
269       updatedAt: this.updatedAt
270     }
271   }
272
273 }