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