]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add data directory for plugins and some helpers
authorChocobozzz <me@florianbigard.com>
Thu, 22 Apr 2021 08:55:28 +0000 (10:55 +0200)
committerChocobozzz <me@florianbigard.com>
Thu, 22 Apr 2021 08:55:28 +0000 (10:55 +0200)
server/lib/plugins/plugin-helpers-builder.ts
server/lib/plugins/plugin-manager.ts
server/tests/fixtures/peertube-plugin-test-four/main.js
server/tests/fixtures/peertube-plugin-test-six/main.js
server/tests/plugins/plugin-helpers.ts
server/tests/plugins/plugin-storage.ts
server/types/plugins/register-server-option.model.ts
support/doc/plugins/guide.md

index cbd849742e95ef1181beaad2f6dd7122c0f5dc84..d57c69ef0251f0c30353bb4fe7f726f681f494e5 100644 (file)
@@ -1,19 +1,22 @@
-import { PeerTubeHelpers } from '@server/types/plugins'
-import { sequelizeTypescript } from '@server/initializers/database'
+import * as express from 'express'
+import { join } from 'path'
 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 { ServerModel } from '@server/models/server/server'
+import { sequelizeTypescript } from '@server/initializers/database'
+import { AccountModel } from '@server/models/account/account'
+import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
 import { getServerActor } from '@server/models/application/application'
-import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist'
+import { ServerModel } from '@server/models/server/server'
 import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
-import { AccountModel } from '@server/models/account/account'
-import { VideoBlacklistCreate } from '@shared/models'
-import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
+import { VideoModel } from '@server/models/video/video'
 import { VideoBlacklistModel } from '@server/models/video/video-blacklist'
-import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
-import { getServerConfig } from '../config'
 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 {
   const logger = buildPluginLogger(npmName)
@@ -27,7 +30,9 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
 
   const moderation = buildModerationHelpers()
 
-  const plugin = buildPluginRelatedHelpers(pluginModel)
+  const plugin = buildPluginRelatedHelpers(pluginModel, npmName)
+
+  const user = buildUserHelpers()
 
   return {
     logger,
@@ -36,7 +41,8 @@ function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHel
     config,
     moderation,
     plugin,
-    server
+    server,
+    user
   }
 }
 
@@ -145,8 +151,18 @@ function buildConfigHelpers () {
   }
 }
 
-function buildPluginRelatedHelpers (plugin: MPlugin) {
+function buildPluginRelatedHelpers (plugin: MPlugin, npmName: string) {
+  return {
+    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 {
-    getBaseStaticRoute: () => `/plugins/${plugin.name}/${plugin.version}/static/`
+    getAuthUser: (res: express.Response) => res.locals.oauth?.token?.User
   }
 }
index ae05af721b2dc78df52564e081decfc88207deab..ba9814383f19eeb028b136a9f8ae77ce635e566c 100644 (file)
@@ -1,7 +1,7 @@
 import decache from 'decache'
 import * as express from 'express'
 import { createReadStream, createWriteStream } from 'fs'
-import { outputFile, readJSON } from 'fs-extra'
+import { ensureDir, outputFile, readJSON } from 'fs-extra'
 import { basename, join } from 'path'
 import { MOAuthTokenUser, MUser } from '@server/types/models'
 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)
+
+    await ensureDir(registerOptions.peertubeHelpers.plugin.getDataDirectoryPath())
+
     library.register(registerOptions)
            .catch(err => logger.error('Cannot register plugin %s.', npmName, { err }))
 
index ea0599997bf10ea33375e3e34403b31235f2dd2d..6930ac511343e285c6899505ae6987f7ff6970a0 100644 (file)
@@ -77,10 +77,31 @@ async function register ({
     })
 
     router.get('/static-route', async (req, res) => {
-      const staticRoute = await peertubeHelpers.plugin.getBaseStaticRoute()
+      const staticRoute = peertubeHelpers.plugin.getBaseStaticRoute()
 
       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
+      })
+    })
   }
 
 }
index bb9aaffa7d9226866e67124ea76c429568e263e1..858bdb2df1ba64189950506e3e8eb3ea3bff4d6b 100644 (file)
@@ -1,6 +1,10 @@
+const fs = require('fs')
+const path = require('path')
+
 async function register ({
   storageManager,
-  peertubeHelpers
+  peertubeHelpers,
+  getRouter
 }) {
   const { logger } = peertubeHelpers
 
@@ -11,6 +15,18 @@ async function register ({
     const result = await storageManager.getData('superkey')
     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 () {
index 325d20e841301c26af536ad66bc5fa68baa91a09..2ac070b41026156dbf344bfb01209bc9bb0914f4 100644 (file)
@@ -100,6 +100,46 @@ describe('Test plugin helpers', function () {
 
       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 () {
index 356692eb9ff42b457a70fbcb18a719f9211bc371..3c46b2585cc9050d53cf396f9100b59f7824d552 100644 (file)
@@ -1,7 +1,18 @@
 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
 
 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'
 
 describe('Test plugin storage', function () {
@@ -20,8 +31,71 @@ describe('Test plugin storage', function () {
     })
   })
 
-  it('Should correctly store a subkey', async function () {
-    await waitUntilLog(server, 'superkey stored value is toto')
+  describe('DB storage', function () {
+
+    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 () {
index 391dcc3f9219d468dfafca2be0377511045dc1cf..1b9250ce4682df58d845e36a4fba60028e4c0abc 100644 (file)
@@ -1,4 +1,4 @@
-import { Router } from 'express'
+import { Router, Response } from 'express'
 import { Logger } from 'winston'
 import { ActorModel } from '@server/models/activitypub/actor'
 import {
@@ -13,6 +13,7 @@ import {
   RegisterServerHookOptions,
   RegisterServerSettingOptions,
   ServerConfig,
+  UserRole,
   VideoBlacklistCreate
 } from '@shared/models'
 import { MVideoThumbnail } from '../models'
@@ -58,6 +59,20 @@ export type PeerTubeHelpers = {
 
   plugin: {
     getBaseStaticRoute: () => string
+
+    getBaseRouterRoute: () => string
+
+    getDataDirectoryPath: () => string
+  }
+
+  user: {
+    getAuthUser: (response: Response) => {
+      id?: string
+      username: string
+      email: string
+      blocked: boolean
+      role: UserRole
+    } | undefined
   }
 }
 
index 5b5a3065dce4e1b0928400322916a3ead9c6a5a9..36ade117bb5a9b0c444b6897735dfae57ecd0f7e 100644 (file)
@@ -195,12 +195,30 @@ Plugins can store/load JSON data, that PeerTube will store in its database (so d
 Example:
 
 ```js
-function register (...) {
+function register ({
+  storageManager
+}) {
   const value = await storageManager.getData('mykey')
   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
 
 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:
 
 ```js
-function register (...) {
+function register ({
+  router
+}) {
   const router = getRouter()
   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
+    })
+  })
 }
 ```