151196bf19eaa12be4c5e92e0865b3ba1cd6a650
[oweals/peertube.git] / server / lib / plugins / register-helpers-store.ts
1 import * as express from 'express'
2 import { logger } from '@server/helpers/logger'
3 import {
4   VIDEO_CATEGORIES,
5   VIDEO_LANGUAGES,
6   VIDEO_LICENCES,
7   VIDEO_PLAYLIST_PRIVACIES,
8   VIDEO_PRIVACIES
9 } from '@server/initializers/constants'
10 import { onExternalUserAuthenticated } from '@server/lib/auth'
11 import { PluginModel } from '@server/models/server/plugin'
12 import { RegisterServerOptions } from '@server/typings/plugins'
13 import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
14 import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
15 import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model'
16 import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model'
17 import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model'
18 import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model'
19 import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model'
20 import {
21   RegisterServerAuthExternalOptions,
22   RegisterServerAuthExternalResult,
23   RegisterServerAuthPassOptions,
24   RegisterServerExternalAuthenticatedResult
25 } from '@shared/models/plugins/register-server-auth.model'
26 import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
27 import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
28 import { serverHookObject } from '@shared/models/plugins/server-hook.model'
29 import { buildPluginHelpers } from './plugin-helpers'
30
31 type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
32 type VideoConstant = { [key in number | string]: string }
33
34 type UpdatedVideoConstant = {
35   [name in AlterableVideoConstant]: {
36     added: { key: number | string, label: string }[]
37     deleted: { key: number | string, label: string }[]
38   }
39 }
40
41 export class RegisterHelpersStore {
42   private readonly updatedVideoConstants: UpdatedVideoConstant = {
43     playlistPrivacy: { added: [], deleted: [] },
44     privacy: { added: [], deleted: [] },
45     language: { added: [], deleted: [] },
46     licence: { added: [], deleted: [] },
47     category: { added: [], deleted: [] }
48   }
49
50   private readonly settings: RegisterServerSettingOptions[] = []
51
52   private readonly idAndPassAuths: RegisterServerAuthPassOptions[] = []
53   private readonly externalAuths: RegisterServerAuthExternalOptions[] = []
54
55   private readonly router: express.Router
56
57   constructor (
58     private readonly npmName: string,
59     private readonly plugin: PluginModel,
60     private readonly onHookAdded: (options: RegisterServerHookOptions) => void
61   ) {
62     this.router = express.Router()
63   }
64
65   buildRegisterHelpers (): RegisterServerOptions {
66     const registerHook = this.buildRegisterHook()
67     const registerSetting = this.buildRegisterSetting()
68
69     const getRouter = this.buildGetRouter()
70
71     const settingsManager = this.buildSettingsManager()
72     const storageManager = this.buildStorageManager()
73
74     const videoLanguageManager = this.buildVideoLanguageManager()
75
76     const videoLicenceManager = this.buildVideoLicenceManager()
77     const videoCategoryManager = this.buildVideoCategoryManager()
78
79     const videoPrivacyManager = this.buildVideoPrivacyManager()
80     const playlistPrivacyManager = this.buildPlaylistPrivacyManager()
81
82     const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth()
83     const registerExternalAuth = this.buildRegisterExternalAuth()
84
85     const peertubeHelpers = buildPluginHelpers(this.npmName)
86
87     return {
88       registerHook,
89       registerSetting,
90
91       getRouter,
92
93       settingsManager,
94       storageManager,
95
96       videoLanguageManager,
97       videoCategoryManager,
98       videoLicenceManager,
99
100       videoPrivacyManager,
101       playlistPrivacyManager,
102
103       registerIdAndPassAuth,
104       registerExternalAuth,
105
106       peertubeHelpers
107     }
108   }
109
110   reinitVideoConstants (npmName: string) {
111     const hash = {
112       language: VIDEO_LANGUAGES,
113       licence: VIDEO_LICENCES,
114       category: VIDEO_CATEGORIES,
115       privacy: VIDEO_PRIVACIES,
116       playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
117     }
118     const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
119
120     for (const type of types) {
121       const updatedConstants = this.updatedVideoConstants[type][npmName]
122       if (!updatedConstants) continue
123
124       for (const added of updatedConstants.added) {
125         delete hash[type][added.key]
126       }
127
128       for (const deleted of updatedConstants.deleted) {
129         hash[type][deleted.key] = deleted.label
130       }
131
132       delete this.updatedVideoConstants[type][npmName]
133     }
134   }
135
136   getSettings () {
137     return this.settings
138   }
139
140   getRouter () {
141     return this.router
142   }
143
144   getIdAndPassAuths () {
145     return this.idAndPassAuths
146   }
147
148   getExternalAuths () {
149     return this.externalAuths
150   }
151
152   private buildGetRouter () {
153     return () => this.router
154   }
155
156   private buildRegisterSetting () {
157     return (options: RegisterServerSettingOptions) => {
158       this.settings.push(options)
159     }
160   }
161
162   private buildRegisterHook () {
163     return (options: RegisterServerHookOptions) => {
164       if (serverHookObject[options.target] !== true) {
165         logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName)
166         return
167       }
168
169       return this.onHookAdded(options)
170     }
171   }
172
173   private buildRegisterIdAndPassAuth () {
174     return (options: RegisterServerAuthPassOptions) => {
175       if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') {
176         logger.error('Cannot register auth plugin %s: authName of getWeight or login are not valid.', this.npmName)
177         return
178       }
179
180       this.idAndPassAuths.push(options)
181     }
182   }
183
184   private buildRegisterExternalAuth () {
185     const self = this
186
187     return (options: RegisterServerAuthExternalOptions) => {
188       if (!options.authName || !options.onAuthRequest || typeof options.onAuthRequest !== 'function') {
189         logger.error('Cannot register auth plugin %s: authName of getWeight or login are not valid.', this.npmName)
190         return
191       }
192
193       this.externalAuths.push(options)
194
195       return {
196         userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
197           onExternalUserAuthenticated({
198             npmName: self.npmName,
199             authName: options.authName,
200             authResult: result
201           }).catch(err => {
202             logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err })
203           })
204         }
205       } as RegisterServerAuthExternalResult
206     }
207   }
208
209   private buildSettingsManager (): PluginSettingsManager {
210     return {
211       getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name),
212
213       getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names),
214
215       setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value)
216     }
217   }
218
219   private buildStorageManager (): PluginStorageManager {
220     return {
221       getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key),
222
223       storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data)
224     }
225   }
226
227   private buildVideoLanguageManager (): PluginVideoLanguageManager {
228     return {
229       addLanguage: (key: string, label: string) => {
230         return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label })
231       },
232
233       deleteLanguage: (key: string) => {
234         return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
235       }
236     }
237   }
238
239   private buildVideoCategoryManager (): PluginVideoCategoryManager {
240     return {
241       addCategory: (key: number, label: string) => {
242         return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label })
243       },
244
245       deleteCategory: (key: number) => {
246         return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
247       }
248     }
249   }
250
251   private buildVideoPrivacyManager (): PluginVideoPrivacyManager {
252     return {
253       deletePrivacy: (key: number) => {
254         return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key })
255       }
256     }
257   }
258
259   private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager {
260     return {
261       deletePlaylistPrivacy: (key: number) => {
262         return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key })
263       }
264     }
265   }
266
267   private buildVideoLicenceManager (): PluginVideoLicenceManager {
268     return {
269       addLicence: (key: number, label: string) => {
270         return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label })
271       },
272
273       deleteLicence: (key: number) => {
274         return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key })
275       }
276     }
277   }
278
279   private addConstant<T extends string | number> (parameters: {
280     npmName: string
281     type: AlterableVideoConstant
282     obj: VideoConstant
283     key: T
284     label: string
285   }) {
286     const { npmName, type, obj, key, label } = parameters
287
288     if (obj[key]) {
289       logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
290       return false
291     }
292
293     if (!this.updatedVideoConstants[type][npmName]) {
294       this.updatedVideoConstants[type][npmName] = {
295         added: [],
296         deleted: []
297       }
298     }
299
300     this.updatedVideoConstants[type][npmName].added.push({ key, label })
301     obj[key] = label
302
303     return true
304   }
305
306   private deleteConstant<T extends string | number> (parameters: {
307     npmName: string
308     type: AlterableVideoConstant
309     obj: VideoConstant
310     key: T
311   }) {
312     const { npmName, type, obj, key } = parameters
313
314     if (!obj[key]) {
315       logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key)
316       return false
317     }
318
319     if (!this.updatedVideoConstants[type][npmName]) {
320       this.updatedVideoConstants[type][npmName] = {
321         added: [],
322         deleted: []
323       }
324     }
325
326     this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] })
327     delete obj[key]
328
329     return true
330   }
331 }