1
0
Fork 0
mirror of https://github.com/Chocobozzz/PeerTube.git synced 2025-10-03 09:49:20 +02:00
Peertube/server/core/middlewares/validators/config.ts
Gianantonio Pini e74bf8ae2a
feat(config): add admin options to customize default "Browse videos" behaviour (#7193)
* feat(customBrowseVideosDefaultSort): update server config

* feat(customBrowseVideosDefaultSort): update client admin-config-general component

* feat(customBrowseVideosDefaultSort): add new consistency check to server checker-after-init

* feat(customBrowseVideosDefaultSort): update config .yaml with more details about available options

* feat(customBrowseVideosDefaultSort): refactor consistency check in server checker-after-init

* feat(customBrowseVideosDefaultSort): client, add new select-videos-sort shared component

* feat(customBrowseVideosDefaultSort): client, refactor admin-config-general component to use new select-videos-sort shared component

* feat(customBrowseVideosDefaultSort): client, fix my-select-videos-sort width in admin-config-general

* feat(customBrowseVideosDefaultSort): client, update video-filters-header scss

* feat(customBrowseVideosDefaultSort): client, update videos-list-all component

* feat(config): refactor checkBrowseVideosConfig logic into separate custom validator

* feat(config): refactor isBrowseVideosDefaultSortValid to use template literals

* feat(config): refactor isBrowseVideosDefaultSortValid

* feat(config): add check for invalid browse videos config to customConfigUpdateValidator

* feat(config): group browse-videos tests in describe block

* feat(config): refactor to use client.browse_videos section, instead of browse.videos section

* feat(config): add browse_videos default_scope config key (config and server changes)

* feat(config): add browse_videos default_scope config key (client changes)

* Reorder browse videos before videos

* Fix i18n message

---------

Co-authored-by: Chocobozzz <me@florianbigard.com>
2025-09-10 10:41:45 +02:00

279 lines
11 KiB
TypeScript

import { CustomConfig, HttpStatusCode } from '@peertube/peertube-models'
import { isConfigLogoTypeValid } from '@server/helpers/custom-validators/config.js'
import { isIntOrNull } from '@server/helpers/custom-validators/misc.js'
import { isNumberArray, isStringArray } from '@server/helpers/custom-validators/search.js'
import { isVideoCommentsPolicyValid, isVideoLicenceValid, isVideoPrivacyValid } from '@server/helpers/custom-validators/videos.js'
import { guessLanguageFromReq } from '@server/helpers/i18n.js'
import { CONFIG, isEmailEnabled } from '@server/initializers/config.js'
import express from 'express'
import { body, param } from 'express-validator'
import { getBrowseVideosDefaultScopeError, getBrowseVideosDefaultSortError } from '../../helpers/custom-validators/browse-videos.js'
import { isThemeNameValid } from '../../helpers/custom-validators/plugins.js'
import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users.js'
import { isThemeRegistered } from '../../lib/plugins/theme-utils.js'
import { areValidationErrors, updateActorImageValidatorFactory } from './shared/index.js'
export const customConfigUpdateValidator = [
body('instance.name').exists(),
body('instance.shortDescription').exists(),
body('instance.description').exists(),
body('instance.terms').exists(),
body('instance.codeOfConduct').exists(),
body('instance.creationReason').exists(),
body('instance.moderationInformation').exists(),
body('instance.administrator').exists(),
body('instance.maintenanceLifetime').exists(),
body('instance.businessModel').exists(),
body('instance.hardwareInformation').exists(),
body('instance.serverCountry').exists(),
body('instance.support.text').exists(),
body('instance.social.externalLink').exists(),
body('instance.social.mastodonLink').exists(),
body('instance.social.blueskyLink').exists(),
body('instance.defaultLanguage').exists(),
body('instance.isNSFW').isBoolean(),
body('instance.languages').custom(isStringArray),
body('instance.categories').custom(isNumberArray),
body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid),
body('instance.defaultClientRoute').exists(),
body('instance.customizations.css').exists(),
body('instance.customizations.javascript').exists(),
body('services.twitter.username').exists(),
body('cache.previews.size').isInt(),
body('cache.captions.size').isInt(),
body('cache.torrents.size').isInt(),
body('cache.storyboards.size').isInt(),
body('signup.enabled').isBoolean(),
body('signup.limit').isInt(),
body('signup.requiresEmailVerification').isBoolean(),
body('signup.requiresApproval').isBoolean(),
body('signup.minimumAge').isInt(),
body('admin.email').isEmail(),
body('contactForm.enabled').isBoolean(),
body('user.history.videos.enabled').isBoolean(),
body('user.videoQuota').custom(isUserVideoQuotaValid),
body('user.videoQuotaDaily').custom(isUserVideoQuotaDailyValid),
body('videoChannels.maxPerUser').isInt(),
body('transcoding.enabled').isBoolean(),
body('transcoding.originalFile.keep').isBoolean(),
body('transcoding.allowAdditionalExtensions').isBoolean(),
body('transcoding.threads').isInt(),
body('transcoding.concurrency').isInt({ min: 1 }),
body('transcoding.resolutions.0p').isBoolean(),
body('transcoding.resolutions.144p').isBoolean(),
body('transcoding.resolutions.240p').isBoolean(),
body('transcoding.resolutions.360p').isBoolean(),
body('transcoding.resolutions.480p').isBoolean(),
body('transcoding.resolutions.720p').isBoolean(),
body('transcoding.resolutions.1080p').isBoolean(),
body('transcoding.resolutions.1440p').isBoolean(),
body('transcoding.resolutions.2160p').isBoolean(),
body('transcoding.remoteRunners.enabled').isBoolean(),
body('transcoding.alwaysTranscodeOriginalResolution').isBoolean(),
body('transcoding.fps.max').custom(isIntOrNull),
body('transcoding.webVideos.enabled').isBoolean(),
body('transcoding.hls.enabled').isBoolean(),
body('videoStudio.enabled').isBoolean(),
body('videoStudio.remoteRunners.enabled').isBoolean(),
body('videoFile.update.enabled').isBoolean(),
body('import.videos.concurrency').isInt({ min: 0 }),
body('import.videos.http.enabled').isBoolean(),
body('import.videos.torrent.enabled').isBoolean(),
body('import.videoChannelSynchronization.enabled').isBoolean(),
body('import.users.enabled').isBoolean(),
body('export.users.enabled').isBoolean(),
body('export.users.maxUserVideoQuota').exists(),
body('export.users.exportExpiration').exists(),
body('trending.videos.algorithms.default').exists(),
body('trending.videos.algorithms.enabled').exists(),
body('followers.instance.enabled').isBoolean(),
body('followers.instance.manualApproval').isBoolean(),
body('followers.channels.enabled').isBoolean(),
body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)),
body('broadcastMessage.enabled').isBoolean(),
body('broadcastMessage.message').exists(),
body('broadcastMessage.level').exists(),
body('broadcastMessage.dismissable').isBoolean(),
body('live.enabled').isBoolean(),
body('live.allowReplay').isBoolean(),
body('live.maxDuration').isInt(),
body('live.maxInstanceLives').custom(isIntOrNull),
body('live.maxUserLives').custom(isIntOrNull),
body('live.transcoding.enabled').isBoolean(),
body('live.transcoding.threads').isInt(),
body('live.transcoding.resolutions.144p').isBoolean(),
body('live.transcoding.resolutions.240p').isBoolean(),
body('live.transcoding.resolutions.360p').isBoolean(),
body('live.transcoding.resolutions.480p').isBoolean(),
body('live.transcoding.resolutions.720p').isBoolean(),
body('live.transcoding.resolutions.1080p').isBoolean(),
body('live.transcoding.resolutions.1440p').isBoolean(),
body('live.transcoding.resolutions.2160p').isBoolean(),
body('live.transcoding.alwaysTranscodeOriginalResolution').isBoolean(),
body('live.transcoding.fps.max').custom(isIntOrNull),
body('live.transcoding.remoteRunners.enabled').isBoolean(),
body('search.remoteUri.users').isBoolean(),
body('search.remoteUri.anonymous').isBoolean(),
body('search.searchIndex.enabled').isBoolean(),
body('search.searchIndex.url').exists(),
body('search.searchIndex.disableLocalSearch').isBoolean(),
body('search.searchIndex.isDefaultSearch').isBoolean(),
body('defaults.publish.commentsPolicy').custom(isVideoCommentsPolicyValid),
body('defaults.publish.privacy').custom(isVideoPrivacyValid),
body('defaults.publish.licence').optional().custom(isVideoLicenceValid),
body('defaults.p2p.webapp.enabled').isBoolean(),
body('defaults.p2p.embed.enabled').isBoolean(),
body('defaults.player.autoPlay').isBoolean(),
body('email.body.signature').exists(),
body('email.subject.prefix').exists(),
body('videoComments.acceptRemoteComments').isBoolean(),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
if (!checkInvalidConfigIfEmailDisabled(req.body, req, res)) return
if (!checkInvalidTranscodingConfig(req.body, req, res)) return
if (!checkInvalidSynchronizationConfig(req.body, req, res)) return
if (!checkInvalidLiveConfig(req.body, req, res)) return
if (!checkInvalidVideoStudioConfig(req.body, req, res)) return
if (!checkInvalidSearchConfig(req.body, req, res)) return
if (!checkInvalidBrowseVideosConfig(req.body, req, res)) return
return next()
}
]
export function ensureConfigIsEditable (req: express.Request, res: express.Response, next: express.NextFunction) {
if (!CONFIG.WEBADMIN.CONFIGURATION.EDITION.ALLOWED) {
return res.fail({
status: HttpStatusCode.METHOD_NOT_ALLOWED_405,
message: req.t('Server configuration is static and cannot be edited')
})
}
return next()
}
export const updateOrDeleteLogoValidator = [
param('logoType')
.custom(isConfigLogoTypeValid),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
if (areValidationErrors(req, res)) return
return next()
}
]
export const updateInstanceLogoValidator = updateActorImageValidatorFactory('logofile')
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (isEmailEnabled()) return true
if (customConfig.signup.requiresEmailVerification === true) {
res.fail({ message: req.t('SMTP is not configured but you require signup email verification.') })
return false
}
return true
}
function checkInvalidTranscodingConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (customConfig.transcoding.enabled === false) return true
if (customConfig.transcoding.webVideos.enabled === false && customConfig.transcoding.hls.enabled === false) {
res.fail({ message: req.t('You need to enable at least web_videos transcoding or hls transcoding') })
return false
}
return true
}
function checkInvalidSynchronizationConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (customConfig.import.videoChannelSynchronization.enabled && !customConfig.import.videos.http.enabled) {
res.fail({ message: req.t('You need to enable HTTP video import in order to enable channel synchronization') })
return false
}
return true
}
function checkInvalidLiveConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (customConfig.live.enabled === false) return true
if (customConfig.live.allowReplay === true && customConfig.transcoding.enabled === false) {
res.fail({ message: req.t('You cannot allow live replay if transcoding is not enabled') })
return false
}
return true
}
function checkInvalidVideoStudioConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (customConfig.videoStudio.enabled === false) return true
if (customConfig.videoStudio.enabled === true && customConfig.transcoding.enabled === false) {
res.fail({ message: req.t('You cannot enable video studio if transcoding is not enabled') })
return false
}
return true
}
function checkInvalidSearchConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
if (customConfig.search.searchIndex.enabled === false) return true
if (customConfig.search.searchIndex.enabled === true && customConfig.search.remoteUri.users === false) {
res.fail({ message: req.t('You cannot enable search index without enabling remote URI search for users.') })
return false
}
return true
}
function checkInvalidBrowseVideosConfig (customConfig: CustomConfig, req: express.Request, res: express.Response) {
const sortError = getBrowseVideosDefaultSortError(
customConfig.client.browseVideos.defaultSort,
customConfig.trending.videos.algorithms.enabled,
guessLanguageFromReq(req, res)
)
if (sortError) {
res.fail({ message: sortError })
return false
}
const scopeError = getBrowseVideosDefaultScopeError(customConfig.client.browseVideos.defaultScope, guessLanguageFromReq(req, res))
if (scopeError) {
res.fail({ message: scopeError })
return false
}
return true
}