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