diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 49b89cef4..ca7890d84 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -142,6 +142,20 @@
{{ formErrors.userVideoQuota }}
+
+
Transcoding is enabled on server. The video quota only take in account
original video.
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts
index ea8c733c3..4e7ca8a1b 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.ts
+++ b/client/src/app/+admin/users/user-edit/user-edit.ts
@@ -1,18 +1,15 @@
import { ServerService } from '../../../core'
import { FormReactive } from '../../../shared'
import { USER_ROLE_LABELS, VideoResolution } from '../../../../../../shared'
+import { EditCustomConfigComponent } from '../../../+admin/config/edit-custom-config/'
export abstract class UserEdit extends FormReactive {
- videoQuotaOptions = [
- { value: -1, label: 'Unlimited' },
- { value: 0, label: '0' },
- { value: 100 * 1024 * 1024, label: '100MB' },
- { value: 500 * 1024 * 1024, label: '500MB' },
- { value: 1024 * 1024 * 1024, label: '1GB' },
- { 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
+
+ // These are used by a HTML select, so convert key into strings
+ videoQuotaOptions = EditCustomConfigComponent.videoQuotaOptions
+ .map(q => ({ value: q.value.toString(), label: q.label }))
+ videoQuotaDailyOptions = EditCustomConfigComponent.videoQuotaDailyOptions
+ .map(q => ({ value: q.value.toString(), label: q.label }))
roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] }))
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
index 06bde582e..5821229b3 100644
--- a/client/src/app/+admin/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -36,11 +36,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
}
ngOnInit () {
- const defaultValues = { videoQuota: '-1' }
+ const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' }
this.buildForm({
email: this.userValidatorsService.USER_EMAIL,
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)
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
userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+ userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
this.userService.updateUser(this.userId, userUpdate).subscribe(
() => {
@@ -93,7 +95,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
this.form.patchValue({
email: userJson.email,
role: userJson.role,
- videoQuota: userJson.videoQuota
+ videoQuota: userJson.videoQuota,
+ videoQuotaDaily: userJson.videoQuotaDaily
})
}
}
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 7823fa80e..a1ce12069 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -67,7 +67,8 @@ export class ServerService {
}
},
user: {
- videoQuota: -1
+ videoQuota: -1,
+ videoQuotaDaily: -1
},
import: {
videos: {
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/forms/form-validators/user-validators.service.ts
index ec9566ef3..424553d74 100644
--- a/client/src/app/shared/forms/form-validators/user-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/user-validators.service.ts
@@ -9,6 +9,7 @@ export class UserValidatorsService {
readonly USER_EMAIL: BuildFormValidator
readonly USER_PASSWORD: BuildFormValidator
readonly USER_VIDEO_QUOTA: BuildFormValidator
+ readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
readonly USER_ROLE: BuildFormValidator
readonly USER_DISPLAY_NAME: BuildFormValidator
readonly USER_DESCRIPTION: BuildFormValidator
@@ -61,6 +62,13 @@ export class UserValidatorsService {
'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 = {
VALIDATORS: [ Validators.required ],
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 2748001d0..877f1bf3a 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -16,6 +16,7 @@ export type UserConstructorHash = {
email: string,
role: UserRole,
videoQuota?: number,
+ videoQuotaDaily?: number,
nsfwPolicy?: NSFWPolicyType,
autoPlayVideo?: boolean,
createdAt?: Date,
@@ -33,6 +34,7 @@ export class User implements UserServerModel {
nsfwPolicy: NSFWPolicyType
autoPlayVideo: boolean
videoQuota: number
+ videoQuotaDaily: number
account: Account
videoChannels: VideoChannel[]
createdAt: Date
@@ -48,6 +50,7 @@ export class User implements UserServerModel {
this.videoChannels = hash.videoChannels
this.videoQuota = hash.videoQuota
+ this.videoQuotaDaily = hash.videoQuotaDaily
this.nsfwPolicy = hash.nsfwPolicy
this.autoPlayVideo = hash.autoPlayVideo
this.createdAt = hash.createdAt
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
index 3ec89ff62..c9ab35b1d 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
@@ -31,6 +31,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
userVideoQuotaUsed = 0
+ userVideoQuotaUsedDaily = 0
isUploadingVideo = false
isUpdatingVideo = false
@@ -68,6 +69,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
this.userService.getMyVideoQuotaUsed()
.subscribe(data => this.userVideoQuotaUsed = data.videoQuotaUsed)
+
+ this.userService.getMyVideoQuotaUsed()
+ .subscribe(data => this.userVideoQuotaUsedDaily = data.videoQuotaUsedDaily)
}
ngOnDestroy () {
@@ -115,10 +119,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
return
}
+ const bytePipes = new BytesPipe()
const videoQuota = this.authService.getUser().videoQuota
if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
- const bytePipes = new BytesPipe()
-
const msg = this.i18n(
'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
}
+ 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(/\.[^/.]+$/, '')
let name: string
diff --git a/config/default.yaml b/config/default.yaml
index 6a02f254d..7799ea927 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -83,6 +83,7 @@ user:
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
# -1 == unlimited
video_quota: -1
+ video_quota_daily: -1
# 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.
diff --git a/config/production.yaml.example b/config/production.yaml.example
index fc698ae96..33a26dec1 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -96,6 +96,7 @@ user:
# Default value of maximum video BYTES the user can upload (does not take into account transcoded files).
# -1 == unlimited
video_quota: -1
+ video_quota_daily: -1
# 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.
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index b25f739bb..3fd355e6d 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -103,7 +103,8 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
}
},
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.signup.limit = parseInt('' + toUpdate.signup.limit, 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)
// camelCase to snake_case key
@@ -223,7 +225,8 @@ function customConfig (): CustomConfig {
email: CONFIG.ADMIN.EMAIL
},
user: {
- videoQuota: CONFIG.USER.VIDEO_QUOTA
+ videoQuota: CONFIG.USER.VIDEO_QUOTA,
+ videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
},
transcoding: {
enabled: CONFIG.TRANSCODING.ENABLED,
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 608d439ac..25d51ae5e 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -134,7 +134,8 @@ async function createUser (req: express.Request, res: express.Response) {
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
autoPlayVideo: true,
role: body.role,
- videoQuota: body.videoQuota
+ videoQuota: body.videoQuota,
+ videoQuotaDaily: body.videoQuotaDaily
})
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,
autoPlayVideo: true,
role: UserRole.USER,
- videoQuota: CONFIG.USER.VIDEO_QUOTA
+ videoQuota: CONFIG.USER.VIDEO_QUOTA,
+ videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
})
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.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
+ if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
if (body.role !== undefined) userToUpdate.role = body.role
const user = await userToUpdate.save()
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 000c706b5..0f18b42f9 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -283,9 +283,11 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
// We did not load channels in res.locals.user
const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
+ const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user)
const data: UserVideoQuota = {
- videoQuotaUsed
+ videoQuotaUsed,
+ videoQuotaUsedDaily
}
return res.json(data)
}
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index c3cdefd4e..8d6247e41 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -15,6 +15,10 @@ function isUserVideoQuotaValid (value: string) {
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) {
const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max
const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min
@@ -66,6 +70,7 @@ export {
isUserBlockedReasonValid,
isUserRoleValid,
isUserVideoQuotaValid,
+ isUserVideoQuotaDailyValid,
isUserUsernameValid,
isUserNSFWPolicyValid,
isUserAutoPlayVideoValid,
diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts
index 608123607..916e9067e 100644
--- a/server/initializers/checker.ts
+++ b/server/initializers/checker.ts
@@ -47,7 +47,7 @@ function checkMissedConfig () {
'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',
'log.level',
- 'user.video_quota',
+ 'user.video_quota', 'user.video_quota_daily',
'cache.previews.size', 'admin.email',
'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
'transcoding.enabled', 'transcoding.threads',
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index a0dd78f42..4111d04ec 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -202,7 +202,8 @@ const CONFIG = {
}
},
USER: {
- get VIDEO_QUOTA () { return config.get
('user.video_quota') }
+ get VIDEO_QUOTA () { return config.get('user.video_quota') },
+ get VIDEO_QUOTA_DAILY () { return config.get('user.video_quota_daily') }
},
TRANSCODING: {
get ENABLED () { return config.get('transcoding.enabled') },
@@ -263,6 +264,7 @@ const CONSTRAINTS_FIELDS = {
USERNAME: { min: 3, max: 20 }, // Length
PASSWORD: { min: 6, max: 255 }, // Length
VIDEO_QUOTA: { min: -1 },
+ VIDEO_QUOTA_DAILY: { min: -1 },
BLOCKED_REASON: { min: 3, max: 250 } // Length
},
VIDEO_ABUSES: {
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index e319164e4..d4aaec8fe 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -123,7 +123,8 @@ async function createOAuthAdminIfNotExist () {
password,
role,
nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
- videoQuota: -1
+ videoQuota: -1,
+ videoQuotaDaily: -1
}
const user = new UserModel(userData)
diff --git a/server/initializers/migrations/0260-upload_quota_daily.ts b/server/initializers/migrations/0260-upload_quota_daily.ts
new file mode 100644
index 000000000..d25154ba6
--- /dev/null
+++ b/server/initializers/migrations/0260-upload_quota_daily.ts
@@ -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 {
+ {
+ 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 }
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index c8baf22e2..6c5e783e9 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -12,7 +12,8 @@ import {
isUserPasswordValid,
isUserRoleValid,
isUserUsernameValid,
- isUserVideoQuotaValid
+ isUserVideoQuotaValid,
+ isUserVideoQuotaDailyValid
} from '../../helpers/custom-validators/users'
import { isVideoExist } from '../../helpers/custom-validators/videos'
import { logger } from '../../helpers/logger'
@@ -27,6 +28,7 @@ const usersAddValidator = [
body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
body('email').isEmail().withMessage('Should have a valid email'),
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'),
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'),
body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
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'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 0150df4ce..178012eae 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -27,7 +27,8 @@ import {
isUserPasswordValid,
isUserRoleValid,
isUserUsernameValid,
- isUserVideoQuotaValid
+ isUserVideoQuotaValid,
+ isUserVideoQuotaDailyValid
} from '../../helpers/custom-validators/users'
import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -124,6 +125,11 @@ export class UserModel extends Model {
@Column(DataType.BIGINT)
videoQuota: number
+ @AllowNull(false)
+ @Is('UserVideoQuotaDaily', value => throwIfNotValid(value, isUserVideoQuotaDailyValid, 'video quota daily'))
+ @Column(DataType.BIGINT)
+ videoQuotaDaily: number
+
@CreatedAt
createdAt: Date
@@ -271,7 +277,32 @@ export class UserModel extends Model {
'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 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 = {
bind: { userId: user.id },
@@ -303,6 +334,7 @@ export class UserModel extends Model {
toFormattedJSON (): User {
const videoQuotaUsed = this.get('videoQuotaUsed')
+ const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
const json = {
id: this.id,
@@ -313,12 +345,18 @@ export class UserModel extends Model {
role: this.role,
roleLabel: USER_ROLE_LABELS[ this.role ],
videoQuota: this.videoQuota,
+ videoQuotaDaily: this.videoQuotaDaily,
createdAt: this.createdAt,
blocked: this.blocked,
blockedReason: this.blockedReason,
account: this.Account.toFormattedJSON(),
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) {
@@ -335,12 +373,24 @@ export class UserModel extends Model {
return json
}
- isAbleToUploadVideo (videoFile: { size: number }) {
- if (this.videoQuota === -1) return Promise.resolve(true)
+ async isAbleToUploadVideo (videoFile: { size: number }) {
+ if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true)
- return UserModel.getOriginalVideoFileTotalFromUser(this)
- .then(totalBytes => {
- return (videoFile.size + totalBytes) < this.videoQuota
- })
+ const [ totalBytes, totalBytesDaily ] = await Promise.all([
+ UserModel.getOriginalVideoFileTotalFromUser(this),
+ 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)
}
}
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index b26dfa252..ecfb76d47 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -48,7 +48,8 @@ describe('Test config API validators', function () {
email: 'superadmin1@example.com'
},
user: {
- videoQuota: 5242881
+ videoQuota: 5242881,
+ videoQuotaDaily: 318742
},
transcoding: {
enabled: true,
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index b3fb61f6c..8b2ed1b04 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -94,6 +94,7 @@ describe('Test users API validators', function () {
email: 'test@example.com',
password: 'my super password',
videoQuota: -1,
+ videoQuotaDaily: -1,
role: UserRole.USER
}
@@ -173,12 +174,24 @@ describe('Test users API validators', function () {
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 () {
const fields = immutableAssign(baseCorrectParams, { videoQuota: -5 })
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 () {
const fields = omit(baseCorrectParams, 'role')
@@ -607,7 +620,7 @@ describe('Test users API validators', 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({
url: server.url,
userId: rootId,
@@ -618,7 +631,7 @@ describe('Test users API validators', function () {
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)
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 () {
const path = '/api/v1/users/ask-reset-password'
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index f9805b6ea..8a5f27c34 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -37,6 +37,7 @@ function checkInitialConfig (data: CustomConfig) {
expect(data.signup.limit).to.equal(4)
expect(data.admin.email).to.equal('admin1@example.com')
expect(data.user.videoQuota).to.equal(5242880)
+ expect(data.user.videoQuotaDaily).to.equal(318742)
expect(data.transcoding.enabled).to.be.false
expect(data.transcoding.threads).to.equal(2)
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.admin.email).to.equal('superadmin1@example.com')
expect(data.user.videoQuota).to.equal(5242881)
+ expect(data.user.videoQuotaDaily).to.equal(318742)
expect(data.transcoding.enabled).to.be.true
expect(data.transcoding.threads).to.equal(1)
expect(data.transcoding.resolutions['240p']).to.be.false
@@ -152,7 +154,8 @@ describe('Test config', function () {
email: 'superadmin1@example.com'
},
user: {
- videoQuota: 5242881
+ videoQuota: 5242881,
+ videoQuotaDaily: 318742
},
transcoding: {
enabled: true,
diff --git a/server/tests/utils/server/config.ts b/server/tests/utils/server/config.ts
index d6ac3ef8a..799c31ae5 100644
--- a/server/tests/utils/server/config.ts
+++ b/server/tests/utils/server/config.ts
@@ -80,7 +80,8 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
email: 'superadmin1@example.com'
},
user: {
- videoQuota: 5242881
+ videoQuota: 5242881,
+ videoQuotaDaily: 318742
},
transcoding: {
enabled: true,
diff --git a/server/tests/utils/users/users.ts b/server/tests/utils/users/users.ts
index f786de6e3..5dba34b69 100644
--- a/server/tests/utils/users/users.ts
+++ b/server/tests/utils/users/users.ts
@@ -10,6 +10,7 @@ function createUser (
username: string,
password: string,
videoQuota = 1000000,
+ videoQuotaDaily = -1,
role: UserRole = UserRole.USER,
specialStatus = 200
) {
@@ -19,7 +20,8 @@ function createUser (
password,
role,
email: username + '@example.com',
- videoQuota
+ videoQuota,
+ videoQuotaDaily
}
return request(url)
@@ -202,6 +204,7 @@ function updateUser (options: {
accessToken: string,
email?: string,
videoQuota?: number,
+ videoQuotaDaily?: number,
role?: UserRole
}) {
const path = '/api/v1/users/' + options.userId
@@ -209,6 +212,7 @@ function updateUser (options: {
const toSend = {}
if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
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
return makePutBodyRequest({
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index d70c757b6..2f5cebf7f 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -42,6 +42,7 @@ export interface CustomConfig {
user: {
videoQuota: number
+ videoQuotaDaily: number
}
transcoding: {
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index 8cb087234..9bbeb14d2 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -66,5 +66,6 @@ export interface ServerConfig {
user: {
videoQuota: number
+ videoQuotaDaily: number
}
}
diff --git a/shared/models/users/user-create.model.ts b/shared/models/users/user-create.model.ts
index 65830f55e..08be4db05 100644
--- a/shared/models/users/user-create.model.ts
+++ b/shared/models/users/user-create.model.ts
@@ -5,5 +5,6 @@ export interface UserCreate {
password: string
email: string
videoQuota: number
+ videoQuotaDaily: number
role: UserRole
}
diff --git a/shared/models/users/user-update.model.ts b/shared/models/users/user-update.model.ts
index 96b454b7c..ce866fb18 100644
--- a/shared/models/users/user-update.model.ts
+++ b/shared/models/users/user-update.model.ts
@@ -3,5 +3,6 @@ import { UserRole } from './user-role'
export interface UserUpdate {
email?: string
videoQuota?: number
+ videoQuotaDaily?: number
role?: UserRole
}
diff --git a/shared/models/users/user-video-quota.model.ts b/shared/models/users/user-video-quota.model.ts
index b856fd9fc..a24871d71 100644
--- a/shared/models/users/user-video-quota.model.ts
+++ b/shared/models/users/user-video-quota.model.ts
@@ -1,3 +1,4 @@
export interface UserVideoQuota {
videoQuotaUsed: number
+ videoQuotaUsedDaily: number
}
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 455211aa3..8147dc48e 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -11,6 +11,7 @@ export interface User {
autoPlayVideo: boolean
role: UserRole
videoQuota: number
+ videoQuotaDaily: number
createdAt: Date
account: Account
videoChannels?: VideoChannel[]