aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorFelix Ableitner <me@nutomic.com>2018-08-28 02:01:35 -0500
committerChocobozzz <me@florianbigard.com>2018-08-28 09:01:35 +0200
commitbee0abffff73804d816b90c7fd599e0a51c09d61 (patch)
treefae6d58637f9c63a3800090277f8e130b43442dd /server
parentc907c2fa3fd7c0a741117a0204d0ebca675124bd (diff)
downloadPeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.tar.gz
PeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.tar.zst
PeerTube-bee0abffff73804d816b90c7fd599e0a51c09d61.zip
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
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/config.ts7
-rw-r--r--server/controllers/api/users/index.ts7
-rw-r--r--server/controllers/api/users/me.ts4
-rw-r--r--server/helpers/custom-validators/users.ts5
-rw-r--r--server/initializers/checker.ts2
-rw-r--r--server/initializers/constants.ts4
-rw-r--r--server/initializers/installer.ts3
-rw-r--r--server/initializers/migrations/0260-upload_quota_daily.ts23
-rw-r--r--server/middlewares/validators/users.ts5
-rw-r--r--server/models/account/user.ts68
-rw-r--r--server/tests/api/check-params/config.ts3
-rw-r--r--server/tests/api/check-params/users.ts56
-rw-r--r--server/tests/api/server/config.ts5
-rw-r--r--server/tests/utils/server/config.ts3
-rw-r--r--server/tests/utils/users/users.ts6
15 files changed, 177 insertions, 24 deletions
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
103 } 103 }
104 }, 104 },
105 user: { 105 user: {
106 videoQuota: CONFIG.USER.VIDEO_QUOTA 106 videoQuota: CONFIG.USER.VIDEO_QUOTA,
107 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
107 } 108 }
108 } 109 }
109 110
@@ -154,6 +155,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
154 toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10) 155 toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
155 toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10) 156 toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
156 toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10) 157 toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
158 toUpdate.user.videoQuotaDaily = parseInt('' + toUpdate.user.videoQuotaDaily, 10)
157 toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10) 159 toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
158 160
159 // camelCase to snake_case key 161 // camelCase to snake_case key
@@ -223,7 +225,8 @@ function customConfig (): CustomConfig {
223 email: CONFIG.ADMIN.EMAIL 225 email: CONFIG.ADMIN.EMAIL
224 }, 226 },
225 user: { 227 user: {
226 videoQuota: CONFIG.USER.VIDEO_QUOTA 228 videoQuota: CONFIG.USER.VIDEO_QUOTA,
229 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
227 }, 230 },
228 transcoding: { 231 transcoding: {
229 enabled: CONFIG.TRANSCODING.ENABLED, 232 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) {
134 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 134 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
135 autoPlayVideo: true, 135 autoPlayVideo: true,
136 role: body.role, 136 role: body.role,
137 videoQuota: body.videoQuota 137 videoQuota: body.videoQuota,
138 videoQuotaDaily: body.videoQuotaDaily
138 }) 139 })
139 140
140 const { user, account } = await createUserAccountAndChannel(userToCreate) 141 const { user, account } = await createUserAccountAndChannel(userToCreate)
@@ -163,7 +164,8 @@ async function registerUser (req: express.Request, res: express.Response) {
163 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 164 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
164 autoPlayVideo: true, 165 autoPlayVideo: true,
165 role: UserRole.USER, 166 role: UserRole.USER,
166 videoQuota: CONFIG.USER.VIDEO_QUOTA 167 videoQuota: CONFIG.USER.VIDEO_QUOTA,
168 videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
167 }) 169 })
168 170
169 const { user } = await createUserAccountAndChannel(userToCreate) 171 const { user } = await createUserAccountAndChannel(userToCreate)
@@ -219,6 +221,7 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
219 221
220 if (body.email !== undefined) userToUpdate.email = body.email 222 if (body.email !== undefined) userToUpdate.email = body.email
221 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota 223 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
224 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
222 if (body.role !== undefined) userToUpdate.role = body.role 225 if (body.role !== undefined) userToUpdate.role = body.role
223 226
224 const user = await userToUpdate.save() 227 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
283 // We did not load channels in res.locals.user 283 // We did not load channels in res.locals.user
284 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 284 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
285 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) 285 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
286 const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user)
286 287
287 const data: UserVideoQuota = { 288 const data: UserVideoQuota = {
288 videoQuotaUsed 289 videoQuotaUsed,
290 videoQuotaUsedDaily
289 } 291 }
290 return res.json(data) 292 return res.json(data)
291} 293}
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) {
15 return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA) 15 return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA)
16} 16}
17 17
18function isUserVideoQuotaDailyValid (value: string) {
19 return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA_DAILY)
20}
21
18function isUserUsernameValid (value: string) { 22function isUserUsernameValid (value: string) {
19 const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max 23 const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max
20 const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min 24 const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min
@@ -66,6 +70,7 @@ export {
66 isUserBlockedReasonValid, 70 isUserBlockedReasonValid,
67 isUserRoleValid, 71 isUserRoleValid,
68 isUserVideoQuotaValid, 72 isUserVideoQuotaValid,
73 isUserVideoQuotaDailyValid,
69 isUserUsernameValid, 74 isUserUsernameValid,
70 isUserNSFWPolicyValid, 75 isUserNSFWPolicyValid,
71 isUserAutoPlayVideoValid, 76 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 () {
47 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', 47 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
48 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', 48 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
49 'log.level', 49 'log.level',
50 'user.video_quota', 50 'user.video_quota', 'user.video_quota_daily',
51 'cache.previews.size', 'admin.email', 51 'cache.previews.size', 'admin.email',
52 'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', 52 'signup.enabled', 'signup.limit', 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
53 'transcoding.enabled', 'transcoding.threads', 53 '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 = {
202 } 202 }
203 }, 203 },
204 USER: { 204 USER: {
205 get VIDEO_QUOTA () { return config.get<number>('user.video_quota') } 205 get VIDEO_QUOTA () { return config.get<number>('user.video_quota') },
206 get VIDEO_QUOTA_DAILY () { return config.get<number>('user.video_quota_daily') }
206 }, 207 },
207 TRANSCODING: { 208 TRANSCODING: {
208 get ENABLED () { return config.get<boolean>('transcoding.enabled') }, 209 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
@@ -263,6 +264,7 @@ const CONSTRAINTS_FIELDS = {
263 USERNAME: { min: 3, max: 20 }, // Length 264 USERNAME: { min: 3, max: 20 }, // Length
264 PASSWORD: { min: 6, max: 255 }, // Length 265 PASSWORD: { min: 6, max: 255 }, // Length
265 VIDEO_QUOTA: { min: -1 }, 266 VIDEO_QUOTA: { min: -1 },
267 VIDEO_QUOTA_DAILY: { min: -1 },
266 BLOCKED_REASON: { min: 3, max: 250 } // Length 268 BLOCKED_REASON: { min: 3, max: 250 } // Length
267 }, 269 },
268 VIDEO_ABUSES: { 270 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 () {
123 password, 123 password,
124 role, 124 role,
125 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 125 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
126 videoQuota: -1 126 videoQuota: -1,
127 videoQuotaDaily: -1
127 } 128 }
128 const user = new UserModel(userData) 129 const user = new UserModel(userData)
129 130
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 @@
1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3
4async function up (utils: {
5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize
8}): Promise<any> {
9 {
10 const data = {
11 type: Sequelize.BIGINT,
12 allowNull: false,
13 defaultValue: -1
14 }
15 await utils.queryInterface.addColumn('user', 'videoQuotaDaily', data)
16 }
17}
18
19function down (options) {
20 throw new Error('Not implemented.')
21}
22
23export { 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 {
12 isUserPasswordValid, 12 isUserPasswordValid,
13 isUserRoleValid, 13 isUserRoleValid,
14 isUserUsernameValid, 14 isUserUsernameValid,
15 isUserVideoQuotaValid 15 isUserVideoQuotaValid,
16 isUserVideoQuotaDailyValid
16} from '../../helpers/custom-validators/users' 17} from '../../helpers/custom-validators/users'
17import { isVideoExist } from '../../helpers/custom-validators/videos' 18import { isVideoExist } from '../../helpers/custom-validators/videos'
18import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
@@ -27,6 +28,7 @@ const usersAddValidator = [
27 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), 28 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
28 body('email').isEmail().withMessage('Should have a valid email'), 29 body('email').isEmail().withMessage('Should have a valid email'),
29 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), 30 body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
31 body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
30 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'), 32 body('role').custom(isUserRoleValid).withMessage('Should have a valid role'),
31 33
32 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 34 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -112,6 +114,7 @@ const usersUpdateValidator = [
112 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), 114 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
113 body('email').optional().isEmail().withMessage('Should have a valid email attribute'), 115 body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
114 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), 116 body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'),
117 body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'),
115 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'), 118 body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'),
116 119
117 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 120 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 {
27 isUserPasswordValid, 27 isUserPasswordValid,
28 isUserRoleValid, 28 isUserRoleValid,
29 isUserUsernameValid, 29 isUserUsernameValid,
30 isUserVideoQuotaValid 30 isUserVideoQuotaValid,
31 isUserVideoQuotaDailyValid
31} from '../../helpers/custom-validators/users' 32} from '../../helpers/custom-validators/users'
32import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 33import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
33import { OAuthTokenModel } from '../oauth/oauth-token' 34import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -124,6 +125,11 @@ export class UserModel extends Model<UserModel> {
124 @Column(DataType.BIGINT) 125 @Column(DataType.BIGINT)
125 videoQuota: number 126 videoQuota: number
126 127
128 @AllowNull(false)
129 @Is('UserVideoQuotaDaily', value => throwIfNotValid(value, isUserVideoQuotaDailyValid, 'video quota daily'))
130 @Column(DataType.BIGINT)
131 videoQuotaDaily: number
132
127 @CreatedAt 133 @CreatedAt
128 createdAt: Date 134 createdAt: Date
129 135
@@ -271,7 +277,32 @@ export class UserModel extends Model<UserModel> {
271 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 277 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
272 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 278 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
273 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 279 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
274 'WHERE "account"."userId" = $userId GROUP BY "video"."id") t' 280 'WHERE "account"."userId" = $userId ' +
281 'GROUP BY "video"."id") t'
282
283 const options = {
284 bind: { userId: user.id },
285 type: Sequelize.QueryTypes.SELECT
286 }
287 return UserModel.sequelize.query(query, options)
288 .then(([ { total } ]) => {
289 if (total === null) return 0
290
291 return parseInt(total, 10)
292 })
293 }
294
295 // Returns comulative size of all video files uploaded in the last 24 hours.
296 static getOriginalVideoFileTotalDailyFromUser (user: UserModel) {
297 // Don't use sequelize because we need to use a sub query
298 const query = 'SELECT SUM("size") AS "total" FROM ' +
299 '(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
300 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
301 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
302 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
303 'WHERE "account"."userId" = $userId ' +
304 'AND "video"."createdAt" > now() - interval \'24 hours\'' +
305 'GROUP BY "video"."id") t'
275 306
276 const options = { 307 const options = {
277 bind: { userId: user.id }, 308 bind: { userId: user.id },
@@ -303,6 +334,7 @@ export class UserModel extends Model<UserModel> {
303 334
304 toFormattedJSON (): User { 335 toFormattedJSON (): User {
305 const videoQuotaUsed = this.get('videoQuotaUsed') 336 const videoQuotaUsed = this.get('videoQuotaUsed')
337 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
306 338
307 const json = { 339 const json = {
308 id: this.id, 340 id: this.id,
@@ -313,12 +345,18 @@ export class UserModel extends Model<UserModel> {
313 role: this.role, 345 role: this.role,
314 roleLabel: USER_ROLE_LABELS[ this.role ], 346 roleLabel: USER_ROLE_LABELS[ this.role ],
315 videoQuota: this.videoQuota, 347 videoQuota: this.videoQuota,
348 videoQuotaDaily: this.videoQuotaDaily,
316 createdAt: this.createdAt, 349 createdAt: this.createdAt,
317 blocked: this.blocked, 350 blocked: this.blocked,
318 blockedReason: this.blockedReason, 351 blockedReason: this.blockedReason,
319 account: this.Account.toFormattedJSON(), 352 account: this.Account.toFormattedJSON(),
320 videoChannels: [], 353 videoChannels: [],
321 videoQuotaUsed: videoQuotaUsed !== undefined ? parseInt(videoQuotaUsed, 10) : undefined 354 videoQuotaUsed: videoQuotaUsed !== undefined
355 ? parseInt(videoQuotaUsed, 10)
356 : undefined,
357 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
358 ? parseInt(videoQuotaUsedDaily, 10)
359 : undefined
322 } 360 }
323 361
324 if (Array.isArray(this.Account.VideoChannels) === true) { 362 if (Array.isArray(this.Account.VideoChannels) === true) {
@@ -335,12 +373,24 @@ export class UserModel extends Model<UserModel> {
335 return json 373 return json
336 } 374 }
337 375
338 isAbleToUploadVideo (videoFile: { size: number }) { 376 async isAbleToUploadVideo (videoFile: { size: number }) {
339 if (this.videoQuota === -1) return Promise.resolve(true) 377 if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true)
340 378
341 return UserModel.getOriginalVideoFileTotalFromUser(this) 379 const [ totalBytes, totalBytesDaily ] = await Promise.all([
342 .then(totalBytes => { 380 UserModel.getOriginalVideoFileTotalFromUser(this),
343 return (videoFile.size + totalBytes) < this.videoQuota 381 UserModel.getOriginalVideoFileTotalDailyFromUser(this)
344 }) 382 ])
383
384 const uploadedTotal = videoFile.size + totalBytes
385 const uploadedDaily = videoFile.size + totalBytesDaily
386 if (this.videoQuotaDaily === -1) {
387 return uploadedTotal < this.videoQuota
388 }
389 if (this.videoQuota === -1) {
390 return uploadedDaily < this.videoQuotaDaily
391 }
392
393 return (uploadedTotal < this.videoQuota) &&
394 (uploadedDaily < this.videoQuotaDaily)
345 } 395 }
346} 396}
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 () {
48 email: 'superadmin1@example.com' 48 email: 'superadmin1@example.com'
49 }, 49 },
50 user: { 50 user: {
51 videoQuota: 5242881 51 videoQuota: 5242881,
52 videoQuotaDaily: 318742
52 }, 53 },
53 transcoding: { 54 transcoding: {
54 enabled: true, 55 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 () {
94 email: 'test@example.com', 94 email: 'test@example.com',
95 password: 'my super password', 95 password: 'my super password',
96 videoQuota: -1, 96 videoQuota: -1,
97 videoQuotaDaily: -1,
97 role: UserRole.USER 98 role: UserRole.USER
98 } 99 }
99 100
@@ -173,12 +174,24 @@ describe('Test users API validators', function () {
173 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 174 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
174 }) 175 })
175 176
177 it('Should fail without a videoQuotaDaily', async function () {
178 const fields = omit(baseCorrectParams, 'videoQuotaDaily')
179
180 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
181 })
182
176 it('Should fail with an invalid videoQuota', async function () { 183 it('Should fail with an invalid videoQuota', async function () {
177 const fields = immutableAssign(baseCorrectParams, { videoQuota: -5 }) 184 const fields = immutableAssign(baseCorrectParams, { videoQuota: -5 })
178 185
179 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 186 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
180 }) 187 })
181 188
189 it('Should fail with an invalid videoQuotaDaily', async function () {
190 const fields = immutableAssign(baseCorrectParams, { videoQuotaDaily: -7 })
191
192 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
193 })
194
182 it('Should fail without a user role', async function () { 195 it('Should fail without a user role', async function () {
183 const fields = omit(baseCorrectParams, 'role') 196 const fields = omit(baseCorrectParams, 'role')
184 197
@@ -607,7 +620,7 @@ describe('Test users API validators', function () {
607 }) 620 })
608 621
609 describe('When having a video quota', function () { 622 describe('When having a video quota', function () {
610 it('Should fail with a user having too many video', async function () { 623 it('Should fail with a user having too many videos', async function () {
611 await updateUser({ 624 await updateUser({
612 url: server.url, 625 url: server.url,
613 userId: rootId, 626 userId: rootId,
@@ -618,7 +631,7 @@ describe('Test users API validators', function () {
618 await uploadVideo(server.url, server.accessToken, {}, 403) 631 await uploadVideo(server.url, server.accessToken, {}, 403)
619 }) 632 })
620 633
621 it('Should fail with a registered user having too many video', async function () { 634 it('Should fail with a registered user having too many videos', async function () {
622 this.timeout(30000) 635 this.timeout(30000)
623 636
624 const user = { 637 const user = {
@@ -663,6 +676,45 @@ describe('Test users API validators', function () {
663 }) 676 })
664 }) 677 })
665 678
679 describe('When having a daily video quota', function () {
680 it('Should fail with a user having too many videos', async function () {
681 await updateUser({
682 url: server.url,
683 userId: rootId,
684 accessToken: server.accessToken,
685 videoQuotaDaily: 42
686 })
687
688 await uploadVideo(server.url, server.accessToken, {}, 403)
689 })
690 })
691
692 describe('When having an absolute and daily video quota', function () {
693 it('Should fail if exceeding total quota', async function () {
694 await updateUser({
695 url: server.url,
696 userId: rootId,
697 accessToken: server.accessToken,
698 videoQuota: 42,
699 videoQuotaDaily: 1024 * 1024 * 1024
700 })
701
702 await uploadVideo(server.url, server.accessToken, {}, 403)
703 })
704
705 it('Should fail if exceeding daily quota', async function () {
706 await updateUser({
707 url: server.url,
708 userId: rootId,
709 accessToken: server.accessToken,
710 videoQuota: 1024 * 1024 * 1024,
711 videoQuotaDaily: 42
712 })
713
714 await uploadVideo(server.url, server.accessToken, {}, 403)
715 })
716 })
717
666 describe('When asking a password reset', function () { 718 describe('When asking a password reset', function () {
667 const path = '/api/v1/users/ask-reset-password' 719 const path = '/api/v1/users/ask-reset-password'
668 720
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) {
37 expect(data.signup.limit).to.equal(4) 37 expect(data.signup.limit).to.equal(4)
38 expect(data.admin.email).to.equal('admin1@example.com') 38 expect(data.admin.email).to.equal('admin1@example.com')
39 expect(data.user.videoQuota).to.equal(5242880) 39 expect(data.user.videoQuota).to.equal(5242880)
40 expect(data.user.videoQuotaDaily).to.equal(318742)
40 expect(data.transcoding.enabled).to.be.false 41 expect(data.transcoding.enabled).to.be.false
41 expect(data.transcoding.threads).to.equal(2) 42 expect(data.transcoding.threads).to.equal(2)
42 expect(data.transcoding.resolutions['240p']).to.be.true 43 expect(data.transcoding.resolutions['240p']).to.be.true
@@ -65,6 +66,7 @@ function checkUpdatedConfig (data: CustomConfig) {
65 expect(data.signup.limit).to.equal(5) 66 expect(data.signup.limit).to.equal(5)
66 expect(data.admin.email).to.equal('superadmin1@example.com') 67 expect(data.admin.email).to.equal('superadmin1@example.com')
67 expect(data.user.videoQuota).to.equal(5242881) 68 expect(data.user.videoQuota).to.equal(5242881)
69 expect(data.user.videoQuotaDaily).to.equal(318742)
68 expect(data.transcoding.enabled).to.be.true 70 expect(data.transcoding.enabled).to.be.true
69 expect(data.transcoding.threads).to.equal(1) 71 expect(data.transcoding.threads).to.equal(1)
70 expect(data.transcoding.resolutions['240p']).to.be.false 72 expect(data.transcoding.resolutions['240p']).to.be.false
@@ -152,7 +154,8 @@ describe('Test config', function () {
152 email: 'superadmin1@example.com' 154 email: 'superadmin1@example.com'
153 }, 155 },
154 user: { 156 user: {
155 videoQuota: 5242881 157 videoQuota: 5242881,
158 videoQuotaDaily: 318742
156 }, 159 },
157 transcoding: { 160 transcoding: {
158 enabled: true, 161 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) {
80 email: 'superadmin1@example.com' 80 email: 'superadmin1@example.com'
81 }, 81 },
82 user: { 82 user: {
83 videoQuota: 5242881 83 videoQuota: 5242881,
84 videoQuotaDaily: 318742
84 }, 85 },
85 transcoding: { 86 transcoding: {
86 enabled: true, 87 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 (
10 username: string, 10 username: string,
11 password: string, 11 password: string,
12 videoQuota = 1000000, 12 videoQuota = 1000000,
13 videoQuotaDaily = -1,
13 role: UserRole = UserRole.USER, 14 role: UserRole = UserRole.USER,
14 specialStatus = 200 15 specialStatus = 200
15) { 16) {
@@ -19,7 +20,8 @@ function createUser (
19 password, 20 password,
20 role, 21 role,
21 email: username + '@example.com', 22 email: username + '@example.com',
22 videoQuota 23 videoQuota,
24 videoQuotaDaily
23 } 25 }
24 26
25 return request(url) 27 return request(url)
@@ -202,6 +204,7 @@ function updateUser (options: {
202 accessToken: string, 204 accessToken: string,
203 email?: string, 205 email?: string,
204 videoQuota?: number, 206 videoQuota?: number,
207 videoQuotaDaily?: number,
205 role?: UserRole 208 role?: UserRole
206}) { 209}) {
207 const path = '/api/v1/users/' + options.userId 210 const path = '/api/v1/users/' + options.userId
@@ -209,6 +212,7 @@ function updateUser (options: {
209 const toSend = {} 212 const toSend = {}
210 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email 213 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
211 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota 214 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
215 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
212 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role 216 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
213 217
214 return makePutBodyRequest({ 218 return makePutBodyRequest({