diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-11 17:23:24 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | 8d2be0ed7bb87283a1ec98609df6b82d83db706a (patch) | |
tree | 31a36b252df32be83ceb77658a53b57f9d15e8ac | |
parent | dba85a1e9e9f603ba52e1ea42deaf3fdd799b1d8 (diff) | |
download | PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.tar.gz PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.tar.zst PeerTube-8d2be0ed7bb87283a1ec98609df6b82d83db706a.zip |
WIP plugins: move plugin CLI in peertube script
Install/uninstall/list plugins remotely
26 files changed, 452 insertions, 191 deletions
diff --git a/package.json b/package.json index fde913574..306476c6a 100644 --- a/package.json +++ b/package.json | |||
@@ -32,8 +32,6 @@ | |||
32 | "clean:server:test": "scripty", | 32 | "clean:server:test": "scripty", |
33 | "watch:client": "scripty", | 33 | "watch:client": "scripty", |
34 | "watch:server": "scripty", | 34 | "watch:server": "scripty", |
35 | "plugin:install": "node ./dist/scripts/plugin/install.js", | ||
36 | "plugin:uninstall": "node ./dist/scripts/plugin/uninstall.js", | ||
37 | "danger:clean:dev": "scripty", | 35 | "danger:clean:dev": "scripty", |
38 | "danger:clean:prod": "scripty", | 36 | "danger:clean:prod": "scripty", |
39 | "danger:clean:modules": "scripty", | 37 | "danger:clean:modules": "scripty", |
@@ -45,6 +43,7 @@ | |||
45 | "dev": "scripty", | 43 | "dev": "scripty", |
46 | "dev:server": "scripty", | 44 | "dev:server": "scripty", |
47 | "dev:client": "scripty", | 45 | "dev:client": "scripty", |
46 | "dev:cli": "scripty", | ||
48 | "start": "node dist/server", | 47 | "start": "node dist/server", |
49 | "start:server": "node dist/server --no-client", | 48 | "start:server": "node dist/server --no-client", |
50 | "update-host": "node ./dist/scripts/update-host.js", | 49 | "update-host": "node ./dist/scripts/update-host.js", |
diff --git a/scripts/dev/cli.sh b/scripts/dev/cli.sh new file mode 100755 index 000000000..4b6fe5508 --- /dev/null +++ b/scripts/dev/cli.sh | |||
@@ -0,0 +1,15 @@ | |||
1 | #!/bin/sh | ||
2 | |||
3 | set -eu | ||
4 | |||
5 | rm -rf ./dist/server/tools/ | ||
6 | |||
7 | ( | ||
8 | cd ./server/tools | ||
9 | yarn install --pure-lockfile | ||
10 | ) | ||
11 | |||
12 | mkdir -p "./dist/server/tools" | ||
13 | cp -r "./server/tools/node_modules" "./dist/server/tools" | ||
14 | |||
15 | npm run tsc -- --watch --project ./server/tools/tsconfig.json | ||
diff --git a/scripts/plugin/install.ts b/scripts/plugin/install.ts deleted file mode 100755 index 1725cbeb6..000000000 --- a/scripts/plugin/install.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import { initDatabaseModels } from '../../server/initializers/database' | ||
2 | import * as program from 'commander' | ||
3 | import { PluginManager } from '../../server/lib/plugins/plugin-manager' | ||
4 | import { isAbsolute } from 'path' | ||
5 | |||
6 | program | ||
7 | .option('-n, --plugin-name [pluginName]', 'Plugin name to install') | ||
8 | .option('-v, --plugin-version [pluginVersion]', 'Plugin version to install') | ||
9 | .option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install') | ||
10 | .parse(process.argv) | ||
11 | |||
12 | if (!program['pluginName'] && !program['pluginPath']) { | ||
13 | console.error('You need to specify a plugin name with the desired version, or a plugin path.') | ||
14 | process.exit(-1) | ||
15 | } | ||
16 | |||
17 | if (program['pluginName'] && !program['pluginVersion']) { | ||
18 | console.error('You need to specify a the version of the plugin you want to install.') | ||
19 | process.exit(-1) | ||
20 | } | ||
21 | |||
22 | if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) { | ||
23 | console.error('Plugin path should be absolute.') | ||
24 | process.exit(-1) | ||
25 | } | ||
26 | |||
27 | run() | ||
28 | .then(() => process.exit(0)) | ||
29 | .catch(err => { | ||
30 | console.error(err) | ||
31 | process.exit(-1) | ||
32 | }) | ||
33 | |||
34 | async function run () { | ||
35 | await initDatabaseModels(true) | ||
36 | |||
37 | const toInstall = program['pluginName'] || program['pluginPath'] | ||
38 | await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath']) | ||
39 | } | ||
diff --git a/scripts/plugin/uninstall.ts b/scripts/plugin/uninstall.ts deleted file mode 100755 index 7dcc234db..000000000 --- a/scripts/plugin/uninstall.ts +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | import { initDatabaseModels } from '../../server/initializers/database' | ||
2 | import * as program from 'commander' | ||
3 | import { PluginManager } from '../../server/lib/plugins/plugin-manager' | ||
4 | import { isAbsolute } from 'path' | ||
5 | |||
6 | program | ||
7 | .option('-n, --package-name [packageName]', 'Package name to install') | ||
8 | .parse(process.argv) | ||
9 | |||
10 | if (!program['packageName']) { | ||
11 | console.error('You need to specify the plugin name.') | ||
12 | process.exit(-1) | ||
13 | } | ||
14 | |||
15 | run() | ||
16 | .then(() => process.exit(0)) | ||
17 | .catch(err => { | ||
18 | console.error(err) | ||
19 | process.exit(-1) | ||
20 | }) | ||
21 | |||
22 | async function run () { | ||
23 | await initDatabaseModels(true) | ||
24 | |||
25 | const toUninstall = program['packageName'] | ||
26 | await PluginManager.Instance.uninstall(toUninstall) | ||
27 | } | ||
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index f17e8cab9..8e59f27cf 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -21,6 +21,7 @@ import { | |||
21 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 21 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
22 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' | 22 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' |
23 | import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' | 23 | import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' |
24 | import { logger } from '../../helpers/logger' | ||
24 | 25 | ||
25 | const pluginRouter = express.Router() | 26 | const pluginRouter = express.Router() |
26 | 27 | ||
@@ -46,7 +47,7 @@ pluginRouter.get('/:npmName/registered-settings', | |||
46 | authenticate, | 47 | authenticate, |
47 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), | 48 | ensureUserHasRight(UserRight.MANAGE_PLUGINS), |
48 | asyncMiddleware(existingPluginValidator), | 49 | asyncMiddleware(existingPluginValidator), |
49 | asyncMiddleware(getPluginRegisteredSettings) | 50 | getPluginRegisteredSettings |
50 | ) | 51 | ) |
51 | 52 | ||
52 | pluginRouter.put('/:npmName/settings', | 53 | pluginRouter.put('/:npmName/settings', |
@@ -101,7 +102,14 @@ function getPlugin (req: express.Request, res: express.Response) { | |||
101 | async function installPlugin (req: express.Request, res: express.Response) { | 102 | async function installPlugin (req: express.Request, res: express.Response) { |
102 | const body: InstallPlugin = req.body | 103 | const body: InstallPlugin = req.body |
103 | 104 | ||
104 | await PluginManager.Instance.install(body.npmName) | 105 | const fromDisk = !!body.path |
106 | const toInstall = body.npmName || body.path | ||
107 | try { | ||
108 | await PluginManager.Instance.install(toInstall, undefined, fromDisk) | ||
109 | } catch (err) { | ||
110 | logger.warn('Cannot install plugin %s.', toInstall, { err }) | ||
111 | return res.sendStatus(400) | ||
112 | } | ||
105 | 113 | ||
106 | return res.sendStatus(204) | 114 | return res.sendStatus(204) |
107 | } | 115 | } |
@@ -114,10 +122,10 @@ async function uninstallPlugin (req: express.Request, res: express.Response) { | |||
114 | return res.sendStatus(204) | 122 | return res.sendStatus(204) |
115 | } | 123 | } |
116 | 124 | ||
117 | async function getPluginRegisteredSettings (req: express.Request, res: express.Response) { | 125 | function getPluginRegisteredSettings (req: express.Request, res: express.Response) { |
118 | const plugin = res.locals.plugin | 126 | const plugin = res.locals.plugin |
119 | 127 | ||
120 | const settings = await PluginManager.Instance.getSettings(plugin.name) | 128 | const settings = PluginManager.Instance.getSettings(plugin.name) |
121 | 129 | ||
122 | return res.json({ | 130 | return res.json({ |
123 | settings | 131 | settings |
diff --git a/server/controllers/themes.ts b/server/controllers/themes.ts deleted file mode 100644 index 104c285ad..000000000 --- a/server/controllers/themes.ts +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import * as express from 'express' | ||
2 | import { join } from 'path' | ||
3 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' | ||
4 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' | ||
5 | |||
6 | const themesRouter = express.Router() | ||
7 | |||
8 | themesRouter.get('/:themeName/:themeVersion/css/:staticEndpoint(*)', | ||
9 | serveThemeCSSValidator, | ||
10 | serveThemeCSSDirectory | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | themesRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | function serveThemeCSSDirectory (req: express.Request, res: express.Response) { | ||
22 | const plugin: RegisteredPlugin = res.locals.registeredPlugin | ||
23 | const staticEndpoint = req.params.staticEndpoint | ||
24 | |||
25 | if (plugin.css.includes(staticEndpoint) === false) { | ||
26 | return res.sendStatus(404) | ||
27 | } | ||
28 | |||
29 | return res.sendFile(join(plugin.path, staticEndpoint)) | ||
30 | } | ||
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index c5b139378..64818d036 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -3,7 +3,6 @@ | |||
3 | Useful to avoid circular dependencies. | 3 | Useful to avoid circular dependencies. |
4 | */ | 4 | */ |
5 | 5 | ||
6 | import * as bcrypt from 'bcrypt' | ||
7 | import * as createTorrent from 'create-torrent' | 6 | import * as createTorrent from 'create-torrent' |
8 | import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' | 7 | import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' |
9 | import { isAbsolute, join } from 'path' | 8 | import { isAbsolute, join } from 'path' |
@@ -258,9 +257,6 @@ function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => vo | |||
258 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) | 257 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) |
259 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) | 258 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) |
260 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) | 259 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) |
261 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | ||
262 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | ||
263 | const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash) | ||
264 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) | 260 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) |
265 | const execPromise2 = promisify2<string, any, string>(exec) | 261 | const execPromise2 = promisify2<string, any, string>(exec) |
266 | const execPromise = promisify1<string, string>(exec) | 262 | const execPromise = promisify1<string, string>(exec) |
@@ -287,13 +283,11 @@ export { | |||
287 | 283 | ||
288 | promisify0, | 284 | promisify0, |
289 | promisify1, | 285 | promisify1, |
286 | promisify2, | ||
290 | 287 | ||
291 | pseudoRandomBytesPromise, | 288 | pseudoRandomBytesPromise, |
292 | createPrivateKey, | 289 | createPrivateKey, |
293 | getPublicKey, | 290 | getPublicKey, |
294 | bcryptComparePromise, | ||
295 | bcryptGenSaltPromise, | ||
296 | bcryptHashPromise, | ||
297 | createTorrentPromise, | 291 | createTorrentPromise, |
298 | execPromise2, | 292 | execPromise2, |
299 | execPromise | 293 | execPromise |
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index e1a2f9503..f818ce8f1 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts | |||
@@ -51,7 +51,9 @@ export { | |||
51 | 51 | ||
52 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | 52 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { |
53 | if (!videoChannel) { | 53 | if (!videoChannel) { |
54 | `` | 54 | res.status(404) |
55 | .json({ error: 'Video channel not found' }) | ||
56 | .end() | ||
55 | 57 | ||
56 | return false | 58 | return false |
57 | } | 59 | } |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 9148df2eb..1424949d0 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,12 +1,17 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' | 4 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 5 | import { jsig, jsonld } from './custom-jsonld-signature' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
7 | import { cloneDeep } from 'lodash' | 7 | import { cloneDeep } from 'lodash' |
8 | import { createVerify } from 'crypto' | 8 | import { createVerify } from 'crypto' |
9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | 9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' |
10 | import * as bcrypt from 'bcrypt' | ||
11 | |||
12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | ||
13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | ||
14 | const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash) | ||
10 | 15 | ||
11 | const httpSignature = require('http-signature') | 16 | const httpSignature = require('http-signature') |
12 | 17 | ||
@@ -147,3 +152,5 @@ export { | |||
147 | cryptPassword, | 152 | cryptPassword, |
148 | signJsonLDObject | 153 | signJsonLDObject |
149 | } | 154 | } |
155 | |||
156 | // --------------------------------------------------------------------------- | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index a06add6b8..a1634ded4 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -6,6 +6,7 @@ import { isPluginNameValid, isPluginTypeValid, isPluginVersionValid, isNpmPlugin | |||
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 6 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc' |
8 | import { PluginModel } from '../../models/server/plugin' | 8 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model' | ||
9 | 10 | ||
10 | const servePluginStaticDirectoryValidator = [ | 11 | const servePluginStaticDirectoryValidator = [ |
11 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), | 12 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), |
@@ -48,13 +49,25 @@ const listPluginsValidator = [ | |||
48 | ] | 49 | ] |
49 | 50 | ||
50 | const installPluginValidator = [ | 51 | const installPluginValidator = [ |
51 | body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | 52 | body('npmName') |
53 | .optional() | ||
54 | .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), | ||
55 | body('path') | ||
56 | .optional() | ||
57 | .custom(isSafePath).withMessage('Should have a valid safe path'), | ||
52 | 58 | ||
53 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 59 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
54 | logger.debug('Checking installPluginValidator parameters', { parameters: req.body }) | 60 | logger.debug('Checking installPluginValidator parameters', { parameters: req.body }) |
55 | 61 | ||
56 | if (areValidationErrors(req, res)) return | 62 | if (areValidationErrors(req, res)) return |
57 | 63 | ||
64 | const body: InstallPlugin = req.body | ||
65 | if (!body.path && !body.npmName) { | ||
66 | return res.status(400) | ||
67 | .json({ error: 'Should have either a npmName or a path' }) | ||
68 | .end() | ||
69 | } | ||
70 | |||
58 | return next() | 71 | return next() |
59 | } | 72 | } |
60 | ] | 73 | ] |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index 60abaec65..226c08342 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -142,15 +142,17 @@ export class PluginModel extends Model<PluginModel> { | |||
142 | count: number, | 142 | count: number, |
143 | sort: string | 143 | sort: string |
144 | }) { | 144 | }) { |
145 | const { uninstalled = false } = options | ||
145 | const query: FindAndCountOptions = { | 146 | const query: FindAndCountOptions = { |
146 | offset: options.start, | 147 | offset: options.start, |
147 | limit: options.count, | 148 | limit: options.count, |
148 | order: getSort(options.sort), | 149 | order: getSort(options.sort), |
149 | where: {} | 150 | where: { |
151 | uninstalled | ||
152 | } | ||
150 | } | 153 | } |
151 | 154 | ||
152 | if (options.type) query.where['type'] = options.type | 155 | if (options.type) query.where['type'] = options.type |
153 | if (options.uninstalled) query.where['uninstalled'] = options.uninstalled | ||
154 | 156 | ||
155 | return PluginModel | 157 | return PluginModel |
156 | .findAndCountAll(query) | 158 | .findAndCountAll(query) |
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts index 8a008b8c6..d5f0a5457 100644 --- a/server/tests/api/search/search-activitypub-video-channels.ts +++ b/server/tests/api/search/search-activitypub-video-channels.ts | |||
@@ -67,6 +67,8 @@ describe('Test ActivityPub video channels search', function () { | |||
67 | }) | 67 | }) |
68 | 68 | ||
69 | it('Should not find a remote video channel', async function () { | 69 | it('Should not find a remote video channel', async function () { |
70 | this.timeout(15000) | ||
71 | |||
70 | { | 72 | { |
71 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' | 73 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' |
72 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | 74 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) |
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 2eec51aa4..67755022c 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { Netrc } from 'netrc-parser' | 1 | import { Netrc } from 'netrc-parser' |
2 | import { getAppNumber, isTestInstance } from '../helpers/core-utils' | 2 | import { getAppNumber, isTestInstance } from '../helpers/core-utils' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { getVideoChannel, root } from '../../shared/extra-utils' | 4 | import { root } from '../../shared/extra-utils/miscs/miscs' |
5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | ||
5 | import { Command } from 'commander' | 6 | import { Command } from 'commander' |
6 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
7 | 8 | ||
@@ -64,7 +65,11 @@ function deleteSettings () { | |||
64 | }) | 65 | }) |
65 | } | 66 | } |
66 | 67 | ||
67 | function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) { | 68 | function getRemoteObjectOrDie ( |
69 | program: any, | ||
70 | settings: Settings, | ||
71 | netrc: Netrc | ||
72 | ): { url: string, username: string, password: string } { | ||
68 | if (!program['url'] || !program['username'] || !program['password']) { | 73 | if (!program['url'] || !program['username'] || !program['password']) { |
69 | // No remote and we don't have program parameters: quit | 74 | // No remote and we don't have program parameters: quit |
70 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { | 75 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { |
@@ -161,6 +166,13 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, | |||
161 | return videoAttributes | 166 | return videoAttributes |
162 | } | 167 | } |
163 | 168 | ||
169 | function getServerCredentials (program: any) { | ||
170 | return Promise.all([ getSettings(), getNetrc() ]) | ||
171 | .then(([ settings, netrc ]) => { | ||
172 | return getRemoteObjectOrDie(program, settings, netrc) | ||
173 | }) | ||
174 | } | ||
175 | |||
164 | // --------------------------------------------------------------------------- | 176 | // --------------------------------------------------------------------------- |
165 | 177 | ||
166 | export { | 178 | export { |
@@ -172,6 +184,8 @@ export { | |||
172 | writeSettings, | 184 | writeSettings, |
173 | deleteSettings, | 185 | deleteSettings, |
174 | 186 | ||
187 | getServerCredentials, | ||
188 | |||
175 | buildCommonVideoOptions, | 189 | buildCommonVideoOptions, |
176 | buildVideoAttributesFromCommander | 190 | buildVideoAttributesFromCommander |
177 | } | 191 | } |
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index 1035d664a..d4ad56e47 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import * as prompt from 'prompt' | 2 | import * as prompt from 'prompt' |
3 | import { getSettings, writeSettings, getNetrc } from './cli' | 3 | import { getNetrc, getSettings, writeSettings } from './cli' |
4 | import { isHostValid } from '../helpers/custom-validators/servers' | ||
5 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | 4 | import { isUserUsernameValid } from '../helpers/custom-validators/users' |
5 | import { getAccessToken, login } from '../../shared/extra-utils' | ||
6 | 6 | ||
7 | const Table = require('cli-table') | 7 | const Table = require('cli-table') |
8 | 8 | ||
@@ -76,6 +76,14 @@ program | |||
76 | } | 76 | } |
77 | } | 77 | } |
78 | }, async (_, result) => { | 78 | }, async (_, result) => { |
79 | // Check credentials | ||
80 | try { | ||
81 | await getAccessToken(result.url, result.username, result.password) | ||
82 | } catch (err) { | ||
83 | console.error(err.message) | ||
84 | process.exit(-1) | ||
85 | } | ||
86 | |||
79 | await setInstance(result.url, result.username, result.password, program['default']) | 87 | await setInstance(result.url, result.username, result.password, program['default']) |
80 | 88 | ||
81 | process.exit(0) | 89 | process.exit(0) |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index d7bb00e02..1f0350442 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -11,7 +11,7 @@ import * as prompt from 'prompt' | |||
11 | import { remove } from 'fs-extra' | 11 | import { remove } from 'fs-extra' |
12 | import { sha256 } from '../helpers/core-utils' | 12 | import { sha256 } from '../helpers/core-utils' |
13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' | 13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli' | 14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' |
15 | 15 | ||
16 | type UserInfo = { | 16 | type UserInfo = { |
17 | username: string | 17 | username: string |
@@ -36,27 +36,25 @@ command | |||
36 | .option('-v, --verbose', 'Verbose mode') | 36 | .option('-v, --verbose', 'Verbose mode') |
37 | .parse(process.argv) | 37 | .parse(process.argv) |
38 | 38 | ||
39 | Promise.all([ getSettings(), getNetrc() ]) | 39 | getServerCredentials(command) |
40 | .then(([ settings, netrc ]) => { | 40 | .then(({ url, username, password }) => { |
41 | const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc) | 41 | if (!program[ 'targetUrl' ]) { |
42 | console.error('--targetUrl field is required.') | ||
42 | 43 | ||
43 | if (!program[ 'targetUrl' ]) { | 44 | process.exit(-1) |
44 | console.error('--targetUrl field is required.') | 45 | } |
45 | |||
46 | process.exit(-1) | ||
47 | } | ||
48 | 46 | ||
49 | removeEndSlashes(url) | 47 | removeEndSlashes(url) |
50 | removeEndSlashes(program[ 'targetUrl' ]) | 48 | removeEndSlashes(program[ 'targetUrl' ]) |
51 | 49 | ||
52 | const user = { username, password } | 50 | const user = { username, password } |
53 | 51 | ||
54 | run(url, user) | 52 | run(url, user) |
55 | .catch(err => { | 53 | .catch(err => { |
56 | console.error(err) | 54 | console.error(err) |
57 | process.exit(-1) | 55 | process.exit(-1) |
58 | }) | 56 | }) |
59 | }) | 57 | }) |
60 | 58 | ||
61 | async function run (url: string, user: UserInfo) { | 59 | async function run (url: string, user: UserInfo) { |
62 | if (!user.password) { | 60 | if (!user.password) { |
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts new file mode 100644 index 000000000..d5e024383 --- /dev/null +++ b/server/tools/peertube-plugins.ts | |||
@@ -0,0 +1,162 @@ | |||
1 | import * as program from 'commander' | ||
2 | import { PluginType } from '../../shared/models/plugins/plugin.type' | ||
3 | import { getAccessToken } from '../../shared/extra-utils/users/login' | ||
4 | import { getMyUserInformation } from '../../shared/extra-utils/users/users' | ||
5 | import { installPlugin, listPlugins, uninstallPlugin } from '../../shared/extra-utils/server/plugins' | ||
6 | import { getServerCredentials } from './cli' | ||
7 | import { User, UserRole } from '../../shared/models/users' | ||
8 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' | ||
9 | import { isAbsolute } from 'path' | ||
10 | |||
11 | const Table = require('cli-table') | ||
12 | |||
13 | program | ||
14 | .name('plugins') | ||
15 | .usage('[command] [options]') | ||
16 | |||
17 | program | ||
18 | .command('list') | ||
19 | .description('List installed plugins') | ||
20 | .option('-u, --url <url>', 'Server url') | ||
21 | .option('-U, --username <username>', 'Username') | ||
22 | .option('-p, --password <token>', 'Password') | ||
23 | .option('-t, --only-themes', 'List themes only') | ||
24 | .option('-P, --only-plugins', 'List plugins only') | ||
25 | .action(() => pluginsListCLI()) | ||
26 | |||
27 | program | ||
28 | .command('install') | ||
29 | .description('Install a plugin or a theme') | ||
30 | .option('-u, --url <url>', 'Server url') | ||
31 | .option('-U, --username <username>', 'Username') | ||
32 | .option('-p, --password <token>', 'Password') | ||
33 | .option('-P --path <path>', 'Install from a path') | ||
34 | .option('-n, --npm-name <npmName>', 'Install from npm') | ||
35 | .action((options) => installPluginCLI(options)) | ||
36 | |||
37 | program | ||
38 | .command('uninstall') | ||
39 | .description('Uninstall a plugin or a theme') | ||
40 | .option('-u, --url <url>', 'Server url') | ||
41 | .option('-U, --username <username>', 'Username') | ||
42 | .option('-p, --password <token>', 'Password') | ||
43 | .option('-n, --npm-name <npmName>', 'NPM plugin/theme name') | ||
44 | .action(options => uninstallPluginCLI(options)) | ||
45 | |||
46 | if (!process.argv.slice(2).length) { | ||
47 | program.outputHelp() | ||
48 | } | ||
49 | |||
50 | program.parse(process.argv) | ||
51 | |||
52 | // ---------------------------------------------------------------------------- | ||
53 | |||
54 | async function pluginsListCLI () { | ||
55 | const { url, username, password } = await getServerCredentials(program) | ||
56 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
57 | |||
58 | let type: PluginType | ||
59 | if (program['onlyThemes']) type = PluginType.THEME | ||
60 | if (program['onlyPlugins']) type = PluginType.PLUGIN | ||
61 | |||
62 | const res = await listPlugins({ | ||
63 | url, | ||
64 | accessToken, | ||
65 | start: 0, | ||
66 | count: 100, | ||
67 | sort: 'name', | ||
68 | type | ||
69 | }) | ||
70 | const plugins: PeerTubePlugin[] = res.body.data | ||
71 | |||
72 | const table = new Table({ | ||
73 | head: ['name', 'version', 'homepage'], | ||
74 | colWidths: [ 50, 10, 50 ] | ||
75 | }) | ||
76 | |||
77 | for (const plugin of plugins) { | ||
78 | const npmName = plugin.type === PluginType.PLUGIN | ||
79 | ? 'peertube-plugin-' + plugin.name | ||
80 | : 'peertube-theme-' + plugin.name | ||
81 | |||
82 | table.push([ | ||
83 | npmName, | ||
84 | plugin.version, | ||
85 | plugin.homepage | ||
86 | ]) | ||
87 | } | ||
88 | |||
89 | console.log(table.toString()) | ||
90 | process.exit(0) | ||
91 | } | ||
92 | |||
93 | async function installPluginCLI (options: any) { | ||
94 | if (!options['path'] && !options['npmName']) { | ||
95 | console.error('You need to specify the npm name or the path of the plugin you want to install.\n') | ||
96 | program.outputHelp() | ||
97 | process.exit(-1) | ||
98 | } | ||
99 | |||
100 | if (options['path'] && !isAbsolute(options['path'])) { | ||
101 | console.error('Path should be absolute.') | ||
102 | process.exit(-1) | ||
103 | } | ||
104 | |||
105 | const { url, username, password } = await getServerCredentials(options) | ||
106 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
107 | |||
108 | try { | ||
109 | await installPlugin({ | ||
110 | url, | ||
111 | accessToken, | ||
112 | npmName: options['npmName'], | ||
113 | path: options['path'] | ||
114 | }) | ||
115 | } catch (err) { | ||
116 | console.error('Cannot install plugin.', err) | ||
117 | process.exit(-1) | ||
118 | return | ||
119 | } | ||
120 | |||
121 | console.log('Plugin installed.') | ||
122 | process.exit(0) | ||
123 | } | ||
124 | |||
125 | async function uninstallPluginCLI (options: any) { | ||
126 | if (!options['npmName']) { | ||
127 | console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n') | ||
128 | program.outputHelp() | ||
129 | process.exit(-1) | ||
130 | } | ||
131 | |||
132 | const { url, username, password } = await getServerCredentials(options) | ||
133 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
134 | |||
135 | try { | ||
136 | await uninstallPlugin({ | ||
137 | url, | ||
138 | accessToken, | ||
139 | npmName: options[ 'npmName' ] | ||
140 | }) | ||
141 | } catch (err) { | ||
142 | console.error('Cannot uninstall plugin.', err) | ||
143 | process.exit(-1) | ||
144 | return | ||
145 | } | ||
146 | |||
147 | console.log('Plugin uninstalled.') | ||
148 | process.exit(0) | ||
149 | } | ||
150 | |||
151 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
152 | const accessToken = await getAccessToken(url, username, password) | ||
153 | const resMe = await getMyUserInformation(url, accessToken) | ||
154 | const me: User = resMe.body | ||
155 | |||
156 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
157 | console.error('Cannot list plugins if you are not administrator.') | ||
158 | process.exit(-1) | ||
159 | } | ||
160 | |||
161 | return accessToken | ||
162 | } | ||
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts index c00205e8f..d9f9a8ead 100644 --- a/server/tools/peertube-upload.ts +++ b/server/tools/peertube-upload.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import { access, constants } from 'fs-extra' | 2 | import { access, constants } from 'fs-extra' |
3 | import { isAbsolute } from 'path' | 3 | import { isAbsolute } from 'path' |
4 | import { getClient, login } from '../../shared/extra-utils' | 4 | import { getAccessToken } from '../../shared/extra-utils' |
5 | import { uploadVideo } from '../../shared/extra-utils/' | 5 | import { uploadVideo } from '../../shared/extra-utils/' |
6 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli' | 6 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' |
7 | 7 | ||
8 | let command = program | 8 | let command = program |
9 | .name('upload') | 9 | .name('upload') |
@@ -11,7 +11,6 @@ let command = program | |||
11 | command = buildCommonVideoOptions(command) | 11 | command = buildCommonVideoOptions(command) |
12 | 12 | ||
13 | command | 13 | command |
14 | |||
15 | .option('-u, --url <url>', 'Server url') | 14 | .option('-u, --url <url>', 'Server url') |
16 | .option('-U, --username <username>', 'Username') | 15 | .option('-U, --username <username>', 'Username') |
17 | .option('-p, --password <token>', 'Password') | 16 | .option('-p, --password <token>', 'Password') |
@@ -20,44 +19,28 @@ command | |||
20 | .option('-f, --file <file>', 'Video absolute file path') | 19 | .option('-f, --file <file>', 'Video absolute file path') |
21 | .parse(process.argv) | 20 | .parse(process.argv) |
22 | 21 | ||
23 | Promise.all([ getSettings(), getNetrc() ]) | 22 | getServerCredentials(command) |
24 | .then(([ settings, netrc ]) => { | 23 | .then(({ url, username, password }) => { |
25 | const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc) | 24 | if (!program[ 'videoName' ] || !program[ 'file' ]) { |
26 | 25 | if (!program[ 'videoName' ]) console.error('--video-name is required.') | |
27 | if (!program[ 'videoName' ] || !program[ 'file' ]) { | 26 | if (!program[ 'file' ]) console.error('--file is required.') |
28 | if (!program[ 'videoName' ]) console.error('--video-name is required.') | ||
29 | if (!program[ 'file' ]) console.error('--file is required.') | ||
30 | 27 | ||
31 | process.exit(-1) | 28 | process.exit(-1) |
32 | } | 29 | } |
33 | 30 | ||
34 | if (isAbsolute(program[ 'file' ]) === false) { | 31 | if (isAbsolute(program[ 'file' ]) === false) { |
35 | console.error('File path should be absolute.') | 32 | console.error('File path should be absolute.') |
36 | process.exit(-1) | 33 | process.exit(-1) |
37 | } | 34 | } |
38 | 35 | ||
39 | run(url, username, password).catch(err => { | 36 | run(url, username, password).catch(err => { |
40 | console.error(err) | 37 | console.error(err) |
41 | process.exit(-1) | 38 | process.exit(-1) |
42 | }) | 39 | }) |
43 | }) | 40 | }) |
44 | 41 | ||
45 | async function run (url: string, username: string, password: string) { | 42 | async function run (url: string, username: string, password: string) { |
46 | const resClient = await getClient(url) | 43 | const accessToken = await getAccessToken(url, username, password) |
47 | const client = { | ||
48 | id: resClient.body.client_id, | ||
49 | secret: resClient.body.client_secret | ||
50 | } | ||
51 | |||
52 | const user = { username, password } | ||
53 | |||
54 | let accessToken: string | ||
55 | try { | ||
56 | const res = await login(url, client, user) | ||
57 | accessToken = res.body.access_token | ||
58 | } catch (err) { | ||
59 | throw new Error('Cannot authenticate. Please check your username/password.') | ||
60 | } | ||
61 | 44 | ||
62 | await access(program[ 'file' ], constants.F_OK) | 45 | await access(program[ 'file' ], constants.F_OK) |
63 | 46 | ||
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts index daa5586c3..e79a7e041 100644 --- a/server/tools/peertube.ts +++ b/server/tools/peertube.ts | |||
@@ -18,13 +18,10 @@ program | |||
18 | .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') | 18 | .command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token') |
19 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') | 19 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') |
20 | .command('repl', 'initiate a REPL to access internals') | 20 | .command('repl', 'initiate a REPL to access internals') |
21 | .command('plugins [action]', 'manage plugins on a local instance').alias('p') | ||
21 | 22 | ||
22 | /* Not Yet Implemented */ | 23 | /* Not Yet Implemented */ |
23 | program | 24 | program |
24 | .command('plugins [action]', | ||
25 | 'manage plugins on a local instance', | ||
26 | { noHelp: true } as program.CommandOptions | ||
27 | ).alias('p') | ||
28 | .command('diagnostic [action]', | 25 | .command('diagnostic [action]', |
29 | 'like couple therapy, but for your instance', | 26 | 'like couple therapy, but for your instance', |
30 | { noHelp: true } as program.CommandOptions | 27 | { noHelp: true } as program.CommandOptions |
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts index 9d0bbaa38..53ddaa681 100644 --- a/shared/extra-utils/index.ts +++ b/shared/extra-utils/index.ts | |||
@@ -11,6 +11,7 @@ export * from './server/follows' | |||
11 | export * from './requests/requests' | 11 | export * from './requests/requests' |
12 | export * from './requests/check-api-params' | 12 | export * from './requests/check-api-params' |
13 | export * from './server/servers' | 13 | export * from './server/servers' |
14 | export * from './server/plugins' | ||
14 | export * from './videos/services' | 15 | export * from './videos/services' |
15 | export * from './videos/video-playlists' | 16 | export * from './videos/video-playlists' |
16 | export * from './users/users' | 17 | export * from './users/users' |
diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts index fb6430e4f..42250886c 100644 --- a/shared/extra-utils/miscs/miscs.ts +++ b/shared/extra-utils/miscs/miscs.ts | |||
@@ -8,7 +8,7 @@ import { pathExists, readFile } from 'fs-extra' | |||
8 | import * as ffmpeg from 'fluent-ffmpeg' | 8 | import * as ffmpeg from 'fluent-ffmpeg' |
9 | 9 | ||
10 | const expect = chai.expect | 10 | const expect = chai.expect |
11 | let webtorrent = new WebTorrent() | 11 | let webtorrent: WebTorrent.Instance |
12 | 12 | ||
13 | function immutableAssign <T, U> (target: T, source: U) { | 13 | function immutableAssign <T, U> (target: T, source: U) { |
14 | return Object.assign<{}, T, U>({}, target, source) | 14 | return Object.assign<{}, T, U>({}, target, source) |
@@ -27,6 +27,9 @@ function wait (milliseconds: number) { | |||
27 | } | 27 | } |
28 | 28 | ||
29 | function webtorrentAdd (torrent: string, refreshWebTorrent = false) { | 29 | function webtorrentAdd (torrent: string, refreshWebTorrent = false) { |
30 | const WebTorrent = require('webtorrent') | ||
31 | |||
32 | if (!webtorrent) webtorrent = new WebTorrent() | ||
30 | if (refreshWebTorrent === true) webtorrent = new WebTorrent() | 33 | if (refreshWebTorrent === true) webtorrent = new WebTorrent() |
31 | 34 | ||
32 | return new Promise<WebTorrent.Torrent>(res => webtorrent.add(torrent, res)) | 35 | return new Promise<WebTorrent.Torrent>(res => webtorrent.add(torrent, res)) |
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts new file mode 100644 index 000000000..6cd7cd17a --- /dev/null +++ b/shared/extra-utils/server/plugins.ts | |||
@@ -0,0 +1,125 @@ | |||
1 | import { makeGetRequest, makePostBodyRequest } from '../requests/requests' | ||
2 | import { PluginType } from '../../models/plugins/plugin.type' | ||
3 | |||
4 | function listPlugins (parameters: { | ||
5 | url: string, | ||
6 | accessToken: string, | ||
7 | start?: number, | ||
8 | count?: number, | ||
9 | sort?: string, | ||
10 | type?: PluginType, | ||
11 | expectedStatus?: number | ||
12 | }) { | ||
13 | const { url, accessToken, start, count, sort, type, expectedStatus = 200 } = parameters | ||
14 | const path = '/api/v1/plugins' | ||
15 | |||
16 | return makeGetRequest({ | ||
17 | url, | ||
18 | path, | ||
19 | token: accessToken, | ||
20 | query: { | ||
21 | start, | ||
22 | count, | ||
23 | sort, | ||
24 | type | ||
25 | }, | ||
26 | statusCodeExpected: expectedStatus | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | function getPlugin (parameters: { | ||
31 | url: string, | ||
32 | accessToken: string, | ||
33 | npmName: string, | ||
34 | expectedStatus?: number | ||
35 | }) { | ||
36 | const { url, accessToken, npmName, expectedStatus = 200 } = parameters | ||
37 | const path = '/api/v1/plugins/' + npmName | ||
38 | |||
39 | return makeGetRequest({ | ||
40 | url, | ||
41 | path, | ||
42 | token: accessToken, | ||
43 | statusCodeExpected: expectedStatus | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | function getPluginSettings (parameters: { | ||
48 | url: string, | ||
49 | accessToken: string, | ||
50 | npmName: string, | ||
51 | expectedStatus?: number | ||
52 | }) { | ||
53 | const { url, accessToken, npmName, expectedStatus = 200 } = parameters | ||
54 | const path = '/api/v1/plugins/' + npmName + '/settings' | ||
55 | |||
56 | return makeGetRequest({ | ||
57 | url, | ||
58 | path, | ||
59 | token: accessToken, | ||
60 | statusCodeExpected: expectedStatus | ||
61 | }) | ||
62 | } | ||
63 | |||
64 | function getPluginRegisteredSettings (parameters: { | ||
65 | url: string, | ||
66 | accessToken: string, | ||
67 | npmName: string, | ||
68 | expectedStatus?: number | ||
69 | }) { | ||
70 | const { url, accessToken, npmName, expectedStatus = 200 } = parameters | ||
71 | const path = '/api/v1/plugins/' + npmName + '/registered-settings' | ||
72 | |||
73 | return makeGetRequest({ | ||
74 | url, | ||
75 | path, | ||
76 | token: accessToken, | ||
77 | statusCodeExpected: expectedStatus | ||
78 | }) | ||
79 | } | ||
80 | |||
81 | function installPlugin (parameters: { | ||
82 | url: string, | ||
83 | accessToken: string, | ||
84 | path?: string, | ||
85 | npmName?: string | ||
86 | expectedStatus?: number | ||
87 | }) { | ||
88 | const { url, accessToken, npmName, path, expectedStatus = 204 } = parameters | ||
89 | const apiPath = '/api/v1/plugins/install' | ||
90 | |||
91 | return makePostBodyRequest({ | ||
92 | url, | ||
93 | path: apiPath, | ||
94 | token: accessToken, | ||
95 | fields: { npmName, path }, | ||
96 | statusCodeExpected: expectedStatus | ||
97 | }) | ||
98 | } | ||
99 | |||
100 | function uninstallPlugin (parameters: { | ||
101 | url: string, | ||
102 | accessToken: string, | ||
103 | npmName: string | ||
104 | expectedStatus?: number | ||
105 | }) { | ||
106 | const { url, accessToken, npmName, expectedStatus = 204 } = parameters | ||
107 | const apiPath = '/api/v1/plugins/uninstall' | ||
108 | |||
109 | return makePostBodyRequest({ | ||
110 | url, | ||
111 | path: apiPath, | ||
112 | token: accessToken, | ||
113 | fields: { npmName }, | ||
114 | statusCodeExpected: expectedStatus | ||
115 | }) | ||
116 | } | ||
117 | |||
118 | export { | ||
119 | listPlugins, | ||
120 | installPlugin, | ||
121 | getPlugin, | ||
122 | uninstallPlugin, | ||
123 | getPluginSettings, | ||
124 | getPluginRegisteredSettings | ||
125 | } | ||
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts index 4c7d6862a..9167ebe5b 100644 --- a/shared/extra-utils/server/servers.ts +++ b/shared/extra-utils/server/servers.ts | |||
@@ -3,7 +3,7 @@ | |||
3 | import { ChildProcess, exec, fork } from 'child_process' | 3 | import { ChildProcess, exec, fork } from 'child_process' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import { root, wait } from '../miscs/miscs' | 5 | import { root, wait } from '../miscs/miscs' |
6 | import { copy, readdir, readFile, remove } from 'fs-extra' | 6 | import { copy, pathExists, readdir, readFile, remove } from 'fs-extra' |
7 | import { existsSync } from 'fs' | 7 | import { existsSync } from 'fs' |
8 | import { expect } from 'chai' | 8 | import { expect } from 'chai' |
9 | import { VideoChannel } from '../../models/videos' | 9 | import { VideoChannel } from '../../models/videos' |
@@ -241,20 +241,22 @@ async function reRunServer (server: ServerInfo, configOverride?: any) { | |||
241 | return server | 241 | return server |
242 | } | 242 | } |
243 | 243 | ||
244 | async function checkTmpIsEmpty (server: ServerInfo) { | 244 | function checkTmpIsEmpty (server: ServerInfo) { |
245 | return checkDirectoryIsEmpty(server, 'tmp') | 245 | return checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css' ]) |
246 | } | 246 | } |
247 | 247 | ||
248 | async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) { | 248 | async function checkDirectoryIsEmpty (server: ServerInfo, directory: string, exceptions: string[] = []) { |
249 | const testDirectory = 'test' + server.internalServerNumber | 249 | const testDirectory = 'test' + server.internalServerNumber |
250 | 250 | ||
251 | const directoryPath = join(root(), testDirectory, directory) | 251 | const directoryPath = join(root(), testDirectory, directory) |
252 | 252 | ||
253 | const directoryExists = existsSync(directoryPath) | 253 | const directoryExists = await pathExists(directoryPath) |
254 | expect(directoryExists).to.be.true | 254 | expect(directoryExists).to.be.true |
255 | 255 | ||
256 | const files = await readdir(directoryPath) | 256 | const files = await readdir(directoryPath) |
257 | expect(files).to.have.lengthOf(0) | 257 | const filtered = files.filter(f => exceptions.includes(f) === false) |
258 | |||
259 | expect(filtered).to.have.lengthOf(0) | ||
258 | } | 260 | } |
259 | 261 | ||
260 | function killallServers (servers: ServerInfo[]) { | 262 | function killallServers (servers: ServerInfo[]) { |
diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts index ddeb9df2a..f9bfb3cb3 100644 --- a/shared/extra-utils/users/login.ts +++ b/shared/extra-utils/users/login.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | 2 | ||
3 | import { ServerInfo } from '../server/servers' | 3 | import { ServerInfo } from '../server/servers' |
4 | import { getClient } from '../server/clients' | ||
4 | 5 | ||
5 | type Client = { id: string, secret: string } | 6 | type Client = { id: string, secret: string } |
6 | type User = { username: string, password: string } | 7 | type User = { username: string, password: string } |
@@ -38,6 +39,23 @@ async function userLogin (server: Server, user: User, expectedStatus = 200) { | |||
38 | return res.body.access_token as string | 39 | return res.body.access_token as string |
39 | } | 40 | } |
40 | 41 | ||
42 | async function getAccessToken (url: string, username: string, password: string) { | ||
43 | const resClient = await getClient(url) | ||
44 | const client = { | ||
45 | id: resClient.body.client_id, | ||
46 | secret: resClient.body.client_secret | ||
47 | } | ||
48 | |||
49 | const user = { username, password } | ||
50 | |||
51 | try { | ||
52 | const res = await login(url, client, user) | ||
53 | return res.body.access_token | ||
54 | } catch (err) { | ||
55 | throw new Error('Cannot authenticate. Please check your username/password.') | ||
56 | } | ||
57 | } | ||
58 | |||
41 | function setAccessTokensToServers (servers: ServerInfo[]) { | 59 | function setAccessTokensToServers (servers: ServerInfo[]) { |
42 | const tasks: Promise<any>[] = [] | 60 | const tasks: Promise<any>[] = [] |
43 | 61 | ||
@@ -55,6 +73,7 @@ export { | |||
55 | login, | 73 | login, |
56 | serverLogin, | 74 | serverLogin, |
57 | userLogin, | 75 | userLogin, |
76 | getAccessToken, | ||
58 | setAccessTokensToServers, | 77 | setAccessTokensToServers, |
59 | Server, | 78 | Server, |
60 | Client, | 79 | Client, |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 1c39881d6..5fa8cde0c 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' | 2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' |
3 | |||
4 | import { UserCreate, UserRole } from '../../index' | ||
5 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' | 3 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' |
6 | import { ServerInfo, userLogin } from '..' | ||
7 | import { UserAdminFlag } from '../../models/users/user-flag.model' | 4 | import { UserAdminFlag } from '../../models/users/user-flag.model' |
8 | import { UserRegister } from '../../models/users/user-register.model' | 5 | import { UserRegister } from '../../models/users/user-register.model' |
6 | import { UserRole } from '../../models/users/user-role' | ||
7 | import { ServerInfo } from '../server/servers' | ||
8 | import { userLogin } from './login' | ||
9 | 9 | ||
10 | type CreateUserArgs = { url: string, | 10 | type CreateUserArgs = { url: string, |
11 | accessToken: string, | 11 | accessToken: string, |
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts index 3e79cf15a..053842331 100644 --- a/shared/extra-utils/videos/video-channels.ts +++ b/shared/extra-utils/videos/video-channels.ts | |||
@@ -1,8 +1,10 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' | 2 | import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' |
3 | import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' | ||
3 | import { makeGetRequest, updateAvatarRequest } from '../requests/requests' | 4 | import { makeGetRequest, updateAvatarRequest } from '../requests/requests' |
4 | import { getMyUserInformation, ServerInfo } from '..' | 5 | import { ServerInfo } from '../server/servers' |
5 | import { User } from '../..' | 6 | import { User } from '../../models/users/user.model' |
7 | import { getMyUserInformation } from '../users/users' | ||
6 | 8 | ||
7 | function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { | 9 | function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { |
8 | const path = '/api/v1/video-channels' | 10 | const path = '/api/v1/video-channels' |
diff --git a/shared/models/plugins/install-plugin.model.ts b/shared/models/plugins/install-plugin.model.ts index 03d87fe57..b1b46fa08 100644 --- a/shared/models/plugins/install-plugin.model.ts +++ b/shared/models/plugins/install-plugin.model.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export interface InstallPlugin { | 1 | export interface InstallPlugin { |
2 | npmName: string | 2 | npmName?: string |
3 | path?: string | ||
3 | } | 4 | } |