diff options
author | Chocobozzz <me@florianbigard.com> | 2019-07-09 11:45:19 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-07-24 10:58:16 +0200 |
commit | 7cd4d2ba10106c10602c86f74f55743ded588896 (patch) | |
tree | 81f0dd7a7ef763511158d1035f3e09e09d5dcd2c /server | |
parent | 8d76959e11ab7172040853fa4fadaf8d53e6aa12 (diff) | |
download | PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.tar.gz PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.tar.zst PeerTube-7cd4d2ba10106c10602c86f74f55743ded588896.zip |
WIP plugins: add theme support
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/config.ts | 43 | ||||
-rw-r--r-- | server/controllers/api/users/me.ts | 1 | ||||
-rw-r--r-- | server/helpers/custom-validators/plugins.ts | 6 | ||||
-rw-r--r-- | server/initializers/checker-before-init.ts | 3 | ||||
-rw-r--r-- | server/initializers/config.ts | 3 | ||||
-rw-r--r-- | server/initializers/constants.ts | 5 | ||||
-rw-r--r-- | server/initializers/migrations/0400-user-theme.ts | 25 | ||||
-rw-r--r-- | server/lib/plugins/plugin-manager.ts | 34 | ||||
-rw-r--r-- | server/lib/plugins/theme-utils.ts | 24 | ||||
-rw-r--r-- | server/middlewares/validators/config.ts | 5 | ||||
-rw-r--r-- | server/middlewares/validators/plugins.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 4 | ||||
-rw-r--r-- | server/models/account/user.ts | 11 | ||||
-rw-r--r-- | server/tests/api/check-params/config.ts | 3 | ||||
-rw-r--r-- | server/tests/api/server/config.ts | 3 |
15 files changed, 148 insertions, 24 deletions
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 8563b7437..088234074 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { snakeCase } from 'lodash' | 2 | import { snakeCase } from 'lodash' |
3 | import { ServerConfig, ServerConfigPlugin, UserRight } from '../../../shared' | 3 | import { ServerConfig, UserRight } from '../../../shared' |
4 | import { About } from '../../../shared/models/server/about.model' | 4 | import { About } from '../../../shared/models/server/about.model' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
@@ -16,7 +16,7 @@ import { isNumeric } from 'validator' | |||
16 | import { objectConverter } from '../../helpers/core-utils' | 16 | import { objectConverter } from '../../helpers/core-utils' |
17 | import { CONFIG, reloadConfig } from '../../initializers/config' | 17 | import { CONFIG, reloadConfig } from '../../initializers/config' |
18 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 18 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
19 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 19 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
20 | 20 | ||
21 | const packageJSON = require('../../../../package.json') | 21 | const packageJSON = require('../../../../package.json') |
22 | const configRouter = express.Router() | 22 | const configRouter = express.Router() |
@@ -56,19 +56,23 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
56 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true) | 56 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true) |
57 | .map(r => parseInt(r, 10)) | 57 | .map(r => parseInt(r, 10)) |
58 | 58 | ||
59 | const plugins: ServerConfigPlugin[] = [] | ||
60 | const registeredPlugins = PluginManager.Instance.getRegisteredPlugins() | 59 | const registeredPlugins = PluginManager.Instance.getRegisteredPlugins() |
61 | for (const pluginName of Object.keys(registeredPlugins)) { | 60 | .map(p => ({ |
62 | const plugin = registeredPlugins[ pluginName ] | 61 | name: p.name, |
63 | if (plugin.type !== PluginType.PLUGIN) continue | 62 | version: p.version, |
64 | 63 | description: p.description, | |
65 | plugins.push({ | 64 | clientScripts: p.clientScripts |
66 | name: plugin.name, | 65 | })) |
67 | version: plugin.version, | 66 | |
68 | description: plugin.description, | 67 | const registeredThemes = PluginManager.Instance.getRegisteredThemes() |
69 | clientScripts: plugin.clientScripts | 68 | .map(t => ({ |
70 | }) | 69 | name: t.name, |
71 | } | 70 | version: t.version, |
71 | description: t.description, | ||
72 | clientScripts: t.clientScripts | ||
73 | })) | ||
74 | |||
75 | const defaultTheme = getThemeOrDefault(CONFIG.THEME.DEFAULT) | ||
72 | 76 | ||
73 | const json: ServerConfig = { | 77 | const json: ServerConfig = { |
74 | instance: { | 78 | instance: { |
@@ -82,7 +86,13 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
82 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | 86 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS |
83 | } | 87 | } |
84 | }, | 88 | }, |
85 | plugins, | 89 | plugin: { |
90 | registered: registeredPlugins | ||
91 | }, | ||
92 | theme: { | ||
93 | registered: registeredThemes, | ||
94 | default: defaultTheme | ||
95 | }, | ||
86 | email: { | 96 | email: { |
87 | enabled: Emailer.isEnabled() | 97 | enabled: Emailer.isEnabled() |
88 | }, | 98 | }, |
@@ -240,6 +250,9 @@ function customConfig (): CustomConfig { | |||
240 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT | 250 | javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT |
241 | } | 251 | } |
242 | }, | 252 | }, |
253 | theme: { | ||
254 | default: CONFIG.THEME.DEFAULT | ||
255 | }, | ||
243 | services: { | 256 | services: { |
244 | twitter: { | 257 | twitter: { |
245 | username: CONFIG.SERVICES.TWITTER.USERNAME, | 258 | username: CONFIG.SERVICES.TWITTER.USERNAME, |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index a078334fe..e7ed3de64 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -183,6 +183,7 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
183 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo | 183 | if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo |
184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
186 | if (body.theme !== undefined) user.theme = body.theme | ||
186 | 187 | ||
187 | if (body.email !== undefined) { | 188 | if (body.email !== undefined) { |
188 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 189 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts index 2fcdc581f..4ab5f9ce8 100644 --- a/server/helpers/custom-validators/plugins.ts +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -4,6 +4,7 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type' | |||
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' | 5 | import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' |
6 | import { isUrlValid } from './activitypub/misc' | 6 | import { isUrlValid } from './activitypub/misc' |
7 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | ||
7 | 8 | ||
8 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS | 9 | const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS |
9 | 10 | ||
@@ -61,6 +62,10 @@ function isCSSPathsValid (css: any[]) { | |||
61 | return isArray(css) && css.every(c => isSafePath(c)) | 62 | return isArray(css) && css.every(c => isSafePath(c)) |
62 | } | 63 | } |
63 | 64 | ||
65 | function isThemeValid (name: string) { | ||
66 | return isPluginNameValid(name) && isThemeRegistered(name) | ||
67 | } | ||
68 | |||
64 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { | 69 | function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { |
65 | return isNpmPluginNameValid(packageJSON.name) && | 70 | return isNpmPluginNameValid(packageJSON.name) && |
66 | isPluginDescriptionValid(packageJSON.description) && | 71 | isPluginDescriptionValid(packageJSON.description) && |
@@ -82,6 +87,7 @@ function isLibraryCodeValid (library: any) { | |||
82 | export { | 87 | export { |
83 | isPluginTypeValid, | 88 | isPluginTypeValid, |
84 | isPackageJSONValid, | 89 | isPackageJSONValid, |
90 | isThemeValid, | ||
85 | isPluginVersionValid, | 91 | isPluginVersionValid, |
86 | isPluginNameValid, | 92 | isPluginNameValid, |
87 | isPluginDescriptionValid, | 93 | isPluginDescriptionValid, |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 1f5ec20df..c94bca2f8 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -29,7 +29,8 @@ function checkMissedConfig () { | |||
29 | 'followers.instance.enabled', 'followers.instance.manual_approval', | 29 | 'followers.instance.enabled', 'followers.instance.manual_approval', |
30 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', | 30 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', |
31 | 'history.videos.max_age', 'views.videos.remote.max_age', | 31 | 'history.videos.max_age', 'views.videos.remote.max_age', |
32 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max' | 32 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
33 | 'theme.default' | ||
33 | ] | 34 | ] |
34 | const requiredAlternatives = [ | 35 | const requiredAlternatives = [ |
35 | [ // set | 36 | [ // set |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 6737edcd6..dfc4bea21 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -224,6 +224,9 @@ const CONFIG = { | |||
224 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, | 224 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, |
225 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | 225 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } |
226 | } | 226 | } |
227 | }, | ||
228 | THEME: { | ||
229 | get DEFAULT () { return config.get<string>('theme.default') } | ||
227 | } | 230 | } |
228 | } | 231 | } |
229 | 232 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 8ceefbd0e..9d61ed537 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 395 | 17 | const LAST_MIGRATION_VERSION = 400 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -585,6 +585,8 @@ const P2P_MEDIA_LOADER_PEER_VERSION = 2 | |||
585 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' | 585 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' |
586 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) | 586 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) |
587 | 587 | ||
588 | const DEFAULT_THEME = 'default' | ||
589 | |||
588 | // --------------------------------------------------------------------------- | 590 | // --------------------------------------------------------------------------- |
589 | 591 | ||
590 | // Special constants for a test instance | 592 | // Special constants for a test instance |
@@ -667,6 +669,7 @@ export { | |||
667 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 669 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
668 | FEEDS, | 670 | FEEDS, |
669 | JOB_TTL, | 671 | JOB_TTL, |
672 | DEFAULT_THEME, | ||
670 | NSFW_POLICY_TYPES, | 673 | NSFW_POLICY_TYPES, |
671 | STATIC_MAX_AGE, | 674 | STATIC_MAX_AGE, |
672 | STATIC_PATHS, | 675 | STATIC_PATHS, |
diff --git a/server/initializers/migrations/0400-user-theme.ts b/server/initializers/migrations/0400-user-theme.ts new file mode 100644 index 000000000..2c1763890 --- /dev/null +++ b/server/initializers/migrations/0400-user-theme.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING, | ||
11 | allowNull: false, | ||
12 | defaultValue: 'default' | ||
13 | } | ||
14 | |||
15 | await utils.queryInterface.addColumn('user', 'theme', data) | ||
16 | } | ||
17 | |||
18 | function down (options) { | ||
19 | throw new Error('Not implemented.') | ||
20 | } | ||
21 | |||
22 | export { | ||
23 | up, | ||
24 | down | ||
25 | } | ||
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 7cbfa8569..8496979f8 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -11,6 +11,7 @@ import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' | |||
11 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 11 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
12 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 12 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
13 | import { outputFile } from 'fs-extra' | 13 | import { outputFile } from 'fs-extra' |
14 | import { ServerConfigPlugin } from '../../../shared/models/server' | ||
14 | 15 | ||
15 | export interface RegisteredPlugin { | 16 | export interface RegisteredPlugin { |
16 | name: string | 17 | name: string |
@@ -47,7 +48,7 @@ export class PluginManager { | |||
47 | private constructor () { | 48 | private constructor () { |
48 | } | 49 | } |
49 | 50 | ||
50 | async registerPlugins () { | 51 | async registerPluginsAndThemes () { |
51 | await this.resetCSSGlobalFile() | 52 | await this.resetCSSGlobalFile() |
52 | 53 | ||
53 | const plugins = await PluginModel.listEnabledPluginsAndThemes() | 54 | const plugins = await PluginModel.listEnabledPluginsAndThemes() |
@@ -63,12 +64,20 @@ export class PluginManager { | |||
63 | this.sortHooksByPriority() | 64 | this.sortHooksByPriority() |
64 | } | 65 | } |
65 | 66 | ||
67 | getRegisteredPluginOrTheme (name: string) { | ||
68 | return this.registeredPlugins[name] | ||
69 | } | ||
70 | |||
66 | getRegisteredPlugin (name: string) { | 71 | getRegisteredPlugin (name: string) { |
67 | return this.registeredPlugins[ name ] | 72 | const registered = this.getRegisteredPluginOrTheme(name) |
73 | |||
74 | if (!registered || registered.type !== PluginType.PLUGIN) return undefined | ||
75 | |||
76 | return registered | ||
68 | } | 77 | } |
69 | 78 | ||
70 | getRegisteredTheme (name: string) { | 79 | getRegisteredTheme (name: string) { |
71 | const registered = this.getRegisteredPlugin(name) | 80 | const registered = this.getRegisteredPluginOrTheme(name) |
72 | 81 | ||
73 | if (!registered || registered.type !== PluginType.THEME) return undefined | 82 | if (!registered || registered.type !== PluginType.THEME) return undefined |
74 | 83 | ||
@@ -76,7 +85,11 @@ export class PluginManager { | |||
76 | } | 85 | } |
77 | 86 | ||
78 | getRegisteredPlugins () { | 87 | getRegisteredPlugins () { |
79 | return this.registeredPlugins | 88 | return this.getRegisteredPluginsOrThemes(PluginType.PLUGIN) |
89 | } | ||
90 | |||
91 | getRegisteredThemes () { | ||
92 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | ||
80 | } | 93 | } |
81 | 94 | ||
82 | async runHook (hookName: string, param?: any) { | 95 | async runHook (hookName: string, param?: any) { |
@@ -309,6 +322,19 @@ export class PluginManager { | |||
309 | } | 322 | } |
310 | } | 323 | } |
311 | 324 | ||
325 | private getRegisteredPluginsOrThemes (type: PluginType) { | ||
326 | const plugins: RegisteredPlugin[] = [] | ||
327 | |||
328 | for (const pluginName of Object.keys(this.registeredPlugins)) { | ||
329 | const plugin = this.registeredPlugins[ pluginName ] | ||
330 | if (plugin.type !== type) continue | ||
331 | |||
332 | plugins.push(plugin) | ||
333 | } | ||
334 | |||
335 | return plugins | ||
336 | } | ||
337 | |||
312 | static get Instance () { | 338 | static get Instance () { |
313 | return this.instance || (this.instance = new this()) | 339 | return this.instance || (this.instance = new this()) |
314 | } | 340 | } |
diff --git a/server/lib/plugins/theme-utils.ts b/server/lib/plugins/theme-utils.ts new file mode 100644 index 000000000..066339e65 --- /dev/null +++ b/server/lib/plugins/theme-utils.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { DEFAULT_THEME } from '../../initializers/constants' | ||
2 | import { PluginManager } from './plugin-manager' | ||
3 | import { CONFIG } from '../../initializers/config' | ||
4 | |||
5 | function getThemeOrDefault (name: string) { | ||
6 | if (isThemeRegistered(name)) return name | ||
7 | |||
8 | // Fallback to admin default theme | ||
9 | if (name !== CONFIG.THEME.DEFAULT) return getThemeOrDefault(CONFIG.THEME.DEFAULT) | ||
10 | |||
11 | return DEFAULT_THEME | ||
12 | } | ||
13 | |||
14 | function isThemeRegistered (name: string) { | ||
15 | if (name === DEFAULT_THEME) return true | ||
16 | |||
17 | return !!PluginManager.Instance.getRegisteredThemes() | ||
18 | .find(r => r.name === name) | ||
19 | } | ||
20 | |||
21 | export { | ||
22 | getThemeOrDefault, | ||
23 | isThemeRegistered | ||
24 | } | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index d015fa6fe..31b131914 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users' | 3 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { Emailer } from '../../lib/emailer' | 6 | import { Emailer } from '../../lib/emailer' |
7 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
8 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
8 | 9 | ||
9 | const customConfigUpdateValidator = [ | 10 | const customConfigUpdateValidator = [ |
10 | body('instance.name').exists().withMessage('Should have a valid instance name'), | 11 | body('instance.name').exists().withMessage('Should have a valid instance name'), |
@@ -47,6 +48,8 @@ const customConfigUpdateValidator = [ | |||
47 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), | 48 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), |
48 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), | 49 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), |
49 | 50 | ||
51 | body('theme.default').custom(isThemeValid).withMessage('Should have a valid theme'), | ||
52 | |||
50 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 53 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
51 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 54 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
52 | 55 | ||
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 672299ee1..fcb461624 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -16,7 +16,7 @@ const servePluginStaticDirectoryValidator = [ | |||
16 | 16 | ||
17 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
18 | 18 | ||
19 | const plugin = PluginManager.Instance.getRegisteredPlugin(req.params.pluginName) | 19 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(req.params.pluginName) |
20 | 20 | ||
21 | if (!plugin || plugin.version !== req.params.pluginVersion) { | 21 | if (!plugin || plugin.version !== req.params.pluginVersion) { |
22 | return res.sendStatus(404) | 22 | return res.sendStatus(404) |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 947ed36c3..df7f77b84 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -28,6 +28,7 @@ import { ActorModel } from '../../models/activitypub/actor' | |||
28 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' | 28 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' |
29 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 29 | import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
30 | import { UserRegister } from '../../../shared/models/users/user-register.model' | 30 | import { UserRegister } from '../../../shared/models/users/user-register.model' |
31 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
31 | 32 | ||
32 | const usersAddValidator = [ | 33 | const usersAddValidator = [ |
33 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 34 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -204,6 +205,9 @@ const usersUpdateMeValidator = [ | |||
204 | body('videosHistoryEnabled') | 205 | body('videosHistoryEnabled') |
205 | .optional() | 206 | .optional() |
206 | .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'), | 207 | .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'), |
208 | body('theme') | ||
209 | .optional() | ||
210 | .custom(isThemeValid).withMessage('Should have a valid theme'), | ||
207 | 211 | ||
208 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 212 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
209 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 213 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 0f425bb82..b8ca1dd5c 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -44,7 +44,7 @@ import { VideoChannelModel } from '../video/video-channel' | |||
44 | import { AccountModel } from './account' | 44 | import { AccountModel } from './account' |
45 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 45 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
46 | import { values } from 'lodash' | 46 | import { values } from 'lodash' |
47 | import { NSFW_POLICY_TYPES } from '../../initializers/constants' | 47 | import { DEFAULT_THEME, NSFW_POLICY_TYPES } from '../../initializers/constants' |
48 | import { clearCacheByUserId } from '../../lib/oauth-model' | 48 | import { clearCacheByUserId } from '../../lib/oauth-model' |
49 | import { UserNotificationSettingModel } from './user-notification-setting' | 49 | import { UserNotificationSettingModel } from './user-notification-setting' |
50 | import { VideoModel } from '../video/video' | 50 | import { VideoModel } from '../video/video' |
@@ -52,6 +52,8 @@ import { ActorModel } from '../activitypub/actor' | |||
52 | import { ActorFollowModel } from '../activitypub/actor-follow' | 52 | import { ActorFollowModel } from '../activitypub/actor-follow' |
53 | import { VideoImportModel } from '../video/video-import' | 53 | import { VideoImportModel } from '../video/video-import' |
54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' | 54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' |
55 | import { isThemeValid } from '../../helpers/custom-validators/plugins' | ||
56 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | ||
55 | 57 | ||
56 | enum ScopeNames { | 58 | enum ScopeNames { |
57 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 59 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -187,6 +189,12 @@ export class UserModel extends Model<UserModel> { | |||
187 | @Column(DataType.BIGINT) | 189 | @Column(DataType.BIGINT) |
188 | videoQuotaDaily: number | 190 | videoQuotaDaily: number |
189 | 191 | ||
192 | @AllowNull(false) | ||
193 | @Default(DEFAULT_THEME) | ||
194 | @Is('UserTheme', value => throwIfNotValid(value, isThemeValid, 'theme')) | ||
195 | @Column | ||
196 | theme: string | ||
197 | |||
190 | @CreatedAt | 198 | @CreatedAt |
191 | createdAt: Date | 199 | createdAt: Date |
192 | 200 | ||
@@ -560,6 +568,7 @@ export class UserModel extends Model<UserModel> { | |||
560 | autoPlayVideo: this.autoPlayVideo, | 568 | autoPlayVideo: this.autoPlayVideo, |
561 | videoLanguages: this.videoLanguages, | 569 | videoLanguages: this.videoLanguages, |
562 | role: this.role, | 570 | role: this.role, |
571 | theme: getThemeOrDefault(this.theme), | ||
563 | roleLabel: USER_ROLE_LABELS[ this.role ], | 572 | roleLabel: USER_ROLE_LABELS[ this.role ], |
564 | videoQuota: this.videoQuota, | 573 | videoQuota: this.videoQuota, |
565 | videoQuotaDaily: this.videoQuotaDaily, | 574 | videoQuotaDaily: this.videoQuotaDaily, |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index a0d9392dc..7773ae1e7 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -27,6 +27,9 @@ describe('Test config API validators', function () { | |||
27 | css: 'body { background-color: red; }' | 27 | css: 'body { background-color: red; }' |
28 | } | 28 | } |
29 | }, | 29 | }, |
30 | theme: { | ||
31 | default: 'default' | ||
32 | }, | ||
30 | services: { | 33 | services: { |
31 | twitter: { | 34 | twitter: { |
32 | username: '@MySuperUsername', | 35 | username: '@MySuperUsername', |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index c39516dee..78fdc9cc0 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -190,6 +190,9 @@ describe('Test config', function () { | |||
190 | css: 'body { background-color: red; }' | 190 | css: 'body { background-color: red; }' |
191 | } | 191 | } |
192 | }, | 192 | }, |
193 | theme: { | ||
194 | default: 'default' | ||
195 | }, | ||
193 | services: { | 196 | services: { |
194 | twitter: { | 197 | twitter: { |
195 | username: '@Kuja', | 198 | username: '@Kuja', |