1
0
Fork 0

WIP plugins: move plugin CLI in peertube script

Install/uninstall/list plugins remotely
This commit is contained in:
Chocobozzz 2019-07-11 17:23:24 +02:00 committed by Chocobozzz
parent dba85a1e9e
commit 8d2be0ed7b
26 changed files with 452 additions and 191 deletions

View File

@ -32,8 +32,6 @@
"clean:server:test": "scripty",
"watch:client": "scripty",
"watch:server": "scripty",
"plugin:install": "node ./dist/scripts/plugin/install.js",
"plugin:uninstall": "node ./dist/scripts/plugin/uninstall.js",
"danger:clean:dev": "scripty",
"danger:clean:prod": "scripty",
"danger:clean:modules": "scripty",
@ -45,6 +43,7 @@
"dev": "scripty",
"dev:server": "scripty",
"dev:client": "scripty",
"dev:cli": "scripty",
"start": "node dist/server",
"start:server": "node dist/server --no-client",
"update-host": "node ./dist/scripts/update-host.js",

15
scripts/dev/cli.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/sh
set -eu
rm -rf ./dist/server/tools/
(
cd ./server/tools
yarn install --pure-lockfile
)
mkdir -p "./dist/server/tools"
cp -r "./server/tools/node_modules" "./dist/server/tools"
npm run tsc -- --watch --project ./server/tools/tsconfig.json

View File

@ -1,39 +0,0 @@
import { initDatabaseModels } from '../../server/initializers/database'
import * as program from 'commander'
import { PluginManager } from '../../server/lib/plugins/plugin-manager'
import { isAbsolute } from 'path'
program
.option('-n, --plugin-name [pluginName]', 'Plugin name to install')
.option('-v, --plugin-version [pluginVersion]', 'Plugin version to install')
.option('-p, --plugin-path [pluginPath]', 'Path of the plugin you want to install')
.parse(process.argv)
if (!program['pluginName'] && !program['pluginPath']) {
console.error('You need to specify a plugin name with the desired version, or a plugin path.')
process.exit(-1)
}
if (program['pluginName'] && !program['pluginVersion']) {
console.error('You need to specify a the version of the plugin you want to install.')
process.exit(-1)
}
if (program['pluginPath'] && !isAbsolute(program['pluginPath'])) {
console.error('Plugin path should be absolute.')
process.exit(-1)
}
run()
.then(() => process.exit(0))
.catch(err => {
console.error(err)
process.exit(-1)
})
async function run () {
await initDatabaseModels(true)
const toInstall = program['pluginName'] || program['pluginPath']
await PluginManager.Instance.install(toInstall, program['pluginVersion'], !!program['pluginPath'])
}

View File

@ -1,27 +0,0 @@
import { initDatabaseModels } from '../../server/initializers/database'
import * as program from 'commander'
import { PluginManager } from '../../server/lib/plugins/plugin-manager'
import { isAbsolute } from 'path'
program
.option('-n, --package-name [packageName]', 'Package name to install')
.parse(process.argv)
if (!program['packageName']) {
console.error('You need to specify the plugin name.')
process.exit(-1)
}
run()
.then(() => process.exit(0))
.catch(err => {
console.error(err)
process.exit(-1)
})
async function run () {
await initDatabaseModels(true)
const toUninstall = program['packageName']
await PluginManager.Instance.uninstall(toUninstall)
}

View File

@ -21,6 +21,7 @@ import {
import { PluginManager } from '../../lib/plugins/plugin-manager'
import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model'
import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model'
import { logger } from '../../helpers/logger'
const pluginRouter = express.Router()
@ -46,7 +47,7 @@ pluginRouter.get('/:npmName/registered-settings',
authenticate,
ensureUserHasRight(UserRight.MANAGE_PLUGINS),
asyncMiddleware(existingPluginValidator),
asyncMiddleware(getPluginRegisteredSettings)
getPluginRegisteredSettings
)
pluginRouter.put('/:npmName/settings',
@ -101,7 +102,14 @@ function getPlugin (req: express.Request, res: express.Response) {
async function installPlugin (req: express.Request, res: express.Response) {
const body: InstallPlugin = req.body
await PluginManager.Instance.install(body.npmName)
const fromDisk = !!body.path
const toInstall = body.npmName || body.path
try {
await PluginManager.Instance.install(toInstall, undefined, fromDisk)
} catch (err) {
logger.warn('Cannot install plugin %s.', toInstall, { err })
return res.sendStatus(400)
}
return res.sendStatus(204)
}
@ -114,10 +122,10 @@ async function uninstallPlugin (req: express.Request, res: express.Response) {
return res.sendStatus(204)
}
async function getPluginRegisteredSettings (req: express.Request, res: express.Response) {
function getPluginRegisteredSettings (req: express.Request, res: express.Response) {
const plugin = res.locals.plugin
const settings = await PluginManager.Instance.getSettings(plugin.name)
const settings = PluginManager.Instance.getSettings(plugin.name)
return res.json({
settings

View File

@ -1,30 +0,0 @@
import * as express from 'express'
import { join } from 'path'
import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
const themesRouter = express.Router()
themesRouter.get('/:themeName/:themeVersion/css/:staticEndpoint(*)',
serveThemeCSSValidator,
serveThemeCSSDirectory
)
// ---------------------------------------------------------------------------
export {
themesRouter
}
// ---------------------------------------------------------------------------
function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
const plugin: RegisteredPlugin = res.locals.registeredPlugin
const staticEndpoint = req.params.staticEndpoint
if (plugin.css.includes(staticEndpoint) === false) {
return res.sendStatus(404)
}
return res.sendFile(join(plugin.path, staticEndpoint))
}

View File

@ -3,7 +3,6 @@
Useful to avoid circular dependencies.
*/
import * as bcrypt from 'bcrypt'
import * as createTorrent from 'create-torrent'
import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto'
import { isAbsolute, join } from 'path'
@ -258,9 +257,6 @@ function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => vo
const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
const createTorrentPromise = promisify2<string, any, any>(createTorrent)
const execPromise2 = promisify2<string, any, string>(exec)
const execPromise = promisify1<string, string>(exec)
@ -287,13 +283,11 @@ export {
promisify0,
promisify1,
promisify2,
pseudoRandomBytesPromise,
createPrivateKey,
getPublicKey,
bcryptComparePromise,
bcryptGenSaltPromise,
bcryptHashPromise,
createTorrentPromise,
execPromise2,
execPromise

View File

@ -51,7 +51,9 @@ export {
function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
if (!videoChannel) {
``
res.status(404)
.json({ error: 'Video channel not found' })
.end()
return false
}

View File

@ -1,12 +1,17 @@
import { Request } from 'express'
import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
import { ActorModel } from '../models/activitypub/actor'
import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils'
import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
import { jsig, jsonld } from './custom-jsonld-signature'
import { logger } from './logger'
import { cloneDeep } from 'lodash'
import { createVerify } from 'crypto'
import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
import * as bcrypt from 'bcrypt'
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
const httpSignature = require('http-signature')
@ -147,3 +152,5 @@ export {
cryptPassword,
signJsonLDObject
}
// ---------------------------------------------------------------------------

View File

@ -6,6 +6,7 @@ import { isPluginNameValid, isPluginTypeValid, isPluginVersionValid, isNpmPlugin
import { PluginManager } from '../../lib/plugins/plugin-manager'
import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc'
import { PluginModel } from '../../models/server/plugin'
import { InstallPlugin } from '../../../shared/models/plugins/install-plugin.model'
const servePluginStaticDirectoryValidator = [
param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'),
@ -48,13 +49,25 @@ const listPluginsValidator = [
]
const installPluginValidator = [
body('npmName').custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'),
body('npmName')
.optional()
.custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'),
body('path')
.optional()
.custom(isSafePath).withMessage('Should have a valid safe path'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking installPluginValidator parameters', { parameters: req.body })
if (areValidationErrors(req, res)) return
const body: InstallPlugin = req.body
if (!body.path && !body.npmName) {
return res.status(400)
.json({ error: 'Should have either a npmName or a path' })
.end()
}
return next()
}
]

View File

@ -142,15 +142,17 @@ export class PluginModel extends Model<PluginModel> {
count: number,
sort: string
}) {
const { uninstalled = false } = options
const query: FindAndCountOptions = {
offset: options.start,
limit: options.count,
order: getSort(options.sort),
where: {}
where: {
uninstalled
}
}
if (options.type) query.where['type'] = options.type
if (options.uninstalled) query.where['uninstalled'] = options.uninstalled
return PluginModel
.findAndCountAll(query)

View File

@ -67,6 +67,8 @@ describe('Test ActivityPub video channels search', function () {
})
it('Should not find a remote video channel', async function () {
this.timeout(15000)
{
const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3'
const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken)

View File

@ -1,7 +1,8 @@
import { Netrc } from 'netrc-parser'
import { getAppNumber, isTestInstance } from '../helpers/core-utils'
import { join } from 'path'
import { getVideoChannel, root } from '../../shared/extra-utils'
import { root } from '../../shared/extra-utils/miscs/miscs'
import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
import { Command } from 'commander'
import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
@ -64,7 +65,11 @@ function deleteSettings () {
})
}
function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) {
function getRemoteObjectOrDie (
program: any,
settings: Settings,
netrc: Netrc
): { url: string, username: string, password: string } {
if (!program['url'] || !program['username'] || !program['password']) {
// No remote and we don't have program parameters: quit
if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
@ -161,6 +166,13 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
return videoAttributes
}
function getServerCredentials (program: any) {
return Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => {
return getRemoteObjectOrDie(program, settings, netrc)
})
}
// ---------------------------------------------------------------------------
export {
@ -172,6 +184,8 @@ export {
writeSettings,
deleteSettings,
getServerCredentials,
buildCommonVideoOptions,
buildVideoAttributesFromCommander
}

View File

@ -1,8 +1,8 @@
import * as program from 'commander'
import * as prompt from 'prompt'
import { getSettings, writeSettings, getNetrc } from './cli'
import { isHostValid } from '../helpers/custom-validators/servers'
import { getNetrc, getSettings, writeSettings } from './cli'
import { isUserUsernameValid } from '../helpers/custom-validators/users'
import { getAccessToken, login } from '../../shared/extra-utils'
const Table = require('cli-table')
@ -76,6 +76,14 @@ program
}
}
}, async (_, result) => {
// Check credentials
try {
await getAccessToken(result.url, result.username, result.password)
} catch (err) {
console.error(err.message)
process.exit(-1)
}
await setInstance(result.url, result.username, result.password, program['default'])
process.exit(0)

View File

@ -11,7 +11,7 @@ import * as prompt from 'prompt'
import { remove } from 'fs-extra'
import { sha256 } from '../helpers/core-utils'
import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
type UserInfo = {
username: string
@ -36,27 +36,25 @@ command
.option('-v, --verbose', 'Verbose mode')
.parse(process.argv)
Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => {
const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
getServerCredentials(command)
.then(({ url, username, password }) => {
if (!program[ 'targetUrl' ]) {
console.error('--targetUrl field is required.')
if (!program[ 'targetUrl' ]) {
console.error('--targetUrl field is required.')
process.exit(-1)
}
process.exit(-1)
}
removeEndSlashes(url)
removeEndSlashes(program[ 'targetUrl' ])
removeEndSlashes(url)
removeEndSlashes(program[ 'targetUrl' ])
const user = { username, password }
const user = { username, password }
run(url, user)
.catch(err => {
console.error(err)
process.exit(-1)
})
})
run(url, user)
.catch(err => {
console.error(err)
process.exit(-1)
})
})
async function run (url: string, user: UserInfo) {
if (!user.password) {

View File

@ -0,0 +1,162 @@
import * as program from 'commander'
import { PluginType } from '../../shared/models/plugins/plugin.type'
import { getAccessToken } from '../../shared/extra-utils/users/login'
import { getMyUserInformation } from '../../shared/extra-utils/users/users'
import { installPlugin, listPlugins, uninstallPlugin } from '../../shared/extra-utils/server/plugins'
import { getServerCredentials } from './cli'
import { User, UserRole } from '../../shared/models/users'
import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model'
import { isAbsolute } from 'path'
const Table = require('cli-table')
program
.name('plugins')
.usage('[command] [options]')
program
.command('list')
.description('List installed plugins')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-t, --only-themes', 'List themes only')
.option('-P, --only-plugins', 'List plugins only')
.action(() => pluginsListCLI())
program
.command('install')
.description('Install a plugin or a theme')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-P --path <path>', 'Install from a path')
.option('-n, --npm-name <npmName>', 'Install from npm')
.action((options) => installPluginCLI(options))
program
.command('uninstall')
.description('Uninstall a plugin or a theme')
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
.option('-n, --npm-name <npmName>', 'NPM plugin/theme name')
.action(options => uninstallPluginCLI(options))
if (!process.argv.slice(2).length) {
program.outputHelp()
}
program.parse(process.argv)
// ----------------------------------------------------------------------------
async function pluginsListCLI () {
const { url, username, password } = await getServerCredentials(program)
const accessToken = await getAdminTokenOrDie(url, username, password)
let type: PluginType
if (program['onlyThemes']) type = PluginType.THEME
if (program['onlyPlugins']) type = PluginType.PLUGIN
const res = await listPlugins({
url,
accessToken,
start: 0,
count: 100,
sort: 'name',
type
})
const plugins: PeerTubePlugin[] = res.body.data
const table = new Table({
head: ['name', 'version', 'homepage'],
colWidths: [ 50, 10, 50 ]
})
for (const plugin of plugins) {
const npmName = plugin.type === PluginType.PLUGIN
? 'peertube-plugin-' + plugin.name
: 'peertube-theme-' + plugin.name
table.push([
npmName,
plugin.version,
plugin.homepage
])
}
console.log(table.toString())
process.exit(0)
}
async function installPluginCLI (options: any) {
if (!options['path'] && !options['npmName']) {
console.error('You need to specify the npm name or the path of the plugin you want to install.\n')
program.outputHelp()
process.exit(-1)
}
if (options['path'] && !isAbsolute(options['path'])) {
console.error('Path should be absolute.')
process.exit(-1)
}
const { url, username, password } = await getServerCredentials(options)
const accessToken = await getAdminTokenOrDie(url, username, password)
try {
await installPlugin({
url,
accessToken,
npmName: options['npmName'],
path: options['path']
})
} catch (err) {
console.error('Cannot install plugin.', err)
process.exit(-1)
return
}
console.log('Plugin installed.')
process.exit(0)
}
async function uninstallPluginCLI (options: any) {
if (!options['npmName']) {
console.error('You need to specify the npm name of the plugin/theme you want to uninstall.\n')
program.outputHelp()
process.exit(-1)
}
const { url, username, password } = await getServerCredentials(options)
const accessToken = await getAdminTokenOrDie(url, username, password)
try {
await uninstallPlugin({
url,
accessToken,
npmName: options[ 'npmName' ]
})
} catch (err) {
console.error('Cannot uninstall plugin.', err)
process.exit(-1)
return
}
console.log('Plugin uninstalled.')
process.exit(0)
}
async function getAdminTokenOrDie (url: string, username: string, password: string) {
const accessToken = await getAccessToken(url, username, password)
const resMe = await getMyUserInformation(url, accessToken)
const me: User = resMe.body
if (me.role !== UserRole.ADMINISTRATOR) {
console.error('Cannot list plugins if you are not administrator.')
process.exit(-1)
}
return accessToken
}

View File

@ -1,9 +1,9 @@
import * as program from 'commander'
import { access, constants } from 'fs-extra'
import { isAbsolute } from 'path'
import { getClient, login } from '../../shared/extra-utils'
import { getAccessToken } from '../../shared/extra-utils'
import { uploadVideo } from '../../shared/extra-utils/'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
let command = program
.name('upload')
@ -11,7 +11,6 @@ let command = program
command = buildCommonVideoOptions(command)
command
.option('-u, --url <url>', 'Server url')
.option('-U, --username <username>', 'Username')
.option('-p, --password <token>', 'Password')
@ -20,44 +19,28 @@ command
.option('-f, --file <file>', 'Video absolute file path')
.parse(process.argv)
Promise.all([ getSettings(), getNetrc() ])
.then(([ settings, netrc ]) => {
const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
getServerCredentials(command)
.then(({ url, username, password }) => {
if (!program[ 'videoName' ] || !program[ 'file' ]) {
if (!program[ 'videoName' ]) console.error('--video-name is required.')
if (!program[ 'file' ]) console.error('--file is required.')
if (!program[ 'videoName' ] || !program[ 'file' ]) {
if (!program[ 'videoName' ]) console.error('--video-name is required.')
if (!program[ 'file' ]) console.error('--file is required.')
process.exit(-1)
}
process.exit(-1)
}
if (isAbsolute(program[ 'file' ]) === false) {
console.error('File path should be absolute.')
process.exit(-1)
}
if (isAbsolute(program[ 'file' ]) === false) {
console.error('File path should be absolute.')
process.exit(-1)
}
run(url, username, password).catch(err => {
console.error(err)
process.exit(-1)
})
})
run(url, username, password).catch(err => {
console.error(err)
process.exit(-1)
})
})
async function run (url: string, username: string, password: string) {
const resClient = await getClient(url)
const client = {
id: resClient.body.client_id,
secret: resClient.body.client_secret
}
const user = { username, password }
let accessToken: string
try {
const res = await login(url, client, user)
accessToken = res.body.access_token
} catch (err) {
throw new Error('Cannot authenticate. Please check your username/password.')
}
const accessToken = await getAccessToken(url, username, password)
await access(program[ 'file' ], constants.F_OK)

View File

@ -18,13 +18,10 @@ program
.command('get-access-token', 'get a peertube access token', { noHelp: true }).alias('token')
.command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
.command('repl', 'initiate a REPL to access internals')
.command('plugins [action]', 'manage plugins on a local instance').alias('p')
/* Not Yet Implemented */
program
.command('plugins [action]',
'manage plugins on a local instance',
{ noHelp: true } as program.CommandOptions
).alias('p')
.command('diagnostic [action]',
'like couple therapy, but for your instance',
{ noHelp: true } as program.CommandOptions

View File

@ -11,6 +11,7 @@ export * from './server/follows'
export * from './requests/requests'
export * from './requests/check-api-params'
export * from './server/servers'
export * from './server/plugins'
export * from './videos/services'
export * from './videos/video-playlists'
export * from './users/users'

View File

@ -8,7 +8,7 @@ import { pathExists, readFile } from 'fs-extra'
import * as ffmpeg from 'fluent-ffmpeg'
const expect = chai.expect
let webtorrent = new WebTorrent()
let webtorrent: WebTorrent.Instance
function immutableAssign <T, U> (target: T, source: U) {
return Object.assign<{}, T, U>({}, target, source)
@ -27,6 +27,9 @@ function wait (milliseconds: number) {
}
function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
const WebTorrent = require('webtorrent')
if (!webtorrent) webtorrent = new WebTorrent()
if (refreshWebTorrent === true) webtorrent = new WebTorrent()
return new Promise<WebTorrent.Torrent>(res => webtorrent.add(torrent, res))

View File

@ -0,0 +1,125 @@
import { makeGetRequest, makePostBodyRequest } from '../requests/requests'
import { PluginType } from '../../models/plugins/plugin.type'
function listPlugins (parameters: {
url: string,
accessToken: string,
start?: number,
count?: number,
sort?: string,
type?: PluginType,
expectedStatus?: number
}) {
const { url, accessToken, start, count, sort, type, expectedStatus = 200 } = parameters
const path = '/api/v1/plugins'
return makeGetRequest({
url,
path,
token: accessToken,
query: {
start,
count,
sort,
type
},
statusCodeExpected: expectedStatus
})
}
function getPlugin (parameters: {
url: string,
accessToken: string,
npmName: string,
expectedStatus?: number
}) {
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
const path = '/api/v1/plugins/' + npmName
return makeGetRequest({
url,
path,
token: accessToken,
statusCodeExpected: expectedStatus
})
}
function getPluginSettings (parameters: {
url: string,
accessToken: string,
npmName: string,
expectedStatus?: number
}) {
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
const path = '/api/v1/plugins/' + npmName + '/settings'
return makeGetRequest({
url,
path,
token: accessToken,
statusCodeExpected: expectedStatus
})
}
function getPluginRegisteredSettings (parameters: {
url: string,
accessToken: string,
npmName: string,
expectedStatus?: number
}) {
const { url, accessToken, npmName, expectedStatus = 200 } = parameters
const path = '/api/v1/plugins/' + npmName + '/registered-settings'
return makeGetRequest({
url,
path,
token: accessToken,
statusCodeExpected: expectedStatus
})
}
function installPlugin (parameters: {
url: string,
accessToken: string,
path?: string,
npmName?: string
expectedStatus?: number
}) {
const { url, accessToken, npmName, path, expectedStatus = 204 } = parameters
const apiPath = '/api/v1/plugins/install'
return makePostBodyRequest({
url,
path: apiPath,
token: accessToken,
fields: { npmName, path },
statusCodeExpected: expectedStatus
})
}
function uninstallPlugin (parameters: {
url: string,
accessToken: string,
npmName: string
expectedStatus?: number
}) {
const { url, accessToken, npmName, expectedStatus = 204 } = parameters
const apiPath = '/api/v1/plugins/uninstall'
return makePostBodyRequest({
url,
path: apiPath,
token: accessToken,
fields: { npmName },
statusCodeExpected: expectedStatus
})
}
export {
listPlugins,
installPlugin,
getPlugin,
uninstallPlugin,
getPluginSettings,
getPluginRegisteredSettings
}

View File

@ -3,7 +3,7 @@
import { ChildProcess, exec, fork } from 'child_process'
import { join } from 'path'
import { root, wait } from '../miscs/miscs'
import { copy, readdir, readFile, remove } from 'fs-extra'
import { copy, pathExists, readdir, readFile, remove } from 'fs-extra'
import { existsSync } from 'fs'
import { expect } from 'chai'
import { VideoChannel } from '../../models/videos'
@ -241,20 +241,22 @@ async function reRunServer (server: ServerInfo, configOverride?: any) {
return server
}
async function checkTmpIsEmpty (server: ServerInfo) {
return checkDirectoryIsEmpty(server, 'tmp')
function checkTmpIsEmpty (server: ServerInfo) {
return checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css' ])
}
async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
async function checkDirectoryIsEmpty (server: ServerInfo, directory: string, exceptions: string[] = []) {
const testDirectory = 'test' + server.internalServerNumber
const directoryPath = join(root(), testDirectory, directory)
const directoryExists = existsSync(directoryPath)
const directoryExists = await pathExists(directoryPath)
expect(directoryExists).to.be.true
const files = await readdir(directoryPath)
expect(files).to.have.lengthOf(0)
const filtered = files.filter(f => exceptions.includes(f) === false)
expect(filtered).to.have.lengthOf(0)
}
function killallServers (servers: ServerInfo[]) {

View File

@ -1,6 +1,7 @@
import * as request from 'supertest'
import { ServerInfo } from '../server/servers'
import { getClient } from '../server/clients'
type Client = { id: string, secret: string }
type User = { username: string, password: string }
@ -38,6 +39,23 @@ async function userLogin (server: Server, user: User, expectedStatus = 200) {
return res.body.access_token as string
}
async function getAccessToken (url: string, username: string, password: string) {
const resClient = await getClient(url)
const client = {
id: resClient.body.client_id,
secret: resClient.body.client_secret
}
const user = { username, password }
try {
const res = await login(url, client, user)
return res.body.access_token
} catch (err) {
throw new Error('Cannot authenticate. Please check your username/password.')
}
}
function setAccessTokensToServers (servers: ServerInfo[]) {
const tasks: Promise<any>[] = []
@ -55,6 +73,7 @@ export {
login,
serverLogin,
userLogin,
getAccessToken,
setAccessTokensToServers,
Server,
Client,

View File

@ -1,11 +1,11 @@
import * as request from 'supertest'
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
import { UserCreate, UserRole } from '../../index'
import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
import { ServerInfo, userLogin } from '..'
import { UserAdminFlag } from '../../models/users/user-flag.model'
import { UserRegister } from '../../models/users/user-register.model'
import { UserRole } from '../../models/users/user-role'
import { ServerInfo } from '../server/servers'
import { userLogin } from './login'
type CreateUserArgs = { url: string,
accessToken: string,

View File

@ -1,8 +1,10 @@
import * as request from 'supertest'
import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
import { makeGetRequest, updateAvatarRequest } from '../requests/requests'
import { getMyUserInformation, ServerInfo } from '..'
import { User } from '../..'
import { ServerInfo } from '../server/servers'
import { User } from '../../models/users/user.model'
import { getMyUserInformation } from '../users/users'
function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
const path = '/api/v1/video-channels'

View File

@ -1,3 +1,4 @@
export interface InstallPlugin {
npmName: string
npmName?: string
path?: string
}