Made the video channels limit (per user) server-wide configurable (#4491)
* Made the video channels limit (per user) server-wide configurable Implements https://github.com/Chocobozzz/PeerTube/issues/3092 Also added a "quota bar" in the account's settings page * Fixed lint errors * Another pass at fixing lint errors * Applied code suggestions * Removed 'video channels quota'
This commit is contained in:
parent
615836dbd4
commit
754b6f5f41
|
@ -272,6 +272,28 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row mt-4"> <!-- video channels grid -->
|
||||||
|
<div class="form-group col-12 col-lg-4 col-xl-3">
|
||||||
|
<div i18n class="inner-form-title">VIDEO CHANNELS</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
|
||||||
|
<div class="form-group" formGroupName="videoChannels">
|
||||||
|
<label i18n for="videoChannelsMaxPerUser">Max video channels per user</label>
|
||||||
|
|
||||||
|
<div class="number-with-unit">
|
||||||
|
<input
|
||||||
|
type="number" min="1" id="videoChannelsMaxPerUser" class="form-control"
|
||||||
|
formControlName="maxPerUser" [ngClass]="{ 'input-error': formErrors['videoChannels.maxPerUser'] }"
|
||||||
|
>
|
||||||
|
<span i18n>{form.value['videoChannels']['maxPerUser'], plural, =1 {channel} other {channels}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="formErrors.videoChannels.maxPerUser" class="form-error">{{ formErrors.videoChannels.maxPerUser }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-row mt-4"> <!-- search grid -->
|
<div class="form-row mt-4"> <!-- search grid -->
|
||||||
<div class="form-group col-12 col-lg-4 col-xl-3">
|
<div class="form-group col-12 col-lg-4 col-xl-3">
|
||||||
<div i18n class="inner-form-title">SEARCH</div>
|
<div i18n class="inner-form-title">SEARCH</div>
|
||||||
|
|
|
@ -22,7 +22,8 @@ import {
|
||||||
SERVICES_TWITTER_USERNAME_VALIDATOR,
|
SERVICES_TWITTER_USERNAME_VALIDATOR,
|
||||||
SIGNUP_LIMIT_VALIDATOR,
|
SIGNUP_LIMIT_VALIDATOR,
|
||||||
SIGNUP_MINIMUM_AGE_VALIDATOR,
|
SIGNUP_MINIMUM_AGE_VALIDATOR,
|
||||||
TRANSCODING_THREADS_VALIDATOR
|
TRANSCODING_THREADS_VALIDATOR,
|
||||||
|
MAX_VIDEO_CHANNELS_PER_USER_VALIDATOR
|
||||||
} from '@app/shared/form-validators/custom-config-validators'
|
} from '@app/shared/form-validators/custom-config-validators'
|
||||||
import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators'
|
import { USER_VIDEO_QUOTA_DAILY_VALIDATOR, USER_VIDEO_QUOTA_VALIDATOR } from '@app/shared/form-validators/user-validators'
|
||||||
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
|
@ -151,6 +152,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
|
videoQuota: USER_VIDEO_QUOTA_VALIDATOR,
|
||||||
videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR
|
videoQuotaDaily: USER_VIDEO_QUOTA_DAILY_VALIDATOR
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: MAX_VIDEO_CHANNELS_PER_USER_VALIDATOR
|
||||||
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: null,
|
enabled: null,
|
||||||
threads: TRANSCODING_THREADS_VALIDATOR,
|
threads: TRANSCODING_THREADS_VALIDATOR,
|
||||||
|
|
|
@ -98,6 +98,15 @@ export const MAX_USER_LIVES_VALIDATOR: BuildFormValidator = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_VIDEO_CHANNELS_PER_USER_VALIDATOR: BuildFormValidator = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ],
|
||||||
|
MESSAGES: {
|
||||||
|
required: $localize`Max video channels per user is required.`,
|
||||||
|
min: $localize`Max video channels per user must be greater or equal to 1.`,
|
||||||
|
pattern: $localize`Max video channels per user must be a number.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const CONCURRENCY_VALIDATOR: BuildFormValidator = {
|
export const CONCURRENCY_VALIDATOR: BuildFormValidator = {
|
||||||
VALIDATORS: [ Validators.required, Validators.min(1) ],
|
VALIDATORS: [ Validators.required, Validators.min(1) ],
|
||||||
MESSAGES: {
|
MESSAGES: {
|
||||||
|
|
|
@ -43,7 +43,12 @@ import {
|
||||||
} from './misc'
|
} from './misc'
|
||||||
import { PluginPlaceholderComponent } from './plugins'
|
import { PluginPlaceholderComponent } from './plugins'
|
||||||
import { ActorRedirectGuard } from './router'
|
import { ActorRedirectGuard } from './router'
|
||||||
import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users'
|
import {
|
||||||
|
UserHistoryService,
|
||||||
|
UserNotificationsComponent,
|
||||||
|
UserNotificationService,
|
||||||
|
UserQuotaComponent
|
||||||
|
} from './users'
|
||||||
import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
|
import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
|
||||||
import { VideoCaptionService } from './video-caption'
|
import { VideoCaptionService } from './video-caption'
|
||||||
import { VideoChannelService } from './video-channel'
|
import { VideoChannelService } from './video-channel'
|
||||||
|
|
|
@ -296,6 +296,9 @@ user:
|
||||||
video_quota: -1
|
video_quota: -1
|
||||||
video_quota_daily: -1
|
video_quota_daily: -1
|
||||||
|
|
||||||
|
video_channels:
|
||||||
|
max_per_user: 20 # Allows each user to create up to 20 video channels.
|
||||||
|
|
||||||
# If enabled, the video will be transcoded to mp4 (x264) with `faststart` flag
|
# If enabled, the video will be transcoded to mp4 (x264) with `faststart` flag
|
||||||
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions
|
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions
|
||||||
# Please, do not disable transcoding since many uploaded videos will not work
|
# Please, do not disable transcoding since many uploaded videos will not work
|
||||||
|
|
|
@ -306,6 +306,9 @@ user:
|
||||||
video_quota: -1
|
video_quota: -1
|
||||||
video_quota_daily: -1
|
video_quota_daily: -1
|
||||||
|
|
||||||
|
video_channels:
|
||||||
|
max_per_user: 20 # Allows each user to create up to 20 video channels.
|
||||||
|
|
||||||
# If enabled, the video will be transcoded to mp4 (x264) with `faststart` flag
|
# If enabled, the video will be transcoded to mp4 (x264) with `faststart` flag
|
||||||
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions
|
# In addition, if some resolutions are enabled the mp4 video file will be transcoded to these new resolutions
|
||||||
# Please, do not disable transcoding since many uploaded videos will not work
|
# Please, do not disable transcoding since many uploaded videos will not work
|
||||||
|
|
|
@ -196,6 +196,9 @@ function customConfig (): CustomConfig {
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
||||||
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: CONFIG.VIDEO_CHANNELS.MAX_PER_USER
|
||||||
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: CONFIG.TRANSCODING.ENABLED,
|
enabled: CONFIG.TRANSCODING.ENABLED,
|
||||||
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
|
allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
|
||||||
|
|
|
@ -19,6 +19,7 @@ function checkMissedConfig () {
|
||||||
'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins',
|
'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins',
|
||||||
'log.level',
|
'log.level',
|
||||||
'user.video_quota', 'user.video_quota_daily',
|
'user.video_quota', 'user.video_quota_daily',
|
||||||
|
'video_channels.max_per_user',
|
||||||
'csp.enabled', 'csp.report_only', 'csp.report_uri',
|
'csp.enabled', 'csp.report_only', 'csp.report_uri',
|
||||||
'security.frameguard.enabled',
|
'security.frameguard.enabled',
|
||||||
'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'admin.email', 'contact_form.enabled',
|
'cache.previews.size', 'cache.captions.size', 'cache.torrents.size', 'admin.email', 'contact_form.enabled',
|
||||||
|
|
|
@ -233,6 +233,9 @@ const CONFIG = {
|
||||||
get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
|
get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) },
|
||||||
get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
|
get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) }
|
||||||
},
|
},
|
||||||
|
VIDEO_CHANNELS: {
|
||||||
|
get MAX_PER_USER () { return config.get<number>('video_channels.max_per_user') }
|
||||||
|
},
|
||||||
TRANSCODING: {
|
TRANSCODING: {
|
||||||
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
||||||
get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
|
get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
|
||||||
|
|
|
@ -512,10 +512,6 @@ const OVERVIEWS = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const VIDEO_CHANNELS = {
|
|
||||||
MAX_PER_USER: 20
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const SERVER_ACTOR_NAME = 'peertube'
|
const SERVER_ACTOR_NAME = 'peertube'
|
||||||
|
@ -897,7 +893,6 @@ export {
|
||||||
VIDEO_TRANSCODING_FPS,
|
VIDEO_TRANSCODING_FPS,
|
||||||
FFMPEG_NICE,
|
FFMPEG_NICE,
|
||||||
ABUSE_STATES,
|
ABUSE_STATES,
|
||||||
VIDEO_CHANNELS,
|
|
||||||
LRU_CACHE,
|
LRU_CACHE,
|
||||||
REQUEST_TIMEOUT,
|
REQUEST_TIMEOUT,
|
||||||
USER_PASSWORD_RESET_LIFETIME,
|
USER_PASSWORD_RESET_LIFETIME,
|
||||||
|
|
|
@ -184,6 +184,9 @@ class ServerConfigManager {
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
||||||
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: CONFIG.VIDEO_CHANNELS.MAX_PER_USER
|
||||||
|
},
|
||||||
trending: {
|
trending: {
|
||||||
videos: {
|
videos: {
|
||||||
intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS,
|
intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS,
|
||||||
|
|
|
@ -38,6 +38,8 @@ const customConfigUpdateValidator = [
|
||||||
body('user.videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid video quota'),
|
body('user.videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid video quota'),
|
||||||
body('user.videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily video quota'),
|
body('user.videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily video quota'),
|
||||||
|
|
||||||
|
body('videoChannels.maxPerUser').isInt().withMessage("Should have a valid maximum amount of video channels per user"),
|
||||||
|
|
||||||
body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
|
body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
|
||||||
body('transcoding.allowAdditionalExtensions').isBoolean().withMessage('Should have a valid additional extensions boolean'),
|
body('transcoding.allowAdditionalExtensions').isBoolean().withMessage('Should have a valid additional extensions boolean'),
|
||||||
body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
|
body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { body, param, query } from 'express-validator'
|
import { body, param, query } from 'express-validator'
|
||||||
import { VIDEO_CHANNELS } from '@server/initializers/constants'
|
|
||||||
import { MChannelAccountDefault, MUser } from '@server/types/models'
|
import { MChannelAccountDefault, MUser } from '@server/types/models'
|
||||||
import { UserRight } from '../../../../shared'
|
import { UserRight } from '../../../../shared'
|
||||||
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
|
||||||
|
@ -15,6 +14,7 @@ import { logger } from '../../../helpers/logger'
|
||||||
import { ActorModel } from '../../../models/actor/actor'
|
import { ActorModel } from '../../../models/actor/actor'
|
||||||
import { VideoChannelModel } from '../../../models/video/video-channel'
|
import { VideoChannelModel } from '../../../models/video/video-channel'
|
||||||
import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared'
|
import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
|
||||||
const videoChannelsAddValidator = [
|
const videoChannelsAddValidator = [
|
||||||
body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'),
|
body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'),
|
||||||
|
@ -37,8 +37,8 @@ const videoChannelsAddValidator = [
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
|
const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
|
||||||
if (count >= VIDEO_CHANNELS.MAX_PER_USER) {
|
if (count >= CONFIG.VIDEO_CHANNELS.MAX_PER_USER) {
|
||||||
res.fail({ message: `You cannot create more than ${VIDEO_CHANNELS.MAX_PER_USER} channels` })
|
res.fail({ message: `You cannot create more than ${CONFIG.VIDEO_CHANNELS.MAX_PER_USER} channels` })
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
isVideoChannelDisplayNameValid,
|
isVideoChannelDisplayNameValid,
|
||||||
isVideoChannelSupportValid
|
isVideoChannelSupportValid
|
||||||
} from '../../helpers/custom-validators/video-channels'
|
} from '../../helpers/custom-validators/video-channels'
|
||||||
import { CONSTRAINTS_FIELDS, VIDEO_CHANNELS, WEBSERVER } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
|
||||||
import { sendDeleteActor } from '../../lib/activitypub/send'
|
import { sendDeleteActor } from '../../lib/activitypub/send'
|
||||||
import {
|
import {
|
||||||
MChannelActor,
|
MChannelActor,
|
||||||
|
@ -44,6 +44,7 @@ import { setAsUpdated } from '../shared'
|
||||||
import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
|
import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoPlaylistModel } from './video-playlist'
|
import { VideoPlaylistModel } from './video-playlist'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
|
|
||||||
export enum ScopeNames {
|
export enum ScopeNames {
|
||||||
FOR_API = 'FOR_API',
|
FOR_API = 'FOR_API',
|
||||||
|
@ -584,7 +585,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"`
|
||||||
|
|
||||||
static listAllByAccount (accountId: number) {
|
static listAllByAccount (accountId: number) {
|
||||||
const query = {
|
const query = {
|
||||||
limit: VIDEO_CHANNELS.MAX_PER_USER,
|
limit: CONFIG.VIDEO_CHANNELS.MAX_PER_USER,
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
attributes: [],
|
attributes: [],
|
||||||
|
|
|
@ -81,6 +81,9 @@ describe('Test config API validators', function () {
|
||||||
videoQuota: 5242881,
|
videoQuota: 5242881,
|
||||||
videoQuotaDaily: 318742
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: 20
|
||||||
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
|
|
|
@ -58,6 +58,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
|
||||||
expect(data.user.videoQuota).to.equal(5242880)
|
expect(data.user.videoQuota).to.equal(5242880)
|
||||||
expect(data.user.videoQuotaDaily).to.equal(-1)
|
expect(data.user.videoQuotaDaily).to.equal(-1)
|
||||||
|
|
||||||
|
expect(data.videoChannels.maxPerUser).to.equal(20)
|
||||||
|
|
||||||
expect(data.transcoding.enabled).to.be.false
|
expect(data.transcoding.enabled).to.be.false
|
||||||
expect(data.transcoding.allowAdditionalExtensions).to.be.false
|
expect(data.transcoding.allowAdditionalExtensions).to.be.false
|
||||||
expect(data.transcoding.allowAudioFiles).to.be.false
|
expect(data.transcoding.allowAudioFiles).to.be.false
|
||||||
|
@ -153,6 +155,8 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.user.videoQuota).to.equal(5242881)
|
expect(data.user.videoQuota).to.equal(5242881)
|
||||||
expect(data.user.videoQuotaDaily).to.equal(318742)
|
expect(data.user.videoQuotaDaily).to.equal(318742)
|
||||||
|
|
||||||
|
expect(data.videoChannels.maxPerUser).to.equal(24)
|
||||||
|
|
||||||
expect(data.transcoding.enabled).to.be.true
|
expect(data.transcoding.enabled).to.be.true
|
||||||
expect(data.transcoding.threads).to.equal(1)
|
expect(data.transcoding.threads).to.equal(1)
|
||||||
expect(data.transcoding.concurrency).to.equal(3)
|
expect(data.transcoding.concurrency).to.equal(3)
|
||||||
|
@ -265,6 +269,9 @@ const newCustomConfig: CustomConfig = {
|
||||||
videoQuota: 5242881,
|
videoQuota: 5242881,
|
||||||
videoQuotaDaily: 318742
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: 24
|
||||||
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
|
|
|
@ -220,6 +220,9 @@ export class ConfigCommand extends AbstractCommand {
|
||||||
videoQuota: 5242881,
|
videoQuota: 5242881,
|
||||||
videoQuotaDaily: 318742
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: 20
|
||||||
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
allowAdditionalExtensions: true,
|
allowAdditionalExtensions: true,
|
||||||
|
|
|
@ -85,6 +85,10 @@ export interface CustomConfig {
|
||||||
videoQuotaDaily: number
|
videoQuotaDaily: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: number
|
||||||
|
}
|
||||||
|
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,10 @@ export interface ServerConfig {
|
||||||
videoQuotaDaily: number
|
videoQuotaDaily: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
videoChannels: {
|
||||||
|
maxPerUser: number
|
||||||
|
}
|
||||||
|
|
||||||
trending: {
|
trending: {
|
||||||
videos: {
|
videos: {
|
||||||
intervalDays: number
|
intervalDays: number
|
||||||
|
|
Loading…
Reference in New Issue