aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/lib/plugins/plugin-helpers-builder.ts44
-rw-r--r--server/lib/plugins/plugin-manager.ts5
-rw-r--r--server/tests/fixtures/peertube-plugin-test-four/main.js23
-rw-r--r--server/tests/fixtures/peertube-plugin-test-six/main.js18
-rw-r--r--server/tests/plugins/plugin-helpers.ts40
-rw-r--r--server/tests/plugins/plugin-storage.ts80
-rw-r--r--server/types/plugins/register-server-option.model.ts17
-rw-r--r--support/doc/plugins/guide.md40
8 files changed, 244 insertions, 23 deletions
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts
index cbd849742..d57c69ef0 100644
--- a/server/lib/plugins/plugin-helpers-builder.ts
+++ b/server/lib/plugins/plugin-helpers-builder.ts
@@ -1,19 +1,22 @@
1import { PeerTubeHelpers } from '@server/types/plugins' 1import * as express from 'express'
2import { sequelizeTypescript } from '@server/initializers/database' 2import { join } from 'path'
3import { buildLogger } from '@server/helpers/logger' 3import { buildLogger } from '@server/helpers/logger'
4import { VideoModel } from '@server/models/video/video' 4import { CONFIG } from '@server/initializers/config'
5import { WEBSERVER } from '@server/initializers/constants' 5import { WEBSERVER } from '@server/initializers/constants'
6import { ServerModel } from '@server/models/server/server' 6import { sequelizeTypescript } from '@server/initializers/database'
7import { AccountModel } from '@server/models/account/account'
8import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
7import { getServerActor } from '@server/models/application/application' 9import { getServerActor } from '@server/models/application/application'
8import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' 10import { ServerModel } from '@server/models/server/server'
9import { ServerBlocklistModel } from '@server/models/server/server-blocklist' 11import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
10import { AccountModel } from '@server/models/account/account' 12import { VideoModel } from '@server/models/video/video'
11import { VideoBlacklistCreate } from '@shared/models'
12import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
13import { VideoBlacklistModel } from '@server/models/video/video-blacklist' 13import { VideoBlacklistModel } from '@server/models/video/video-blacklist'
14import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
15import { getServerConfig } from '../config'
16import { MPlugin } from '@server/types/models' 14import { MPlugin } from '@server/types/models'
15import { PeerTubeHelpers } from '@server/types/plugins'
16import { VideoBlacklistCreate } from '@shared/models'
17import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist'
18import { getServerConfig } from '../config'
19import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
17 20
18function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { 21function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers {
19 const logger = buildPluginLogger(npmName) 22 const logger = buildPluginLogger(npmName)
@@ -27,7 +30,9 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
27 30
28 const moderation = buildModerationHelpers() 31 const moderation = buildModerationHelpers()
29 32
30 const plugin = buildPluginRelatedHelpers(pluginModel) 33 const plugin = buildPluginRelatedHelpers(pluginModel, npmName)
34
35 const user = buildUserHelpers()
31 36
32 return { 37 return {
33 logger, 38 logger,
@@ -36,7 +41,8 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
36 config, 41 config,
37 moderation, 42 moderation,
38 plugin, 43 plugin,
39 server 44 server,
45 user
40 } 46 }
41} 47}
42 48
@@ -145,8 +151,18 @@ function buildConfigHelpers () {
145 } 151 }
146} 152}
147 153
148function buildPluginRelatedHelpers (plugin: MPlugin) { 154function buildPluginRelatedHelpers (plugin: MPlugin, npmName: string) {
155 return {
156 getBaseStaticRoute: () => `/plugins/${plugin.name}/${plugin.version}/static/`,
157
158 getBaseRouterRoute: () => `/plugins/${plugin.name}/${plugin.version}/router/`,
159
160 getDataDirectoryPath: () => join(CONFIG.STORAGE.PLUGINS_DIR, 'data', npmName)
161 }
162}
163
164function buildUserHelpers () {
149 return { 165 return {
150 getBaseStaticRoute: () => `/plugins/${plugin.name}/${plugin.version}/static/` 166 getAuthUser: (res: express.Response) => res.locals.oauth?.token?.User
151 } 167 }
152} 168}
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index ae05af721..ba9814383 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -1,7 +1,7 @@
1import decache from 'decache' 1import decache from 'decache'
2import * as express from 'express' 2import * as express from 'express'
3import { createReadStream, createWriteStream } from 'fs' 3import { createReadStream, createWriteStream } from 'fs'
4import { outputFile, readJSON } from 'fs-extra' 4import { ensureDir, outputFile, readJSON } from 'fs-extra'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { MOAuthTokenUser, MUser } from '@server/types/models' 6import { MOAuthTokenUser, MUser } from '@server/types/models'
7import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' 7import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
@@ -428,6 +428,9 @@ export class PluginManager implements ServerHook {
428 } 428 }
429 429
430 const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin) 430 const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin)
431
432 await ensureDir(registerOptions.peertubeHelpers.plugin.getDataDirectoryPath())
433
431 library.register(registerOptions) 434 library.register(registerOptions)
432 .catch(err => logger.error('Cannot register plugin %s.', npmName, { err })) 435 .catch(err => logger.error('Cannot register plugin %s.', npmName, { err }))
433 436
diff --git a/server/tests/fixtures/peertube-plugin-test-four/main.js b/server/tests/fixtures/peertube-plugin-test-four/main.js
index ea0599997..6930ac511 100644
--- a/server/tests/fixtures/peertube-plugin-test-four/main.js
+++ b/server/tests/fixtures/peertube-plugin-test-four/main.js
@@ -77,10 +77,31 @@ async function register ({
77 }) 77 })
78 78
79 router.get('/static-route', async (req, res) => { 79 router.get('/static-route', async (req, res) => {
80 const staticRoute = await peertubeHelpers.plugin.getBaseStaticRoute() 80 const staticRoute = peertubeHelpers.plugin.getBaseStaticRoute()
81 81
82 return res.json({ staticRoute }) 82 return res.json({ staticRoute })
83 }) 83 })
84
85 router.get('/router-route', async (req, res) => {
86 const routerRoute = peertubeHelpers.plugin.getBaseRouterRoute()
87
88 return res.json({ routerRoute })
89 })
90
91 router.get('/user', async (req, res) => {
92 const user = peertubeHelpers.user.getAuthUser(res)
93
94 const isAdmin = user.role === 0
95 const isModerator = user.role === 1
96 const isUser = user.role === 2
97
98 return res.json({
99 username: user.username,
100 isAdmin,
101 isModerator,
102 isUser
103 })
104 })
84 } 105 }
85 106
86} 107}
diff --git a/server/tests/fixtures/peertube-plugin-test-six/main.js b/server/tests/fixtures/peertube-plugin-test-six/main.js
index bb9aaffa7..858bdb2df 100644
--- a/server/tests/fixtures/peertube-plugin-test-six/main.js
+++ b/server/tests/fixtures/peertube-plugin-test-six/main.js
@@ -1,6 +1,10 @@
1const fs = require('fs')
2const path = require('path')
3
1async function register ({ 4async function register ({
2 storageManager, 5 storageManager,
3 peertubeHelpers 6 peertubeHelpers,
7 getRouter
4}) { 8}) {
5 const { logger } = peertubeHelpers 9 const { logger } = peertubeHelpers
6 10
@@ -11,6 +15,18 @@ async function register ({
11 const result = await storageManager.getData('superkey') 15 const result = await storageManager.getData('superkey')
12 logger.info('superkey stored value is %s', result.value) 16 logger.info('superkey stored value is %s', result.value)
13 } 17 }
18
19 {
20 getRouter().get('/create-file', async (req, res) => {
21 const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
22
23 fs.writeFile(path.join(basePath, 'Aladdin.txt'), 'Prince Ali', function (err) {
24 if (err) return res.sendStatus(500)
25
26 res.sendStatus(200)
27 })
28 })
29 }
14} 30}
15 31
16async function unregister () { 32async function unregister () {
diff --git a/server/tests/plugins/plugin-helpers.ts b/server/tests/plugins/plugin-helpers.ts
index 325d20e84..2ac070b41 100644
--- a/server/tests/plugins/plugin-helpers.ts
+++ b/server/tests/plugins/plugin-helpers.ts
@@ -100,6 +100,46 @@ describe('Test plugin helpers', function () {
100 100
101 expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/') 101 expect(res.body.staticRoute).to.equal('/plugins/test-four/0.0.1/static/')
102 }) 102 })
103
104 it('Should get the base static route', async function () {
105 const baseRouter = '/plugins/test-four/0.0.1/router/'
106
107 const res = await makeGetRequest({
108 url: servers[0].url,
109 path: baseRouter + 'router-route',
110 statusCodeExpected: HttpStatusCode.OK_200
111 })
112
113 expect(res.body.routerRoute).to.equal(baseRouter)
114 })
115 })
116
117 describe('User', function () {
118
119 it('Should not get a user if not authenticated', async function () {
120 const res = await makeGetRequest({
121 url: servers[0].url,
122 path: '/plugins/test-four/router/user',
123 statusCodeExpected: HttpStatusCode.OK_200
124 })
125
126 expect(res.body.user).to.be.undefined
127 })
128
129 it('Should get a user if authenticated', async function () {
130 const res = await makeGetRequest({
131 url: servers[0].url,
132 token: servers[0].accessToken,
133 path: '/plugins/test-four/router/user',
134 statusCodeExpected: HttpStatusCode.OK_200
135 })
136
137 expect(res.body.user).to.exist
138 expect(res.body.username).to.equal('root')
139 expect(res.body.isAdmin).to.be.true
140 expect(res.body.isModerator).to.be.false
141 expect(res.body.isUser).to.be.false
142 })
103 }) 143 })
104 144
105 describe('Moderation', function () { 145 describe('Moderation', function () {
diff --git a/server/tests/plugins/plugin-storage.ts b/server/tests/plugins/plugin-storage.ts
index 356692eb9..3c46b2585 100644
--- a/server/tests/plugins/plugin-storage.ts
+++ b/server/tests/plugins/plugin-storage.ts
@@ -1,7 +1,18 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { getPluginTestPath, installPlugin, setAccessTokensToServers } from '../../../shared/extra-utils' 4import { expect } from 'chai'
5import { pathExists, readdir, readFile } from 'fs-extra'
6import { join } from 'path'
7import { HttpStatusCode } from '@shared/core-utils'
8import {
9 buildServerDirectory,
10 getPluginTestPath,
11 installPlugin,
12 makeGetRequest,
13 setAccessTokensToServers,
14 uninstallPlugin
15} from '../../../shared/extra-utils'
5import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' 16import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers'
6 17
7describe('Test plugin storage', function () { 18describe('Test plugin storage', function () {
@@ -20,8 +31,71 @@ describe('Test plugin storage', function () {
20 }) 31 })
21 }) 32 })
22 33
23 it('Should correctly store a subkey', async function () { 34 describe('DB storage', function () {
24 await waitUntilLog(server, 'superkey stored value is toto') 35
36 it('Should correctly store a subkey', async function () {
37 await waitUntilLog(server, 'superkey stored value is toto')
38 })
39 })
40
41 describe('Disk storage', function () {
42 let dataPath: string
43 let pluginDataPath: string
44
45 async function getFileContent () {
46 const files = await readdir(pluginDataPath)
47 expect(files).to.have.lengthOf(1)
48
49 return readFile(join(pluginDataPath, files[0]), 'utf8')
50 }
51
52 before(function () {
53 dataPath = buildServerDirectory(server, 'plugins/data')
54 pluginDataPath = join(dataPath, 'peertube-plugin-test-six')
55 })
56
57 it('Should have created the directory on install', async function () {
58 const dataPath = buildServerDirectory(server, 'plugins/data')
59 const pluginDataPath = join(dataPath, 'peertube-plugin-test-six')
60
61 expect(await pathExists(dataPath)).to.be.true
62 expect(await pathExists(pluginDataPath)).to.be.true
63 expect(await readdir(pluginDataPath)).to.have.lengthOf(0)
64 })
65
66 it('Should have created a file', async function () {
67 await makeGetRequest({
68 url: server.url,
69 token: server.accessToken,
70 path: '/plugins/test-six/router/create-file',
71 statusCodeExpected: HttpStatusCode.OK_200
72 })
73
74 const content = await getFileContent()
75 expect(content).to.equal('Prince Ali')
76 })
77
78 it('Should still have the file after an uninstallation', async function () {
79 await uninstallPlugin({
80 url: server.url,
81 accessToken: server.accessToken,
82 npmName: 'peertube-plugin-test-six'
83 })
84
85 const content = await getFileContent()
86 expect(content).to.equal('Prince Ali')
87 })
88
89 it('Should still have the file after the reinstallation', async function () {
90 await installPlugin({
91 url: server.url,
92 accessToken: server.accessToken,
93 path: getPluginTestPath('-six')
94 })
95
96 const content = await getFileContent()
97 expect(content).to.equal('Prince Ali')
98 })
25 }) 99 })
26 100
27 after(async function () { 101 after(async function () {
diff --git a/server/types/plugins/register-server-option.model.ts b/server/types/plugins/register-server-option.model.ts
index 391dcc3f9..1b9250ce4 100644
--- a/server/types/plugins/register-server-option.model.ts
+++ b/server/types/plugins/register-server-option.model.ts
@@ -1,4 +1,4 @@
1import { Router } from 'express' 1import { Router, Response } from 'express'
2import { Logger } from 'winston' 2import { Logger } from 'winston'
3import { ActorModel } from '@server/models/activitypub/actor' 3import { ActorModel } from '@server/models/activitypub/actor'
4import { 4import {
@@ -13,6 +13,7 @@ import {
13 RegisterServerHookOptions, 13 RegisterServerHookOptions,
14 RegisterServerSettingOptions, 14 RegisterServerSettingOptions,
15 ServerConfig, 15 ServerConfig,
16 UserRole,
16 VideoBlacklistCreate 17 VideoBlacklistCreate
17} from '@shared/models' 18} from '@shared/models'
18import { MVideoThumbnail } from '../models' 19import { MVideoThumbnail } from '../models'
@@ -58,6 +59,20 @@ export type PeerTubeHelpers = {
58 59
59 plugin: { 60 plugin: {
60 getBaseStaticRoute: () => string 61 getBaseStaticRoute: () => string
62
63 getBaseRouterRoute: () => string
64
65 getDataDirectoryPath: () => string
66 }
67
68 user: {
69 getAuthUser: (response: Response) => {
70 id?: string
71 username: string
72 email: string
73 blocked: boolean
74 role: UserRole
75 } | undefined
61 } 76 }
62} 77}
63 78
diff --git a/support/doc/plugins/guide.md b/support/doc/plugins/guide.md
index 5b5a3065d..36ade117b 100644
--- a/support/doc/plugins/guide.md
+++ b/support/doc/plugins/guide.md
@@ -195,12 +195,30 @@ Plugins can store/load JSON data, that PeerTube will store in its database (so d
195Example: 195Example:
196 196
197```js 197```js
198function register (...) { 198function register ({
199 storageManager
200}) {
199 const value = await storageManager.getData('mykey') 201 const value = await storageManager.getData('mykey')
200 await storageManager.storeData('mykey', { subkey: 'value' }) 202 await storageManager.storeData('mykey', { subkey: 'value' })
201} 203}
202``` 204```
203 205
206You can also store files in the plugin data directory (`/{plugins-directory}/data/{npm-plugin-name}`).
207This directory and its content won't be deleted when your plugin is uninstalled/upgraded.
208
209```js
210function register ({
211 storageManager,
212 peertubeHelpers
213}) {
214 const basePath = peertubeHelpers.plugin.getDataDirectoryPath()
215
216 fs.writeFile(path.join(basePath, 'filename.txt'), 'content of my file', function (err) {
217 ...
218 })
219}
220```
221
204#### Update video constants 222#### Update video constants
205 223
206You can add/delete video categories, licences or languages using the appropriate managers: 224You can add/delete video categories, licences or languages using the appropriate managers:
@@ -226,9 +244,27 @@ function register (...) {
226You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin: 244You can create custom routes using an [express Router](https://expressjs.com/en/4x/api.html#router) for your plugin:
227 245
228```js 246```js
229function register (...) { 247function register ({
248 router
249}) {
230 const router = getRouter() 250 const router = getRouter()
231 router.get('/ping', (req, res) => res.json({ message: 'pong' })) 251 router.get('/ping', (req, res) => res.json({ message: 'pong' }))
252
253 // Users are automatically authenticated
254 router.get('/auth', (res, res) => {
255 const user = peertubeHelpers.user.getAuthUser(res)
256
257 const isAdmin = user.role === 0
258 const isModerator = user.role === 1
259 const isUser = user.role === 2
260
261 res.json({
262 username: user.username,
263 isAdmin,
264 isModerator,
265 isUser
266 })
267 })
232} 268}
233``` 269```
234 270