Implement daily upload limit (#956)
* Implement daily upload limit (ref #652) * remove duplicate code * review fixes * fix tests? * whitespace fixes, finish leftover todo * fix tests * added some new tests * use different config value for tests * remove todo
This commit is contained in:
parent
c907c2fa3f
commit
bee0abffff
|
@ -142,6 +142,20 @@
|
||||||
{{ formErrors.userVideoQuota }}
|
{{ formErrors.userVideoQuota }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label i18n for="userVideoQuotaDaily">User default daily upload limit</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily">
|
||||||
|
<option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
|
||||||
|
{{ videoQuotaDailyOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="formErrors.userVideoQuotaDaily" class="form-error">
|
||||||
|
{{ formErrors.userVideoQuotaDaily }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</ngb-tab>
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,7 @@ import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/
|
||||||
styleUrls: [ './edit-custom-config.component.scss' ]
|
styleUrls: [ './edit-custom-config.component.scss' ]
|
||||||
})
|
})
|
||||||
export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
customConfig: CustomConfig
|
static videoQuotaOptions = [
|
||||||
resolutions = [ '240p', '360p', '480p', '720p', '1080p' ]
|
|
||||||
|
|
||||||
videoQuotaOptions = [
|
|
||||||
{ value: -1, label: 'Unlimited' },
|
{ value: -1, label: 'Unlimited' },
|
||||||
{ value: 0, label: '0' },
|
{ value: 0, label: '0' },
|
||||||
{ value: 100 * 1024 * 1024, label: '100MB' },
|
{ value: 100 * 1024 * 1024, label: '100MB' },
|
||||||
|
@ -28,6 +25,20 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
{ value: 20 * 1024 * 1024 * 1024, label: '20GB' },
|
{ value: 20 * 1024 * 1024 * 1024, label: '20GB' },
|
||||||
{ value: 50 * 1024 * 1024 * 1024, label: '50GB' }
|
{ value: 50 * 1024 * 1024 * 1024, label: '50GB' }
|
||||||
]
|
]
|
||||||
|
static videoQuotaDailyOptions = [
|
||||||
|
{ value: -1, label: 'Unlimited' },
|
||||||
|
{ value: 0, label: '0' },
|
||||||
|
{ value: 10 * 1024 * 1024, label: '10MB' },
|
||||||
|
{ value: 50 * 1024 * 1024, label: '50MB' },
|
||||||
|
{ value: 100 * 1024 * 1024, label: '100MB' },
|
||||||
|
{ value: 500 * 1024 * 1024, label: '500MB' },
|
||||||
|
{ value: 2 * 1024 * 1024 * 1024, label: '2GB' },
|
||||||
|
{ value: 5 * 1024 * 1024 * 1024, label: '5GB' }
|
||||||
|
]
|
||||||
|
|
||||||
|
customConfig: CustomConfig
|
||||||
|
resolutions = [ '240p', '360p', '480p', '720p', '1080p' ]
|
||||||
|
|
||||||
transcodingThreadOptions = [
|
transcodingThreadOptions = [
|
||||||
{ value: 0, label: 'Auto (via ffmpeg)' },
|
{ value: 0, label: 'Auto (via ffmpeg)' },
|
||||||
{ value: 1, label: '1' },
|
{ value: 1, label: '1' },
|
||||||
|
@ -75,6 +86,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
importVideosTorrentEnabled: null,
|
importVideosTorrentEnabled: null,
|
||||||
adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL,
|
adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL,
|
||||||
userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
||||||
|
userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
|
||||||
transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
|
||||||
transcodingEnabled: null,
|
transcodingEnabled: null,
|
||||||
customizationJavascript: null,
|
customizationJavascript: null,
|
||||||
|
@ -173,7 +185,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
email: this.form.value['adminEmail']
|
email: this.form.value['adminEmail']
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: this.form.value['userVideoQuota']
|
videoQuota: this.form.value['userVideoQuota'],
|
||||||
|
videoQuotaDaily: this.form.value['userVideoQuotaDaily']
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: this.form.value['transcodingEnabled'],
|
enabled: this.form.value['transcodingEnabled'],
|
||||||
|
@ -231,6 +244,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
|
||||||
signupLimit: this.customConfig.signup.limit,
|
signupLimit: this.customConfig.signup.limit,
|
||||||
adminEmail: this.customConfig.admin.email,
|
adminEmail: this.customConfig.admin.email,
|
||||||
userVideoQuota: this.customConfig.user.videoQuota,
|
userVideoQuota: this.customConfig.user.videoQuota,
|
||||||
|
userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily,
|
||||||
transcodingThreads: this.customConfig.transcoding.threads,
|
transcodingThreads: this.customConfig.transcoding.threads,
|
||||||
transcodingEnabled: this.customConfig.transcoding.enabled,
|
transcodingEnabled: this.customConfig.transcoding.enabled,
|
||||||
customizationJavascript: this.customConfig.instance.customizations.javascript,
|
customizationJavascript: this.customConfig.instance.customizations.javascript,
|
||||||
|
|
|
@ -62,6 +62,15 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label i18n for="videoQuotaDaily">Daily video quota</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="videoQuotaDaily" formControlName="videoQuotaDaily">
|
||||||
|
<option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
|
||||||
|
{{ videoQuotaDailyOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
|
<div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
|
||||||
Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br />
|
Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br />
|
||||||
At most, this user could use ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
|
At most, this user could use ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import { ServerService } from '../../../core'
|
import { ServerService } from '../../../core'
|
||||||
import { FormReactive } from '../../../shared'
|
import { FormReactive } from '../../../shared'
|
||||||
import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared'
|
import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared'
|
||||||
|
import { EditCustomConfigComponent } from '../../../+admin/config/edit-custom-config/'
|
||||||
|
|
||||||
export abstract class UserEdit extends FormReactive {
|
export abstract class UserEdit extends FormReactive {
|
||||||
videoQuotaOptions = [
|
|
||||||
{ value: -1, label: 'Unlimited' },
|
// These are used by a HTML select, so convert key into strings
|
||||||
{ value: 0, label: '0' },
|
videoQuotaOptions = EditCustomConfigComponent.videoQuotaOptions
|
||||||
{ value: 100 * 1024 * 1024, label: '100MB' },
|
.map(q => ({ value: q.value.toString(), label: q.label }))
|
||||||
{ value: 500 * 1024 * 1024, label: '500MB' },
|
videoQuotaDailyOptions = EditCustomConfigComponent.videoQuotaDailyOptions
|
||||||
{ value: 1024 * 1024 * 1024, label: '1GB' },
|
.map(q => ({ value: q.value.toString(), label: q.label }))
|
||||||
{ value: 5 * 1024 * 1024 * 1024, label: '5GB' },
|
|
||||||
{ value: 20 * 1024 * 1024 * 1024, label: '20GB' },
|
|
||||||
{ value: 50 * 1024 * 1024 * 1024, label: '50GB' }
|
|
||||||
].map(q => ({ value: q.value.toString(), label: q.label })) // Used by a HTML select, so convert key into strings
|
|
||||||
|
|
||||||
roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] }))
|
roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] }))
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
const defaultValues = { videoQuota: '-1' }
|
const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' }
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
email: this.userValidatorsService.USER_EMAIL,
|
email: this.userValidatorsService.USER_EMAIL,
|
||||||
role: this.userValidatorsService.USER_ROLE,
|
role: this.userValidatorsService.USER_ROLE,
|
||||||
videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA
|
videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
|
||||||
|
videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY
|
||||||
}, defaultValues)
|
}, defaultValues)
|
||||||
|
|
||||||
this.paramsSub = this.route.params.subscribe(routeParams => {
|
this.paramsSub = this.route.params.subscribe(routeParams => {
|
||||||
|
@ -64,6 +65,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// A select in HTML is always mapped as a string, we convert it to number
|
// A select in HTML is always mapped as a string, we convert it to number
|
||||||
userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
|
userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
|
||||||
|
userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
|
||||||
|
|
||||||
this.userService.updateUser(this.userId, userUpdate).subscribe(
|
this.userService.updateUser(this.userId, userUpdate).subscribe(
|
||||||
() => {
|
() => {
|
||||||
|
@ -93,7 +95,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
email: userJson.email,
|
email: userJson.email,
|
||||||
role: userJson.role,
|
role: userJson.role,
|
||||||
videoQuota: userJson.videoQuota
|
videoQuota: userJson.videoQuota,
|
||||||
|
videoQuotaDaily: userJson.videoQuotaDaily
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,8 @@ export class ServerService {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: -1
|
videoQuota: -1,
|
||||||
|
videoQuotaDaily: -1
|
||||||
},
|
},
|
||||||
import: {
|
import: {
|
||||||
videos: {
|
videos: {
|
||||||
|
|
|
@ -9,6 +9,7 @@ export class UserValidatorsService {
|
||||||
readonly USER_EMAIL: BuildFormValidator
|
readonly USER_EMAIL: BuildFormValidator
|
||||||
readonly USER_PASSWORD: BuildFormValidator
|
readonly USER_PASSWORD: BuildFormValidator
|
||||||
readonly USER_VIDEO_QUOTA: BuildFormValidator
|
readonly USER_VIDEO_QUOTA: BuildFormValidator
|
||||||
|
readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
|
||||||
readonly USER_ROLE: BuildFormValidator
|
readonly USER_ROLE: BuildFormValidator
|
||||||
readonly USER_DISPLAY_NAME: BuildFormValidator
|
readonly USER_DISPLAY_NAME: BuildFormValidator
|
||||||
readonly USER_DESCRIPTION: BuildFormValidator
|
readonly USER_DESCRIPTION: BuildFormValidator
|
||||||
|
@ -61,6 +62,13 @@ export class UserValidatorsService {
|
||||||
'min': this.i18n('Quota must be greater than -1.')
|
'min': this.i18n('Quota must be greater than -1.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.USER_VIDEO_QUOTA_DAILY = {
|
||||||
|
VALIDATORS: [ Validators.required, Validators.min(-1) ],
|
||||||
|
MESSAGES: {
|
||||||
|
'required': this.i18n('Daily upload limit is required.'),
|
||||||
|
'min': this.i18n('Daily upload limit must be greater than -1.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.USER_ROLE = {
|
this.USER_ROLE = {
|
||||||
VALIDATORS: [ Validators.required ],
|
VALIDATORS: [ Validators.required ],
|
||||||
|
|
|
@ -16,6 +16,7 @@ export type UserConstructorHash = {
|
||||||
email: string,
|
email: string,
|
||||||
role: UserRole,
|
role: UserRole,
|
||||||
videoQuota?: number,
|
videoQuota?: number,
|
||||||
|
videoQuotaDaily?: number,
|
||||||
nsfwPolicy?: NSFWPolicyType,
|
nsfwPolicy?: NSFWPolicyType,
|
||||||
autoPlayVideo?: boolean,
|
autoPlayVideo?: boolean,
|
||||||
createdAt?: Date,
|
createdAt?: Date,
|
||||||
|
@ -33,6 +34,7 @@ export class User implements UserServerModel {
|
||||||
nsfwPolicy: NSFWPolicyType
|
nsfwPolicy: NSFWPolicyType
|
||||||
autoPlayVideo: boolean
|
autoPlayVideo: boolean
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
videoQuotaDaily: number
|
||||||
account: Account
|
account: Account
|
||||||
videoChannels: VideoChannel[]
|
videoChannels: VideoChannel[]
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
@ -48,6 +50,7 @@ export class User implements UserServerModel {
|
||||||
|
|
||||||
this.videoChannels = hash.videoChannels
|
this.videoChannels = hash.videoChannels
|
||||||
this.videoQuota = hash.videoQuota
|
this.videoQuota = hash.videoQuota
|
||||||
|
this.videoQuotaDaily = hash.videoQuotaDaily
|
||||||
this.nsfwPolicy = hash.nsfwPolicy
|
this.nsfwPolicy = hash.nsfwPolicy
|
||||||
this.autoPlayVideo = hash.autoPlayVideo
|
this.autoPlayVideo = hash.autoPlayVideo
|
||||||
this.createdAt = hash.createdAt
|
this.createdAt = hash.createdAt
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
|
readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
|
||||||
|
|
||||||
userVideoQuotaUsed = 0
|
userVideoQuotaUsed = 0
|
||||||
|
userVideoQuotaUsedDaily = 0
|
||||||
|
|
||||||
isUploadingVideo = false
|
isUploadingVideo = false
|
||||||
isUpdatingVideo = false
|
isUpdatingVideo = false
|
||||||
|
@ -68,6 +69,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
|
|
||||||
this.userService.getMyVideoQuotaUsed()
|
this.userService.getMyVideoQuotaUsed()
|
||||||
.subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
|
.subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
|
||||||
|
|
||||||
|
this.userService.getMyVideoQuotaUsed()
|
||||||
|
.subscribe(data => this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
@ -115,10 +119,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bytePipes = new BytesPipe()
|
||||||
const videoQuota = this.authService.getUser().videoQuota
|
const videoQuota = this.authService.getUser().videoQuota
|
||||||
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
|
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
|
||||||
const bytePipes = new BytesPipe()
|
|
||||||
|
|
||||||
const msg = this.i18n(
|
const msg = this.i18n(
|
||||||
'Your video quota is exceeded with this video (video size: {{ videoSize }}, used: {{ videoQuotaUsed }}, quota: {{ videoQuota }})',
|
'Your video quota is exceeded with this video (video size: {{ videoSize }}, used: {{ videoQuotaUsed }}, quota: {{ videoQuota }})',
|
||||||
{
|
{
|
||||||
|
@ -131,6 +134,21 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
|
||||||
|
if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
|
||||||
|
const msg = this.i18n(
|
||||||
|
'Your daily video quota is exceeded with this video (video size: {{ videoSize }}, ' +
|
||||||
|
'used: {{ videoQuotaUsedDaily }}, quota: {{ videoQuotaDaily }})',
|
||||||
|
{
|
||||||
|
videoSize: bytePipes.transform(videofile.size, 0),
|
||||||
|
videoQuotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
|
||||||
|
videoQuotaDaily: bytePipes.transform(videoQuotaDaily, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.notificationsService.error(this.i18n('Error'), msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
|
const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
|
||||||
let name: string
|
let name: string
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,7 @@ user:
|
||||||
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
||||||
# -1 == unlimited
|
# -1 == unlimited
|
||||||
video_quota: -1
|
video_quota: -1
|
||||||
|
video_quota_daily: -1
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
@ -96,6 +96,7 @@ user:
|
||||||
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
|
||||||
# -1 == unlimited
|
# -1 == unlimited
|
||||||
video_quota: -1
|
video_quota: -1
|
||||||
|
video_quota_daily: -1
|
||||||
|
|
||||||
# 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.
|
||||||
|
|
|
@ -103,7 +103,8 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
||||||
|
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
|
||||||
toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
|
toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
|
||||||
toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
|
toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
|
||||||
toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
|
toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
|
||||||
|
toUpdate.user.videoQuotaDaily = parseInt('' + toUpdate.user.videoQuotaDaily, 10)
|
||||||
toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
|
toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
|
||||||
|
|
||||||
// camelCase to snake_case key
|
// camelCase to snake_case key
|
||||||
|
@ -223,7 +225,8 @@ function customConfig (): CustomConfig {
|
||||||
email: CONFIG.ADMIN.EMAIL
|
email: CONFIG.ADMIN.EMAIL
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
||||||
|
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: CONFIG.TRANSCODING.ENABLED,
|
enabled: CONFIG.TRANSCODING.ENABLED,
|
||||||
|
|
|
@ -134,7 +134,8 @@ async function createUser (req: express.Request, res: express.Response) {
|
||||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||||
autoPlayVideo: true,
|
autoPlayVideo: true,
|
||||||
role: body.role,
|
role: body.role,
|
||||||
videoQuota: body.videoQuota
|
videoQuota: body.videoQuota,
|
||||||
|
videoQuotaDaily: body.videoQuotaDaily
|
||||||
})
|
})
|
||||||
|
|
||||||
const { user, account } = await createUserAccountAndChannel(userToCreate)
|
const { user, account } = await createUserAccountAndChannel(userToCreate)
|
||||||
|
@ -163,7 +164,8 @@ async function registerUser (req: express.Request, res: express.Response) {
|
||||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||||
autoPlayVideo: true,
|
autoPlayVideo: true,
|
||||||
role: UserRole.USER,
|
role: UserRole.USER,
|
||||||
videoQuota: CONFIG.USER.VIDEO_QUOTA
|
videoQuota: CONFIG.USER.VIDEO_QUOTA,
|
||||||
|
videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
|
||||||
})
|
})
|
||||||
|
|
||||||
const { user } = await createUserAccountAndChannel(userToCreate)
|
const { user } = await createUserAccountAndChannel(userToCreate)
|
||||||
|
@ -219,6 +221,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
|
||||||
|
|
||||||
if (body.email !== undefined) userToUpdate.email = body.email
|
if (body.email !== undefined) userToUpdate.email = body.email
|
||||||
if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
|
if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
|
||||||
|
if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
|
||||||
if (body.role !== undefined) userToUpdate.role = body.role
|
if (body.role !== undefined) userToUpdate.role = body.role
|
||||||
|
|
||||||
const user = await userToUpdate.save()
|
const user = await userToUpdate.save()
|
||||||
|
|
|
@ -283,9 +283,11 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
|
||||||
// We did not load channels in res.locals.user
|
// We did not load channels in res.locals.user
|
||||||
const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
|
||||||
const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
|
const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
|
||||||
|
const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user)
|
||||||
|
|
||||||
const data: UserVideoQuota = {
|
const data: UserVideoQuota = {
|
||||||
videoQuotaUsed
|
videoQuotaUsed,
|
||||||
|
videoQuotaUsedDaily
|
||||||
}
|
}
|
||||||
return res.json(data)
|
return res.json(data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@ function isUserVideoQuotaValid (value: string) {
|
||||||
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isUserVideoQuotaDailyValid (value: string) {
|
||||||
|
return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA_DAILY)
|
||||||
|
}
|
||||||
|
|
||||||
function isUserUsernameValid (value: string) {
|
function isUserUsernameValid (value: string) {
|
||||||
const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max
|
const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max
|
||||||
const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min
|
const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min
|
||||||
|
@ -66,6 +70,7 @@ export {
|
||||||
isUserBlockedReasonValid,
|
isUserBlockedReasonValid,
|
||||||
isUserRoleValid,
|
isUserRoleValid,
|
||||||
isUserVideoQuotaValid,
|
isUserVideoQuotaValid,
|
||||||
|
isUserVideoQuotaDailyValid,
|
||||||
isUserUsernameValid,
|
isUserUsernameValid,
|
||||||
isUserNSFWPolicyValid,
|
isUserNSFWPolicyValid,
|
||||||
isUserAutoPlayVideoValid,
|
isUserAutoPlayVideoValid,
|
||||||
|
|
|
@ -47,7 +47,7 @@ function checkMissedConfig () {
|
||||||
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
|
'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
|
||||||
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
|
'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
|
||||||
'log.level',
|
'log.level',
|
||||||
'user.video_quota',
|
'user.video_quota', 'user.video_quota_daily',
|
||||||
'cache.previews.size', 'admin.email',
|
'cache.previews.size', 'admin.email',
|
||||||
'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
|
||||||
'transcoding.enabled', 'transcoding.threads',
|
'transcoding.enabled', 'transcoding.threads',
|
||||||
|
|
|
@ -202,7 +202,8 @@ const CONFIG = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
USER: {
|
USER: {
|
||||||
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') }
|
get VIDEO_QUOTA () { return config.get<number>('user.video_quota') },
|
||||||
|
get VIDEO_QUOTA_DAILY () { return config.get<number>('user.video_quota_daily') }
|
||||||
},
|
},
|
||||||
TRANSCODING: {
|
TRANSCODING: {
|
||||||
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
get ENABLED () { return config.get<boolean>('transcoding.enabled') },
|
||||||
|
@ -263,6 +264,7 @@ const CONSTRAINTS_FIELDS = {
|
||||||
USERNAME: { min: 3, max: 20 }, // Length
|
USERNAME: { min: 3, max: 20 }, // Length
|
||||||
PASSWORD: { min: 6, max: 255 }, // Length
|
PASSWORD: { min: 6, max: 255 }, // Length
|
||||||
VIDEO_QUOTA: { min: -1 },
|
VIDEO_QUOTA: { min: -1 },
|
||||||
|
VIDEO_QUOTA_DAILY: { min: -1 },
|
||||||
BLOCKED_REASON: { min: 3, max: 250 } // Length
|
BLOCKED_REASON: { min: 3, max: 250 } // Length
|
||||||
},
|
},
|
||||||
VIDEO_ABUSES: {
|
VIDEO_ABUSES: {
|
||||||
|
|
|
@ -123,7 +123,8 @@ async function createOAuthAdminIfNotExist () {
|
||||||
password,
|
password,
|
||||||
role,
|
role,
|
||||||
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
|
||||||
videoQuota: -1
|
videoQuota: -1,
|
||||||
|
videoQuotaDaily: -1
|
||||||
}
|
}
|
||||||
const user = new UserModel(userData)
|
const user = new UserModel(userData)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as Sequelize from 'sequelize'
|
||||||
|
import { CONSTRAINTS_FIELDS } from '../constants'
|
||||||
|
|
||||||
|
async function up (utils: {
|
||||||
|
transaction: Sequelize.Transaction
|
||||||
|
queryInterface: Sequelize.QueryInterface
|
||||||
|
sequelize: Sequelize.Sequelize
|
||||||
|
}): Promise<any> {
|
||||||
|
{
|
||||||
|
const data = {
|
||||||
|
type: Sequelize.BIGINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: -1
|
||||||
|
}
|
||||||
|
await utils.queryInterface.addColumn('user', 'videoQuotaDaily', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function down (options) {
|
||||||
|
throw new Error('Not implemented.')
|
||||||
|
}
|
||||||
|
|
||||||
|
export { up, down }
|
|
@ -12,7 +12,8 @@ import {
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
isUserRoleValid,
|
isUserRoleValid,
|
||||||
isUserUsernameValid,
|
isUserUsernameValid,
|
||||||
isUserVideoQuotaValid
|
isUserVideoQuotaValid,
|
||||||
|
isUserVideoQuotaDailyValid
|
||||||
} from '../../helpers/custom-validators/users'
|
} from '../../helpers/custom-validators/users'
|
||||||
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
import { isVideoExist } from '../../helpers/custom-validators/videos'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
|
@ -27,6 +28,7 @@ const usersAddValidator = [
|
||||||
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
|
||||||
body('email').isEmail().withMessage('Should have a valid email'),
|
body('email').isEmail().withMessage('Should have a valid email'),
|
||||||
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
||||||
|
body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
|
||||||
body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
|
body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
@ -112,6 +114,7 @@ const usersUpdateValidator = [
|
||||||
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
||||||
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
|
||||||
body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
|
||||||
|
body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
|
||||||
body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
|
body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
|
|
@ -27,7 +27,8 @@ import {
|
||||||
isUserPasswordValid,
|
isUserPasswordValid,
|
||||||
isUserRoleValid,
|
isUserRoleValid,
|
||||||
isUserUsernameValid,
|
isUserUsernameValid,
|
||||||
isUserVideoQuotaValid
|
isUserVideoQuotaValid,
|
||||||
|
isUserVideoQuotaDailyValid
|
||||||
} from '../../helpers/custom-validators/users'
|
} from '../../helpers/custom-validators/users'
|
||||||
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
|
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
|
||||||
import { OAuthTokenModel } from '../oauth/oauth-token'
|
import { OAuthTokenModel } from '../oauth/oauth-token'
|
||||||
|
@ -124,6 +125,11 @@ export class UserModel extends Model<UserModel> {
|
||||||
@Column(DataType.BIGINT)
|
@Column(DataType.BIGINT)
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
|
||||||
|
@AllowNull(false)
|
||||||
|
@Is('UserVideoQuotaDaily', value => throwIfNotValid(value, isUserVideoQuotaDailyValid, 'video quota daily'))
|
||||||
|
@Column(DataType.BIGINT)
|
||||||
|
videoQuotaDaily: number
|
||||||
|
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
|
@ -271,7 +277,32 @@ export class UserModel extends Model<UserModel> {
|
||||||
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
|
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
|
||||||
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
||||||
'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
|
'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
|
||||||
'WHERE "account"."userId" = $userId GROUP BY "video"."id") t'
|
'WHERE "account"."userId" = $userId ' +
|
||||||
|
'GROUP BY "video"."id") t'
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
bind: { userId: user.id },
|
||||||
|
type: Sequelize.QueryTypes.SELECT
|
||||||
|
}
|
||||||
|
return UserModel.sequelize.query(query, options)
|
||||||
|
.then(([ { total } ]) => {
|
||||||
|
if (total === null) return 0
|
||||||
|
|
||||||
|
return parseInt(total, 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns comulative size of all video files uploaded in the last 24 hours.
|
||||||
|
static getOriginalVideoFileTotalDailyFromUser (user: UserModel) {
|
||||||
|
// Don't use sequelize because we need to use a sub query
|
||||||
|
const query = 'SELECT SUM("size") AS "total" FROM ' +
|
||||||
|
'(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
|
||||||
|
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
|
||||||
|
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
||||||
|
'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
|
||||||
|
'WHERE "account"."userId" = $userId ' +
|
||||||
|
'AND "video"."createdAt" > now() - interval \'24 hours\'' +
|
||||||
|
'GROUP BY "video"."id") t'
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
bind: { userId: user.id },
|
bind: { userId: user.id },
|
||||||
|
@ -303,6 +334,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
|
|
||||||
toFormattedJSON (): User {
|
toFormattedJSON (): User {
|
||||||
const videoQuotaUsed = this.get('videoQuotaUsed')
|
const videoQuotaUsed = this.get('videoQuotaUsed')
|
||||||
|
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
|
||||||
|
|
||||||
const json = {
|
const json = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -313,12 +345,18 @@ export class UserModel extends Model<UserModel> {
|
||||||
role: this.role,
|
role: this.role,
|
||||||
roleLabel: USER_ROLE_LABELS[ this.role ],
|
roleLabel: USER_ROLE_LABELS[ this.role ],
|
||||||
videoQuota: this.videoQuota,
|
videoQuota: this.videoQuota,
|
||||||
|
videoQuotaDaily: this.videoQuotaDaily,
|
||||||
createdAt: this.createdAt,
|
createdAt: this.createdAt,
|
||||||
blocked: this.blocked,
|
blocked: this.blocked,
|
||||||
blockedReason: this.blockedReason,
|
blockedReason: this.blockedReason,
|
||||||
account: this.Account.toFormattedJSON(),
|
account: this.Account.toFormattedJSON(),
|
||||||
videoChannels: [],
|
videoChannels: [],
|
||||||
videoQuotaUsed: videoQuotaUsed !== undefined ? parseInt(videoQuotaUsed, 10) : undefined
|
videoQuotaUsed: videoQuotaUsed !== undefined
|
||||||
|
? parseInt(videoQuotaUsed, 10)
|
||||||
|
: undefined,
|
||||||
|
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
|
||||||
|
? parseInt(videoQuotaUsedDaily, 10)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(this.Account.VideoChannels) === true) {
|
if (Array.isArray(this.Account.VideoChannels) === true) {
|
||||||
|
@ -335,12 +373,24 @@ export class UserModel extends Model<UserModel> {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
isAbleToUploadVideo (videoFile: { size: number }) {
|
async isAbleToUploadVideo (videoFile: { size: number }) {
|
||||||
if (this.videoQuota === -1) return Promise.resolve(true)
|
if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true)
|
||||||
|
|
||||||
return UserModel.getOriginalVideoFileTotalFromUser(this)
|
const [ totalBytes, totalBytesDaily ] = await Promise.all([
|
||||||
.then(totalBytes => {
|
UserModel.getOriginalVideoFileTotalFromUser(this),
|
||||||
return (videoFile.size + totalBytes) < this.videoQuota
|
UserModel.getOriginalVideoFileTotalDailyFromUser(this)
|
||||||
})
|
])
|
||||||
|
|
||||||
|
const uploadedTotal = videoFile.size + totalBytes
|
||||||
|
const uploadedDaily = videoFile.size + totalBytesDaily
|
||||||
|
if (this.videoQuotaDaily === -1) {
|
||||||
|
return uploadedTotal < this.videoQuota
|
||||||
|
}
|
||||||
|
if (this.videoQuota === -1) {
|
||||||
|
return uploadedDaily < this.videoQuotaDaily
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uploadedTotal < this.videoQuota) &&
|
||||||
|
(uploadedDaily < this.videoQuotaDaily)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,8 @@ describe('Test config API validators', function () {
|
||||||
email: 'superadmin1@example.com'
|
email: 'superadmin1@example.com'
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: 5242881
|
videoQuota: 5242881,
|
||||||
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -94,6 +94,7 @@ describe('Test users API validators', function () {
|
||||||
email: 'test@example.com',
|
email: 'test@example.com',
|
||||||
password: 'my super password',
|
password: 'my super password',
|
||||||
videoQuota: -1,
|
videoQuota: -1,
|
||||||
|
videoQuotaDaily: -1,
|
||||||
role: UserRole.USER
|
role: UserRole.USER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +174,24 @@ describe('Test users API validators', function () {
|
||||||
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail without a videoQuotaDaily', async function () {
|
||||||
|
const fields = omit(baseCorrectParams, 'videoQuotaDaily')
|
||||||
|
|
||||||
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
it('Should fail with an invalid videoQuota', async function () {
|
it('Should fail with an invalid videoQuota', async function () {
|
||||||
const fields = immutableAssign(baseCorrectParams, { videoQuota: -5 })
|
const fields = immutableAssign(baseCorrectParams, { videoQuota: -5 })
|
||||||
|
|
||||||
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should fail with an invalid videoQuotaDaily', async function () {
|
||||||
|
const fields = immutableAssign(baseCorrectParams, { videoQuotaDaily: -7 })
|
||||||
|
|
||||||
|
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
|
||||||
|
})
|
||||||
|
|
||||||
it('Should fail without a user role', async function () {
|
it('Should fail without a user role', async function () {
|
||||||
const fields = omit(baseCorrectParams, 'role')
|
const fields = omit(baseCorrectParams, 'role')
|
||||||
|
|
||||||
|
@ -607,7 +620,7 @@ describe('Test users API validators', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('When having a video quota', function () {
|
describe('When having a video quota', function () {
|
||||||
it('Should fail with a user having too many video', async function () {
|
it('Should fail with a user having too many videos', async function () {
|
||||||
await updateUser({
|
await updateUser({
|
||||||
url: server.url,
|
url: server.url,
|
||||||
userId: rootId,
|
userId: rootId,
|
||||||
|
@ -618,7 +631,7 @@ describe('Test users API validators', function () {
|
||||||
await uploadVideo(server.url, server.accessToken, {}, 403)
|
await uploadVideo(server.url, server.accessToken, {}, 403)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should fail with a registered user having too many video', async function () {
|
it('Should fail with a registered user having too many videos', async function () {
|
||||||
this.timeout(30000)
|
this.timeout(30000)
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
|
@ -663,6 +676,45 @@ describe('Test users API validators', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('When having a daily video quota', function () {
|
||||||
|
it('Should fail with a user having too many videos', async function () {
|
||||||
|
await updateUser({
|
||||||
|
url: server.url,
|
||||||
|
userId: rootId,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
videoQuotaDaily: 42
|
||||||
|
})
|
||||||
|
|
||||||
|
await uploadVideo(server.url, server.accessToken, {}, 403)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When having an absolute and daily video quota', function () {
|
||||||
|
it('Should fail if exceeding total quota', async function () {
|
||||||
|
await updateUser({
|
||||||
|
url: server.url,
|
||||||
|
userId: rootId,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
videoQuota: 42,
|
||||||
|
videoQuotaDaily: 1024 * 1024 * 1024
|
||||||
|
})
|
||||||
|
|
||||||
|
await uploadVideo(server.url, server.accessToken, {}, 403)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fail if exceeding daily quota', async function () {
|
||||||
|
await updateUser({
|
||||||
|
url: server.url,
|
||||||
|
userId: rootId,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
videoQuota: 1024 * 1024 * 1024,
|
||||||
|
videoQuotaDaily: 42
|
||||||
|
})
|
||||||
|
|
||||||
|
await uploadVideo(server.url, server.accessToken, {}, 403)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('When asking a password reset', function () {
|
describe('When asking a password reset', function () {
|
||||||
const path = '/api/v1/users/ask-reset-password'
|
const path = '/api/v1/users/ask-reset-password'
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ function checkInitialConfig (data: CustomConfig) {
|
||||||
expect(data.signup.limit).to.equal(4)
|
expect(data.signup.limit).to.equal(4)
|
||||||
expect(data.admin.email).to.equal('admin1@example.com')
|
expect(data.admin.email).to.equal('admin1@example.com')
|
||||||
expect(data.user.videoQuota).to.equal(5242880)
|
expect(data.user.videoQuota).to.equal(5242880)
|
||||||
|
expect(data.user.videoQuotaDaily).to.equal(318742)
|
||||||
expect(data.transcoding.enabled).to.be.false
|
expect(data.transcoding.enabled).to.be.false
|
||||||
expect(data.transcoding.threads).to.equal(2)
|
expect(data.transcoding.threads).to.equal(2)
|
||||||
expect(data.transcoding.resolutions['240p']).to.be.true
|
expect(data.transcoding.resolutions['240p']).to.be.true
|
||||||
|
@ -65,6 +66,7 @@ function checkUpdatedConfig (data: CustomConfig) {
|
||||||
expect(data.signup.limit).to.equal(5)
|
expect(data.signup.limit).to.equal(5)
|
||||||
expect(data.admin.email).to.equal('superadmin1@example.com')
|
expect(data.admin.email).to.equal('superadmin1@example.com')
|
||||||
expect(data.user.videoQuota).to.equal(5242881)
|
expect(data.user.videoQuota).to.equal(5242881)
|
||||||
|
expect(data.user.videoQuotaDaily).to.equal(318742)
|
||||||
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.resolutions['240p']).to.be.false
|
expect(data.transcoding.resolutions['240p']).to.be.false
|
||||||
|
@ -152,7 +154,8 @@ describe('Test config', function () {
|
||||||
email: 'superadmin1@example.com'
|
email: 'superadmin1@example.com'
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: 5242881
|
videoQuota: 5242881,
|
||||||
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -80,7 +80,8 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
|
||||||
email: 'superadmin1@example.com'
|
email: 'superadmin1@example.com'
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
videoQuota: 5242881
|
videoQuota: 5242881,
|
||||||
|
videoQuotaDaily: 318742
|
||||||
},
|
},
|
||||||
transcoding: {
|
transcoding: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -10,6 +10,7 @@ function createUser (
|
||||||
username: string,
|
username: string,
|
||||||
password: string,
|
password: string,
|
||||||
videoQuota = 1000000,
|
videoQuota = 1000000,
|
||||||
|
videoQuotaDaily = -1,
|
||||||
role: UserRole = UserRole.USER,
|
role: UserRole = UserRole.USER,
|
||||||
specialStatus = 200
|
specialStatus = 200
|
||||||
) {
|
) {
|
||||||
|
@ -19,7 +20,8 @@ function createUser (
|
||||||
password,
|
password,
|
||||||
role,
|
role,
|
||||||
email: username + '@example.com',
|
email: username + '@example.com',
|
||||||
videoQuota
|
videoQuota,
|
||||||
|
videoQuotaDaily
|
||||||
}
|
}
|
||||||
|
|
||||||
return request(url)
|
return request(url)
|
||||||
|
@ -202,6 +204,7 @@ function updateUser (options: {
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
email?: string,
|
email?: string,
|
||||||
videoQuota?: number,
|
videoQuota?: number,
|
||||||
|
videoQuotaDaily?: number,
|
||||||
role?: UserRole
|
role?: UserRole
|
||||||
}) {
|
}) {
|
||||||
const path = '/api/v1/users/' + options.userId
|
const path = '/api/v1/users/' + options.userId
|
||||||
|
@ -209,6 +212,7 @@ function updateUser (options: {
|
||||||
const toSend = {}
|
const toSend = {}
|
||||||
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
|
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
|
||||||
if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
|
if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
|
||||||
|
if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
|
||||||
if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
|
if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
|
||||||
|
|
||||||
return makePutBodyRequest({
|
return makePutBodyRequest({
|
||||||
|
|
|
@ -42,6 +42,7 @@ export interface CustomConfig {
|
||||||
|
|
||||||
user: {
|
user: {
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
videoQuotaDaily: number
|
||||||
}
|
}
|
||||||
|
|
||||||
transcoding: {
|
transcoding: {
|
||||||
|
|
|
@ -66,5 +66,6 @@ export interface ServerConfig {
|
||||||
|
|
||||||
user: {
|
user: {
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
videoQuotaDaily: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,6 @@ export interface UserCreate {
|
||||||
password: string
|
password: string
|
||||||
email: string
|
email: string
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
videoQuotaDaily: number
|
||||||
role: UserRole
|
role: UserRole
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,6 @@ import { UserRole } from './user-role'
|
||||||
export interface UserUpdate {
|
export interface UserUpdate {
|
||||||
email?: string
|
email?: string
|
||||||
videoQuota?: number
|
videoQuota?: number
|
||||||
|
videoQuotaDaily?: number
|
||||||
role?: UserRole
|
role?: UserRole
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export interface UserVideoQuota {
|
export interface UserVideoQuota {
|
||||||
videoQuotaUsed: number
|
videoQuotaUsed: number
|
||||||
|
videoQuotaUsedDaily: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export interface User {
|
||||||
autoPlayVideo: boolean
|
autoPlayVideo: boolean
|
||||||
role: UserRole
|
role: UserRole
|
||||||
videoQuota: number
|
videoQuota: number
|
||||||
|
videoQuotaDaily: number
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
account: Account
|
account: Account
|
||||||
videoChannels?: VideoChannel[]
|
videoChannels?: VideoChannel[]
|
||||||
|
|
Loading…
Reference in New Issue