Add data directory for plugins and some helpers
This commit is contained in:
parent
3e0e8d4afd
commit
302eba0d89
8 changed files with 245 additions and 24 deletions
|
@ -1,19 +1,22 @@
|
||||||
import { PeerTubeHelpers } from '@server/types/plugins'
|
import * as express from 'express'
|
||||||
import { sequelizeTypescript } from '@server/initializers/database'
|
import { join } from 'path'
|
||||||
import { buildLogger } from '@server/helpers/logger'
|
import { buildLogger } from '@server/helpers/logger'
|
||||||
import { VideoModel } from '@server/models/video/video'
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { WEBSERVER } from '@server/initializers/constants'
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
import { ServerModel } from '@server/models/server/server'
|
import { sequelizeTypescript } from '@server/initializers/database'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
|
||||||
import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist'
|
|
||||||
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
|
||||||
import { AccountModel } from '@server/models/account/account'
|
import { AccountModel } from '@server/models/account/account'
|
||||||
import { VideoBlacklistCreate } from '@shared/models'
|
|
||||||
import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
|
|
||||||
import { VideoBlacklistModel } from '@server/models/video/video-blacklist'
|
|
||||||
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
|
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
|
||||||
import { getServerConfig } from '../config'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { ServerModel } from '@server/models/server/server'
|
||||||
|
import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
|
||||||
|
import { VideoModel } from '@server/models/video/video'
|
||||||
|
import { VideoBlacklistModel } from '@server/models/video/video-blacklist'
|
||||||
import { MPlugin } from '@server/types/models'
|
import { MPlugin } from '@server/types/models'
|
||||||
|
import { PeerTubeHelpers } from '@server/types/plugins'
|
||||||
|
import { VideoBlacklistCreate } from '@shared/models'
|
||||||
|
import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist'
|
||||||
|
import { getServerConfig } from '../config'
|
||||||
|
import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
|
||||||
|
|
||||||
function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers {
|
function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers {
|
||||||
const logger = buildPluginLogger(npmName)
|
const logger = buildPluginLogger(npmName)
|
||||||
|
@ -27,7 +30,9 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
|
||||||
|
|
||||||
const moderation = buildModerationHelpers()
|
const moderation = buildModerationHelpers()
|
||||||
|
|
||||||
const plugin = buildPluginRelatedHelpers(pluginModel)
|
const plugin = buildPluginRelatedHelpers(pluginModel, npmName)
|
||||||
|
|
||||||
|
const user = buildUserHelpers()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
logger,
|
logger,
|
||||||
|
@ -36,7 +41,8 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
|
||||||
config,
|
config,
|
||||||
moderation,
|
moderation,
|
||||||
plugin,
|
plugin,
|
||||||
server
|
server,
|
||||||
|
user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,8 +151,18 @@ function buildConfigHelpers () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPluginRelatedHelpers (plugin: MPlugin) {
|
function buildPluginRelatedHelpers (plugin: MPlugin, npmName: string) {
|
||||||
return {
|
return {
|
||||||
getBaseStaticRoute: () => `/plugins/${plugin.name}/${plugin.version}/static/`
|
getBaseStaticRoute: () => `/plugins/${plugin.name}/${plugin.version}/static/`,
|
||||||
|
|
||||||
|
getBaseRouterRoute: () => `/plugins/${plugin.name}/${plugin.version}/router/`,
|
||||||
|
|
||||||
|
getDataDirectoryPath: () => join(CONFIG.STORAGE.PLUGINS_DIR, 'data', npmName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUserHelpers () {
|
||||||
|
return {
|
||||||
|
getAuthUser: (res: express.Response) => res.locals.oauth?.token?.User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import decache from 'decache'
|
import decache from 'decache'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { createReadStream, createWriteStream } from 'fs'
|
import { createReadStream, createWriteStream } from 'fs'
|
||||||
import { outputFile, readJSON } from 'fs-extra'
|
import { ensureDir, outputFile, readJSON } from 'fs-extra'
|
||||||
import { basename, join } from 'path'
|
import { basename, join } from 'path'
|
||||||
import { MOAuthTokenUser, MUser } from '@server/types/models'
|
import { MOAuthTokenUser, MUser } from '@server/types/models'
|
||||||
import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
|
import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
|
||||||
|
@ -428,6 +428,9 @@ export class PluginManager implements ServerHook {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin)
|
const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin)
|
||||||
|
|
||||||
|
await ensureDir(registerOptions.peertubeHelpers.plugin.getDataDirectoryPath())
|
||||||
|
|
||||||
library.register(registerOptions)
|
library.register(registerOptions)
|
||||||
.catch(err => logger.error('Cannot register plugin %s.', npmName, { err }))
|
.catch(err => logger.error('Cannot register plugin %s.', npmName, { err }))
|
||||||
|
|
||||||
|
|
|
@ -77,10 +77,31 @@ async function register ({
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/static-route', async (req, res) => {
|
router.get('/static-route', async (req, res) => {
|
||||||
const staticRoute = await peertubeHelpers.plugin.getBaseStaticRoute()
|
const staticRoute = peertubeHelpers.plugin.getBaseStaticRoute()
|
||||||
|
|
||||||
return res.json({ staticRoute })
|
return res.json({ staticRoute })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.get('/router-route', async (req, res) => {
|
||||||
|
const routerRoute = peertubeHelpers.plugin.getBaseRouterRoute()
|
||||||
|
|
||||||
|
return res.json({ routerRoute })
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/user', async (req, res) => {
|
||||||
|
const user = peertubeHelpers.user.getAuthUser(res)
|
||||||
|
|
||||||
|
const isAdmin = user.role === 0
|
||||||
|
const isModerator = user.role === 1
|
||||||
|
const isUser = user.role === 2
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
username: user.username,
|
||||||
|
isAdmin,
|
||||||
|
isModerator,
|
||||||
|
isUser
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
async function register ({
|
async function register ({
|
||||||
storageManager,
|
storageManager,
|
||||||
peertubeHelpers
|
peertubeHelpers,
|
||||||
|
getRouter
|
||||||
}) {
|
}) {
|
||||||
const { logger } = peertubeHelpers
|
const { logger } = peertubeHelpers
|
||||||
|
|
||||||
|
@ -11,6 +15,18 @@ async function register ({
|
||||||
const result = await storageManager.getData('superkey')
|
const result = await storageManager.getData('superkey')
|
||||||
logger.info('superkey stored value is %s', result.value)
|
logger.info('superkey stored value is %s', result.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
getRouter().get('/create-file', async (req, res) => {
|
||||||
|
const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
|
||||||
|
|
||||||
|
fs.writeFile(path.join(basePath, 'Aladdin.txt'), 'Prince Ali', function (err) {
|
||||||
|
if (err) return res.sendStatus(500)
|
||||||
|
|
||||||
|
res.sendStatus(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unregister () {
|
async function unregister () {
|
||||||
|
|
|
@ -100,6 +100,46 @@ describe('Test plugin helpers', function () {
|
||||||
|
|
||||||
expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/')
|
expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should get the base static route', async function () {
|
||||||
|
const baseRouter = '/plugins/test-four/0.0.1/router/'
|
||||||
|
|
||||||
|
const res = await makeGetRequest({
|
||||||
|
url: servers[0].url,
|
||||||
|
path: baseRouter + 'router-route',
|
||||||
|
statusCodeExpected: HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.body.routerRoute).to.equal(baseRouter)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('User', function () {
|
||||||
|
|
||||||
|
it('Should not get a user if not authenticated', async function () {
|
||||||
|
const res = await makeGetRequest({
|
||||||
|
url: servers[0].url,
|
||||||
|
path: '/plugins/test-four/router/user',
|
||||||
|
statusCodeExpected: HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.body.user).to.be.undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should get a user if authenticated', async function () {
|
||||||
|
const res = await makeGetRequest({
|
||||||
|
url: servers[0].url,
|
||||||
|
token: servers[0].accessToken,
|
||||||
|
path: '/plugins/test-four/router/user',
|
||||||
|
statusCodeExpected: HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(res.body.user).to.exist
|
||||||
|
expect(res.body.username).to.equal('root')
|
||||||
|
expect(res.body.isAdmin).to.be.true
|
||||||
|
expect(res.body.isModerator).to.be.false
|
||||||
|
expect(res.body.isUser).to.be.false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Moderation', function () {
|
describe('Moderation', function () {
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { getPluginTestPath, installPlugin, setAccessTokensToServers } from '../../../shared/extra-utils'
|
import { expect } from 'chai'
|
||||||
|
import { pathExists, readdir, readFile } from 'fs-extra'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { HttpStatusCode } from '@shared/core-utils'
|
||||||
|
import {
|
||||||
|
buildServerDirectory,
|
||||||
|
getPluginTestPath,
|
||||||
|
installPlugin,
|
||||||
|
makeGetRequest,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
uninstallPlugin
|
||||||
|
} from '../../../shared/extra-utils'
|
||||||
import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
|
import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
|
||||||
|
|
||||||
describe('Test plugin storage', function () {
|
describe('Test plugin storage', function () {
|
||||||
|
@ -20,8 +31,71 @@ describe('Test plugin storage', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should correctly store a subkey', async function () {
|
describe('DB storage', function () {
|
||||||
await waitUntilLog(server, 'superkey stored value is toto')
|
|
||||||
|
it('Should correctly store a subkey', async function () {
|
||||||
|
await waitUntilLog(server, 'superkey stored value is toto')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Disk storage', function () {
|
||||||
|
let dataPath: string
|
||||||
|
let pluginDataPath: string
|
||||||
|
|
||||||
|
async function getFileContent () {
|
||||||
|
const files = await readdir(pluginDataPath)
|
||||||
|
expect(files).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
return readFile(join(pluginDataPath, files[0]), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
dataPath = buildServerDirectory(server, 'plugins/data')
|
||||||
|
pluginDataPath = join(dataPath, 'peertube-plugin-test-six')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have created the directory on install', async function () {
|
||||||
|
const dataPath = buildServerDirectory(server, 'plugins/data')
|
||||||
|
const pluginDataPath = join(dataPath, 'peertube-plugin-test-six')
|
||||||
|
|
||||||
|
expect(await pathExists(dataPath)).to.be.true
|
||||||
|
expect(await pathExists(pluginDataPath)).to.be.true
|
||||||
|
expect(await readdir(pluginDataPath)).to.have.lengthOf(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have created a file', async function () {
|
||||||
|
await makeGetRequest({
|
||||||
|
url: server.url,
|
||||||
|
token: server.accessToken,
|
||||||
|
path: '/plugins/test-six/router/create-file',
|
||||||
|
statusCodeExpected: HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await getFileContent()
|
||||||
|
expect(content).to.equal('Prince Ali')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should still have the file after an uninstallation', async function () {
|
||||||
|
await uninstallPlugin({
|
||||||
|
url: server.url,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
npmName: 'peertube-plugin-test-six'
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await getFileContent()
|
||||||
|
expect(content).to.equal('Prince Ali')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should still have the file after the reinstallation', async function () {
|
||||||
|
await installPlugin({
|
||||||
|
url: server.url,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
path: getPluginTestPath('-six')
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = await getFileContent()
|
||||||
|
expect(content).to.equal('Prince Ali')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Router } from 'express'
|
import { Router, Response } from 'express'
|
||||||
import { Logger } from 'winston'
|
import { Logger } from 'winston'
|
||||||
import { ActorModel } from '@server/models/activitypub/actor'
|
import { ActorModel } from '@server/models/activitypub/actor'
|
||||||
import {
|
import {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
||||||
RegisterServerHookOptions,
|
RegisterServerHookOptions,
|
||||||
RegisterServerSettingOptions,
|
RegisterServerSettingOptions,
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
|
UserRole,
|
||||||
VideoBlacklistCreate
|
VideoBlacklistCreate
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { MVideoThumbnail } from '../models'
|
import { MVideoThumbnail } from '../models'
|
||||||
|
@ -58,6 +59,20 @@ export type PeerTubeHelpers = {
|
||||||
|
|
||||||
plugin: {
|
plugin: {
|
||||||
getBaseStaticRoute: () => string
|
getBaseStaticRoute: () => string
|
||||||
|
|
||||||
|
getBaseRouterRoute: () => string
|
||||||
|
|
||||||
|
getDataDirectoryPath: () => string
|
||||||
|
}
|
||||||
|
|
||||||
|
user: {
|
||||||
|
getAuthUser: (response: Response) => {
|
||||||
|
id?: string
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
blocked: boolean
|
||||||
|
role: UserRole
|
||||||
|
} | undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -195,12 +195,30 @@ Plugins can store/load JSON data, that PeerTube will store in its database (so d
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function register (...) {
|
function register ({
|
||||||
|
storageManager
|
||||||
|
}) {
|
||||||
const value = await storageManager.getData('mykey')
|
const value = await storageManager.getData('mykey')
|
||||||
await storageManager.storeData('mykey', { subkey: 'value' })
|
await storageManager.storeData('mykey', { subkey: 'value' })
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can also store files in the plugin data directory (`/{plugins-directory}/data/{npm-plugin-name}`).
|
||||||
|
This directory and its content won't be deleted when your plugin is uninstalled/upgraded.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function register ({
|
||||||
|
storageManager,
|
||||||
|
peertubeHelpers
|
||||||
|
}) {
|
||||||
|
const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
|
||||||
|
|
||||||
|
fs.writeFile(path.join(basePath, 'filename.txt'), 'content of my file', function (err) {
|
||||||
|
...
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Update video constants
|
#### Update video constants
|
||||||
|
|
||||||
You can add/delete video categories, licences or languages using the appropriate managers:
|
You can add/delete video categories, licences or languages using the appropriate managers:
|
||||||
|
@ -226,9 +244,27 @@ function register (...) {
|
||||||
You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
|
You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function register (...) {
|
function register ({
|
||||||
|
router
|
||||||
|
}) {
|
||||||
const router = getRouter()
|
const router = getRouter()
|
||||||
router.get('/ping', (req, res) => res.json({ message: 'pong' }))
|
router.get('/ping', (req, res) => res.json({ message: 'pong' }))
|
||||||
|
|
||||||
|
// Users are automatically authenticated
|
||||||
|
router.get('/auth', (res, res) => {
|
||||||
|
const user = peertubeHelpers.user.getAuthUser(res)
|
||||||
|
|
||||||
|
const isAdmin = user.role === 0
|
||||||
|
const isModerator = user.role === 1
|
||||||
|
const isUser = user.role === 2
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
username: user.username,
|
||||||
|
isAdmin,
|
||||||
|
isModerator,
|
||||||
|
isUser
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue