diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-11 14:40:19 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8 (patch) | |
tree | 7695023d90b78f972abafc718346c50264587ff5 /server | |
parent | d00dc28dd73ad9dd419d5a5ac6ac747cefbc6e8b (diff) | |
download | PeerTube-dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8.tar.gz PeerTube-dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8.tar.zst PeerTube-dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8.zip |
WIP plugins: add plugin settings/uninstall in client
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/plugins.ts | 27 | ||||
-rw-r--r-- | server/helpers/custom-validators/plugins.ts | 7 | ||||
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 25 | ||||
-rw-r--r-- | server/middlewares/validators/plugins.ts | 12 | ||||
-rw-r--r-- | server/models/server/plugin.ts | 28 |
5 files changed, 68 insertions, 31 deletions
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index 89cc67f54..f17e8cab9 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -12,7 +12,7 @@ import { pluginsSortValidator } from '../../middlewares/validators' | |||
12 | import { PluginModel } from '../../models/server/plugin' | 12 | import { PluginModel } from '../../models/server/plugin' |
13 | import { UserRight } from '../../../shared/models/users' | 13 | import { UserRight } from '../../../shared/models/users' |
14 | import { | 14 | import { |
15 | enabledPluginValidator, | 15 | existingPluginValidator, |
16 | installPluginValidator, | 16 | installPluginValidator, |
17 | listPluginsValidator, | 17 | listPluginsValidator, |
18 | uninstallPluginValidator, | 18 | uninstallPluginValidator, |
@@ -35,18 +35,25 @@ pluginRouter.get('/', | |||
35 | asyncMiddleware(listPlugins) | 35 | asyncMiddleware(listPlugins) |
36 | ) | 36 | ) |
37 | 37 | ||
38 | pluginRouter.get('/:pluginName/settings', | 38 | pluginRouter.get('/:npmName', |
39 | authenticate, | 39 | authenticate, |
40 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), | 40 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), |
41 | asyncMiddleware(enabledPluginValidator), | 41 | asyncMiddleware(existingPluginValidator), |
42 | asyncMiddleware(listPluginSettings) | 42 | getPlugin |
43 | ) | 43 | ) |
44 | 44 | ||
45 | pluginRouter.put('/:pluginName/settings', | 45 | pluginRouter.get('/:npmName/registered-settings', |
46 | authenticate, | ||
47 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), | ||
48 | asyncMiddleware(existingPluginValidator), | ||
49 | asyncMiddleware(getPluginRegisteredSettings) | ||
50 | ) | ||
51 | |||
52 | pluginRouter.put('/:npmName/settings', | ||
46 | authenticate, | 53 | authenticate, |
47 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), | 54 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), |
48 | updatePluginSettingsValidator, | 55 | updatePluginSettingsValidator, |
49 | asyncMiddleware(enabledPluginValidator), | 56 | asyncMiddleware(existingPluginValidator), |
50 | asyncMiddleware(updatePluginSettings) | 57 | asyncMiddleware(updatePluginSettings) |
51 | ) | 58 | ) |
52 | 59 | ||
@@ -85,6 +92,12 @@ async function listPlugins (req: express.Request, res: express.Response) { | |||
85 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 92 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
86 | } | 93 | } |
87 | 94 | ||
95 | function getPlugin (req: express.Request, res: express.Response) { | ||
96 | const plugin = res.locals.plugin | ||
97 | |||
98 | return res.json(plugin.toFormattedJSON()) | ||
99 | } | ||
100 | |||
88 | async function installPlugin (req: express.Request, res: express.Response) { | 101 | async function installPlugin (req: express.Request, res: express.Response) { |
89 | const body: InstallPlugin = req.body | 102 | const body: InstallPlugin = req.body |
90 | 103 | ||
@@ -101,7 +114,7 @@ async function uninstallPlugin (req: express.Request, res: express.Response) { | |||
101 | return res.sendStatus(204) | 114 | return res.sendStatus(204) |
102 | } | 115 | } |
103 | 116 | ||
104 | async function listPluginSettings (req: express.Request, res: express.Response) { | 117 | async function getPluginRegisteredSettings (req: express.Request, res: express.Response) { |
105 | const plugin = res.locals.plugin | 118 | const plugin = res.locals.plugin |
106 | 119 | ||
107 | const settings = await PluginManager.Instance.getSettings(plugin.name) | 120 | const settings = await PluginManager.Instance.getSettings(plugin.name) |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts index 4ab5f9ce8..064af9ead 100644 --- a/server/helpers/custom-validators/plugins.ts +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -41,6 +41,10 @@ function isPluginEngineValid (engine: any) { | |||
41 | return exists(engine) && exists(engine.peertube) | 41 | return exists(engine) && exists(engine.peertube) |
42 | } | 42 | } |
43 | 43 | ||
44 | function isPluginHomepage (value: string) { | ||
45 | return isUrlValid(value) | ||
46 | } | ||
47 | |||
44 | function isStaticDirectoriesValid (staticDirs: any) { | 48 | function isStaticDirectoriesValid (staticDirs: any) { |
45 | if (!exists(staticDirs) || typeof staticDirs !== 'object') return false | 49 | if (!exists(staticDirs) || typeof staticDirs !== 'object') return false |
46 | 50 | ||
@@ -70,7 +74,7 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT | |||
70 | return isNpmPluginNameValid(packageJSON.name) && | 74 | return isNpmPluginNameValid(packageJSON.name) && |
71 | isPluginDescriptionValid(packageJSON.description) && | 75 | isPluginDescriptionValid(packageJSON.description) && |
72 | isPluginEngineValid(packageJSON.engine) && | 76 | isPluginEngineValid(packageJSON.engine) && |
73 | isUrlValid(packageJSON.homepage) && | 77 | isPluginHomepage(packageJSON.homepage) && |
74 | exists(packageJSON.author) && | 78 | exists(packageJSON.author) && |
75 | isUrlValid(packageJSON.bugs) && | 79 | isUrlValid(packageJSON.bugs) && |
76 | (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) && | 80 | (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) && |
@@ -88,6 +92,7 @@ export { | |||
88 | isPluginTypeValid, | 92 | isPluginTypeValid, |
89 | isPackageJSONValid, | 93 | isPackageJSONValid, |
90 | isThemeValid, | 94 | isThemeValid, |
95 | isPluginHomepage, | ||
91 | isPluginVersionValid, | 96 | isPluginVersionValid, |
92 | isPluginNameValid, | 97 | isPluginNameValid, |
93 | isPluginDescriptionValid, | 98 | isPluginDescriptionValid, |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 3d8375acd..8cdeff446 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -89,6 +89,8 @@ export class PluginManager { | |||
89 | async runHook (hookName: string, param?: any) { | 89 | async runHook (hookName: string, param?: any) { |
90 | let result = param | 90 | let result = param |
91 | 91 | ||
92 | if (!this.hooks[hookName]) return result | ||
93 | |||
92 | const wait = hookName.startsWith('static:') | 94 | const wait = hookName.startsWith('static:') |
93 | 95 | ||
94 | for (const hook of this.hooks[hookName]) { | 96 | for (const hook of this.hooks[hookName]) { |
@@ -162,8 +164,8 @@ export class PluginManager { | |||
162 | : await installNpmPlugin(toInstall, version) | 164 | : await installNpmPlugin(toInstall, version) |
163 | 165 | ||
164 | name = fromDisk ? basename(toInstall) : toInstall | 166 | name = fromDisk ? basename(toInstall) : toInstall |
165 | const pluginType = name.startsWith('peertube-theme-') ? PluginType.THEME : PluginType.PLUGIN | 167 | const pluginType = PluginModel.getTypeFromNpmName(name) |
166 | const pluginName = this.normalizePluginName(name) | 168 | const pluginName = PluginModel.normalizePluginName(name) |
167 | 169 | ||
168 | const packageJSON = this.getPackageJSON(pluginName, pluginType) | 170 | const packageJSON = this.getPackageJSON(pluginName, pluginType) |
169 | if (!isPackageJSONValid(packageJSON, pluginType)) { | 171 | if (!isPackageJSONValid(packageJSON, pluginType)) { |
@@ -173,6 +175,7 @@ export class PluginManager { | |||
173 | [ plugin ] = await PluginModel.upsert({ | 175 | [ plugin ] = await PluginModel.upsert({ |
174 | name: pluginName, | 176 | name: pluginName, |
175 | description: packageJSON.description, | 177 | description: packageJSON.description, |
178 | homepage: packageJSON.homepage, | ||
176 | type: pluginType, | 179 | type: pluginType, |
177 | version: packageJSON.version, | 180 | version: packageJSON.version, |
178 | enabled: true, | 181 | enabled: true, |
@@ -196,10 +199,10 @@ export class PluginManager { | |||
196 | await this.registerPluginOrTheme(plugin) | 199 | await this.registerPluginOrTheme(plugin) |
197 | } | 200 | } |
198 | 201 | ||
199 | async uninstall (packageName: string) { | 202 | async uninstall (npmName: string) { |
200 | logger.info('Uninstalling plugin %s.', packageName) | 203 | logger.info('Uninstalling plugin %s.', npmName) |
201 | 204 | ||
202 | const pluginName = this.normalizePluginName(packageName) | 205 | const pluginName = PluginModel.normalizePluginName(npmName) |
203 | 206 | ||
204 | try { | 207 | try { |
205 | await this.unregister(pluginName) | 208 | await this.unregister(pluginName) |
@@ -207,9 +210,9 @@ export class PluginManager { | |||
207 | logger.warn('Cannot unregister plugin %s.', pluginName, { err }) | 210 | logger.warn('Cannot unregister plugin %s.', pluginName, { err }) |
208 | } | 211 | } |
209 | 212 | ||
210 | const plugin = await PluginModel.load(pluginName) | 213 | const plugin = await PluginModel.loadByNpmName(npmName) |
211 | if (!plugin || plugin.uninstalled === true) { | 214 | if (!plugin || plugin.uninstalled === true) { |
212 | logger.error('Cannot uninstall plugin %s: it does not exist or is already uninstalled.', packageName) | 215 | logger.error('Cannot uninstall plugin %s: it does not exist or is already uninstalled.', npmName) |
213 | return | 216 | return |
214 | } | 217 | } |
215 | 218 | ||
@@ -218,9 +221,9 @@ export class PluginManager { | |||
218 | 221 | ||
219 | await plugin.save() | 222 | await plugin.save() |
220 | 223 | ||
221 | await removeNpmPlugin(packageName) | 224 | await removeNpmPlugin(npmName) |
222 | 225 | ||
223 | logger.info('Plugin %s uninstalled.', packageName) | 226 | logger.info('Plugin %s uninstalled.', npmName) |
224 | } | 227 | } |
225 | 228 | ||
226 | // ###################### Private register ###################### | 229 | // ###################### Private register ###################### |
@@ -353,10 +356,6 @@ export class PluginManager { | |||
353 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', prefix + pluginName) | 356 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', prefix + pluginName) |
354 | } | 357 | } |
355 | 358 | ||
356 | private normalizePluginName (name: string) { | ||
357 | return name.replace(/^peertube-((theme)|(plugin))-/, '') | ||
358 | } | ||
359 | |||
360 | // ###################### Private getters ###################### | 359 | // ###################### Private getters ###################### |
361 | 360 | ||
362 | private getRegisteredPluginsOrThemes (type: PluginType) { | 361 | private getRegisteredPluginsOrThemes (type: PluginType) { |
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 265ac7c17..a06add6b8 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -63,7 +63,7 @@ const uninstallPluginValidator = [ | |||
63 | body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | 63 | body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), |
64 | 64 | ||
65 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 65 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
66 | logger.debug('Checking managePluginValidator parameters', { parameters: req.body }) | 66 | logger.debug('Checking uninstallPluginValidator parameters', { parameters: req.body }) |
67 | 67 | ||
68 | if (areValidationErrors(req, res)) return | 68 | if (areValidationErrors(req, res)) return |
69 | 69 | ||
@@ -71,15 +71,15 @@ const uninstallPluginValidator = [ | |||
71 | } | 71 | } |
72 | ] | 72 | ] |
73 | 73 | ||
74 | const enabledPluginValidator = [ | 74 | const existingPluginValidator = [ |
75 | body('name').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), | 75 | param('npmName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), |
76 | 76 | ||
77 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 77 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
78 | logger.debug('Checking enabledPluginValidator parameters', { parameters: req.body }) | 78 | logger.debug('Checking enabledPluginValidator parameters', { parameters: req.params }) |
79 | 79 | ||
80 | if (areValidationErrors(req, res)) return | 80 | if (areValidationErrors(req, res)) return |
81 | 81 | ||
82 | const plugin = await PluginModel.load(req.body.name) | 82 | const plugin = await PluginModel.loadByNpmName(req.params.npmName) |
83 | if (!plugin) { | 83 | if (!plugin) { |
84 | return res.status(404) | 84 | return res.status(404) |
85 | .json({ error: 'Plugin not found' }) | 85 | .json({ error: 'Plugin not found' }) |
@@ -110,7 +110,7 @@ export { | |||
110 | servePluginStaticDirectoryValidator, | 110 | servePluginStaticDirectoryValidator, |
111 | updatePluginSettingsValidator, | 111 | updatePluginSettingsValidator, |
112 | uninstallPluginValidator, | 112 | uninstallPluginValidator, |
113 | enabledPluginValidator, | 113 | existingPluginValidator, |
114 | installPluginValidator, | 114 | installPluginValidator, |
115 | listPluginsValidator | 115 | listPluginsValidator |
116 | } | 116 | } |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index 059a442de..60abaec65 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getSort, throwIfNotValid } from '../utils' | 2 | import { getSort, throwIfNotValid } from '../utils' |
3 | import { | 3 | import { |
4 | isPluginDescriptionValid, | 4 | isPluginDescriptionValid, isPluginHomepage, |
5 | isPluginNameValid, | 5 | isPluginNameValid, |
6 | isPluginTypeValid, | 6 | isPluginTypeValid, |
7 | isPluginVersionValid | 7 | isPluginVersionValid |
@@ -20,7 +20,7 @@ import { FindAndCountOptions } from 'sequelize' | |||
20 | tableName: 'plugin', | 20 | tableName: 'plugin', |
21 | indexes: [ | 21 | indexes: [ |
22 | { | 22 | { |
23 | fields: [ 'name' ], | 23 | fields: [ 'name', 'type' ], |
24 | unique: true | 24 | unique: true |
25 | } | 25 | } |
26 | ] | 26 | ] |
@@ -59,6 +59,11 @@ export class PluginModel extends Model<PluginModel> { | |||
59 | @Column | 59 | @Column |
60 | description: string | 60 | description: string |
61 | 61 | ||
62 | @AllowNull(false) | ||
63 | @Is('PluginHomepage', value => throwIfNotValid(value, isPluginHomepage, 'homepage')) | ||
64 | @Column | ||
65 | homepage: string | ||
66 | |||
62 | @AllowNull(true) | 67 | @AllowNull(true) |
63 | @Column(DataType.JSONB) | 68 | @Column(DataType.JSONB) |
64 | settings: any | 69 | settings: any |
@@ -84,10 +89,14 @@ export class PluginModel extends Model<PluginModel> { | |||
84 | return PluginModel.findAll(query) | 89 | return PluginModel.findAll(query) |
85 | } | 90 | } |
86 | 91 | ||
87 | static load (pluginName: string) { | 92 | static loadByNpmName (npmName: string) { |
93 | const name = this.normalizePluginName(npmName) | ||
94 | const type = this.getTypeFromNpmName(npmName) | ||
95 | |||
88 | const query = { | 96 | const query = { |
89 | where: { | 97 | where: { |
90 | name: pluginName | 98 | name, |
99 | type | ||
91 | } | 100 | } |
92 | } | 101 | } |
93 | 102 | ||
@@ -150,6 +159,16 @@ export class PluginModel extends Model<PluginModel> { | |||
150 | }) | 159 | }) |
151 | } | 160 | } |
152 | 161 | ||
162 | static normalizePluginName (name: string) { | ||
163 | return name.replace(/^peertube-((theme)|(plugin))-/, '') | ||
164 | } | ||
165 | |||
166 | static getTypeFromNpmName (npmName: string) { | ||
167 | return npmName.startsWith('peertube-plugin-') | ||
168 | ? PluginType.PLUGIN | ||
169 | : PluginType.THEME | ||
170 | } | ||
171 | |||
153 | toFormattedJSON (): PeerTubePlugin { | 172 | toFormattedJSON (): PeerTubePlugin { |
154 | return { | 173 | return { |
155 | name: this.name, | 174 | name: this.name, |
@@ -159,6 +178,7 @@ export class PluginModel extends Model<PluginModel> { | |||
159 | uninstalled: this.uninstalled, | 178 | uninstalled: this.uninstalled, |
160 | peertubeEngine: this.peertubeEngine, | 179 | peertubeEngine: this.peertubeEngine, |
161 | description: this.description, | 180 | description: this.description, |
181 | homepage: this.homepage, | ||
162 | settings: this.settings, | 182 | settings: this.settings, |
163 | createdAt: this.createdAt, | 183 | createdAt: this.createdAt, |
164 | updatedAt: this.updatedAt | 184 | updatedAt: this.updatedAt |