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