aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/plugins.ts28
-rw-r--r--server/initializers/config.ts6
-rw-r--r--server/initializers/constants.ts5
-rw-r--r--server/lib/plugins/plugin-index.ts64
-rw-r--r--server/lib/plugins/plugin-manager.ts4
-rw-r--r--server/lib/schedulers/plugins-check-scheduler.ts60
-rw-r--r--server/middlewares/validators/plugins.ts30
-rw-r--r--server/middlewares/validators/sort.ts3
-rw-r--r--server/models/server/plugin.ts19
9 files changed, 210 insertions, 9 deletions
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index 14675fdf3..114cc49b6 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -8,12 +8,13 @@ import {
8 setDefaultPagination, 8 setDefaultPagination,
9 setDefaultSort 9 setDefaultSort
10} from '../../middlewares' 10} from '../../middlewares'
11import { pluginsSortValidator } from '../../middlewares/validators' 11import { availablePluginsSortValidator, pluginsSortValidator } from '../../middlewares/validators'
12import { PluginModel } from '../../models/server/plugin' 12import { PluginModel } from '../../models/server/plugin'
13import { UserRight } from '../../../shared/models/users' 13import { UserRight } from '../../../shared/models/users'
14import { 14import {
15 existingPluginValidator, 15 existingPluginValidator,
16 installOrUpdatePluginValidator, 16 installOrUpdatePluginValidator,
17 listAvailablePluginsValidator,
17 listPluginsValidator, 18 listPluginsValidator,
18 uninstallPluginValidator, 19 uninstallPluginValidator,
19 updatePluginSettingsValidator 20 updatePluginSettingsValidator
@@ -22,9 +23,22 @@ import { PluginManager } from '../../lib/plugins/plugin-manager'
22import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' 23import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model'
23import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model' 24import { ManagePlugin } from '../../../shared/models/plugins/manage-plugin.model'
24import { logger } from '../../helpers/logger' 25import { logger } from '../../helpers/logger'
26import { listAvailablePluginsFromIndex } from '../../lib/plugins/plugin-index'
27import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model'
25 28
26const pluginRouter = express.Router() 29const pluginRouter = express.Router()
27 30
31pluginRouter.get('/available',
32 authenticate,
33 ensureUserHasRight(UserRight.MANAGE_PLUGINS),
34 listAvailablePluginsValidator,
35 paginationValidator,
36 availablePluginsSortValidator,
37 setDefaultSort,
38 setDefaultPagination,
39 asyncMiddleware(listAvailablePlugins)
40)
41
28pluginRouter.get('/', 42pluginRouter.get('/',
29 authenticate, 43 authenticate,
30 ensureUserHasRight(UserRight.MANAGE_PLUGINS), 44 ensureUserHasRight(UserRight.MANAGE_PLUGINS),
@@ -88,10 +102,10 @@ export {
88// --------------------------------------------------------------------------- 102// ---------------------------------------------------------------------------
89 103
90async function listPlugins (req: express.Request, res: express.Response) { 104async function listPlugins (req: express.Request, res: express.Response) {
91 const type = req.query.type 105 const pluginType = req.query.pluginType
92 106
93 const resultList = await PluginModel.listForApi({ 107 const resultList = await PluginModel.listForApi({
94 type, 108 pluginType,
95 start: req.query.start, 109 start: req.query.start,
96 count: req.query.count, 110 count: req.query.count,
97 sort: req.query.sort 111 sort: req.query.sort
@@ -160,3 +174,11 @@ async function updatePluginSettings (req: express.Request, res: express.Response
160 174
161 return res.sendStatus(204) 175 return res.sendStatus(204)
162} 176}
177
178async function listAvailablePlugins (req: express.Request, res: express.Response) {
179 const query: PeertubePluginIndexList = req.query
180
181 const resultList = await listAvailablePluginsFromIndex(query)
182
183 return res.json(resultList)
184}
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index dfc4bea21..2c1b30021 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -134,6 +134,12 @@ const CONFIG = {
134 } 134 }
135 } 135 }
136 }, 136 },
137 PLUGINS: {
138 INDEX: {
139 ENABLED: config.get<boolean>('plugins.index.enabled'),
140 URL: config.get<boolean>('plugins.index.url')
141 }
142 },
137 ADMIN: { 143 ADMIN: {
138 get EMAIL () { return config.get<string>('admin.email') } 144 get EMAIL () { return config.get<string>('admin.email') }
139 }, 145 },
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 2d487a263..06e8c070b 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -64,7 +64,9 @@ const SORTABLE_COLUMNS = {
64 64
65 VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ], 65 VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ],
66 66
67 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ] 67 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ],
68
69 AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ]
68} 70}
69 71
70const OAUTH_LIFETIME = { 72const OAUTH_LIFETIME = {
@@ -165,6 +167,7 @@ const SCHEDULER_INTERVALS_MS = {
165 removeOldJobs: 60000 * 60, // 1 hour 167 removeOldJobs: 60000 * 60, // 1 hour
166 updateVideos: 60000, // 1 minute 168 updateVideos: 60000, // 1 minute
167 youtubeDLUpdate: 60000 * 60 * 24, // 1 day 169 youtubeDLUpdate: 60000 * 60 * 24, // 1 day
170 checkPlugins: 60000 * 60 * 24, // 1 day
168 removeOldViews: 60000 * 60 * 24, // 1 day 171 removeOldViews: 60000 * 60 * 24, // 1 day
169 removeOldHistory: 60000 * 60 * 24 // 1 day 172 removeOldHistory: 60000 * 60 * 24 // 1 day
170} 173}
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts
new file mode 100644
index 000000000..4a8a90ec8
--- /dev/null
+++ b/server/lib/plugins/plugin-index.ts
@@ -0,0 +1,64 @@
1import { doRequest } from '../../helpers/requests'
2import { CONFIG } from '../../initializers/config'
3import {
4 PeertubePluginLatestVersionRequest,
5 PeertubePluginLatestVersionResponse
6} from '../../../shared/models/plugins/peertube-plugin-latest-version.model'
7import { PeertubePluginIndexList } from '../../../shared/models/plugins/peertube-plugin-index-list.model'
8import { ResultList } from '../../../shared/models'
9import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model'
10import { PluginModel } from '../../models/server/plugin'
11import { PluginManager } from './plugin-manager'
12import { logger } from '../../helpers/logger'
13
14const packageJSON = require('../../../../package.json')
15
16async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) {
17 const { start = 0, count = 20, search, sort = 'npmName', pluginType } = options
18
19 const qs: PeertubePluginIndexList = {
20 start,
21 count,
22 sort,
23 pluginType,
24 search,
25 currentPeerTubeEngine: packageJSON.version
26 }
27
28 const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins'
29
30 const { body } = await doRequest({ uri, qs, json: true })
31
32 logger.debug('Got result from PeerTube index.', { body })
33
34 await addInstanceInformation(body)
35
36 return body as ResultList<PeerTubePluginIndex>
37}
38
39async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) {
40 for (const d of result.data) {
41 d.installed = PluginManager.Instance.isRegistered(d.npmName)
42 d.name = PluginModel.normalizePluginName(d.npmName)
43 }
44
45 return result
46}
47
48async function getLatestPluginsVersion (npmNames: string[]): Promise<PeertubePluginLatestVersionResponse> {
49 const bodyRequest: PeertubePluginLatestVersionRequest = {
50 npmNames,
51 currentPeerTubeEngine: packageJSON.version
52 }
53
54 const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins/latest-version'
55
56 const { body } = await doRequest({ uri, body: bodyRequest })
57
58 return body
59}
60
61export {
62 listAvailablePluginsFromIndex,
63 getLatestPluginsVersion
64}
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index 7576b284c..9e4ec5adf 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -55,6 +55,10 @@ export class PluginManager {
55 55
56 // ###################### Getters ###################### 56 // ###################### Getters ######################
57 57
58 isRegistered (npmName: string) {
59 return !!this.getRegisteredPluginOrTheme(npmName)
60 }
61
58 getRegisteredPluginOrTheme (npmName: string) { 62 getRegisteredPluginOrTheme (npmName: string) {
59 return this.registeredPlugins[npmName] 63 return this.registeredPlugins[npmName]
60 } 64 }
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts
new file mode 100644
index 000000000..9c60dbcd4
--- /dev/null
+++ b/server/lib/schedulers/plugins-check-scheduler.ts
@@ -0,0 +1,60 @@
1import { logger } from '../../helpers/logger'
2import { AbstractScheduler } from './abstract-scheduler'
3import { retryTransactionWrapper } from '../../helpers/database-utils'
4import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
5import { CONFIG } from '../../initializers/config'
6import { PluginModel } from '../../models/server/plugin'
7import { chunk } from 'lodash'
8import { getLatestPluginsVersion } from '../plugins/plugin-index'
9import { compareSemVer } from '../../../shared/core-utils/miscs/miscs'
10
11export class PluginsCheckScheduler extends AbstractScheduler {
12
13 private static instance: AbstractScheduler
14
15 protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.checkPlugins
16
17 private constructor () {
18 super()
19 }
20
21 protected async internalExecute () {
22 return retryTransactionWrapper(this.checkLatestPluginsVersion.bind(this))
23 }
24
25 private async checkLatestPluginsVersion () {
26 if (CONFIG.PLUGINS.INDEX.ENABLED === false) return
27
28 logger.info('Checkin latest plugins version.')
29
30 const plugins = await PluginModel.listInstalled()
31
32 // Process 10 plugins in 1 HTTP request
33 const chunks = chunk(plugins, 10)
34 for (const chunk of chunks) {
35 // Find plugins according to their npm name
36 const pluginIndex: { [npmName: string]: PluginModel} = {}
37 for (const plugin of chunk) {
38 pluginIndex[PluginModel.buildNpmName(plugin.name, plugin.type)] = plugin
39 }
40
41 const npmNames = Object.keys(pluginIndex)
42 const results = await getLatestPluginsVersion(npmNames)
43
44 for (const result of results) {
45 const plugin = pluginIndex[result.npmName]
46 if (!result.latestVersion) continue
47
48 if (plugin.latestVersion !== result.latestVersion && compareSemVer(plugin.latestVersion, result.latestVersion) < 0) {
49 plugin.latestVersion = result.latestVersion
50 await plugin.save()
51 }
52 }
53 }
54
55 }
56
57 static get Instance () {
58 return this.instance || (this.instance = new this())
59 }
60}
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index 8103ec7d3..8cb3153aa 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -8,6 +8,7 @@ import { isBooleanValid, isSafePath } from '../../helpers/custom-validators/misc
8import { PluginModel } from '../../models/server/plugin' 8import { PluginModel } from '../../models/server/plugin'
9import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' 9import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model'
10import { PluginType } from '../../../shared/models/plugins/plugin.type' 10import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { CONFIG } from '../../initializers/config'
11 12
12const servePluginStaticDirectoryValidator = (pluginType: PluginType) => [ 13const servePluginStaticDirectoryValidator = (pluginType: PluginType) => [
13 param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), 14 param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'),
@@ -33,7 +34,7 @@ const servePluginStaticDirectoryValidator = (pluginType: PluginType) => [
33] 34]
34 35
35const listPluginsValidator = [ 36const listPluginsValidator = [
36 query('type') 37 query('pluginType')
37 .optional() 38 .optional()
38 .custom(isPluginTypeValid).withMessage('Should have a valid plugin type'), 39 .custom(isPluginTypeValid).withMessage('Should have a valid plugin type'),
39 query('uninstalled') 40 query('uninstalled')
@@ -119,12 +120,39 @@ const updatePluginSettingsValidator = [
119 } 120 }
120] 121]
121 122
123const listAvailablePluginsValidator = [
124 query('sort')
125 .optional()
126 .exists().withMessage('Should have a valid sort'),
127 query('search')
128 .optional()
129 .exists().withMessage('Should have a valid search'),
130 query('pluginType')
131 .optional()
132 .custom(isPluginTypeValid).withMessage('Should have a valid plugin type'),
133
134 (req: express.Request, res: express.Response, next: express.NextFunction) => {
135 logger.debug('Checking enabledPluginValidator parameters', { parameters: req.query })
136
137 if (areValidationErrors(req, res)) return
138
139 if (CONFIG.PLUGINS.INDEX.ENABLED === false) {
140 return res.status(400)
141 .json({ error: 'Plugin index is not enabled' })
142 .end()
143 }
144
145 return next()
146 }
147]
148
122// --------------------------------------------------------------------------- 149// ---------------------------------------------------------------------------
123 150
124export { 151export {
125 servePluginStaticDirectoryValidator, 152 servePluginStaticDirectoryValidator,
126 updatePluginSettingsValidator, 153 updatePluginSettingsValidator,
127 uninstallPluginValidator, 154 uninstallPluginValidator,
155 listAvailablePluginsValidator,
128 existingPluginValidator, 156 existingPluginValidator,
129 installOrUpdatePluginValidator, 157 installOrUpdatePluginValidator,
130 listPluginsValidator 158 listPluginsValidator
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index 102db85cb..c75e701d6 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -22,6 +22,7 @@ const SORTABLE_SERVERS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMN
22const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_NOTIFICATIONS) 22const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_NOTIFICATIONS)
23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) 23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) 24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS)
25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
25 26
26const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) 27const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
27const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) 28const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
@@ -43,6 +44,7 @@ const serversBlocklistSortValidator = checkSort(SORTABLE_SERVERS_BLOCKLIST_COLUM
43const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COLUMNS) 44const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COLUMNS)
44const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) 45const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS)
45const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) 46const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS)
47const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS)
46 48
47// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
48 50
@@ -61,6 +63,7 @@ export {
61 videoCommentThreadsSortValidator, 63 videoCommentThreadsSortValidator,
62 videoRatesSortValidator, 64 videoRatesSortValidator,
63 userSubscriptionsSortValidator, 65 userSubscriptionsSortValidator,
66 availablePluginsSortValidator,
64 videoChannelsSearchSortValidator, 67 videoChannelsSearchSortValidator,
65 accountsBlocklistSortValidator, 68 accountsBlocklistSortValidator,
66 serversBlocklistSortValidator, 69 serversBlocklistSortValidator,
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index bd3d7a81e..ba43713f6 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -10,6 +10,7 @@ import {
10import { PluginType } from '../../../shared/models/plugins/plugin.type' 10import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' 11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
12import { FindAndCountOptions, json } from 'sequelize' 12import { FindAndCountOptions, json } from 'sequelize'
13import { PeerTubePluginIndex } from '../../../shared/models/plugins/peertube-plugin-index.model'
13 14
14@DefaultScope(() => ({ 15@DefaultScope(() => ({
15 attributes: { 16 attributes: {
@@ -177,7 +178,7 @@ export class PluginModel extends Model<PluginModel> {
177 } 178 }
178 179
179 static listForApi (options: { 180 static listForApi (options: {
180 type?: PluginType, 181 pluginType?: PluginType,
181 uninstalled?: boolean, 182 uninstalled?: boolean,
182 start: number, 183 start: number,
183 count: number, 184 count: number,
@@ -193,7 +194,7 @@ export class PluginModel extends Model<PluginModel> {
193 } 194 }
194 } 195 }
195 196
196 if (options.type) query.where['type'] = options.type 197 if (options.pluginType) query.where['type'] = options.pluginType
197 198
198 return PluginModel 199 return PluginModel
199 .findAndCountAll(query) 200 .findAndCountAll(query)
@@ -202,8 +203,18 @@ export class PluginModel extends Model<PluginModel> {
202 }) 203 })
203 } 204 }
204 205
205 static normalizePluginName (name: string) { 206 static listInstalled () {
206 return name.replace(/^peertube-((theme)|(plugin))-/, '') 207 const query = {
208 where: {
209 uninstalled: false
210 }
211 }
212
213 return PluginModel.findAll(query)
214 }
215
216 static normalizePluginName (npmName: string) {
217 return npmName.replace(/^peertube-((theme)|(plugin))-/, '')
207 } 218 }
208 219
209 static getTypeFromNpmName (npmName: string) { 220 static getTypeFromNpmName (npmName: string) {