diff options
author | Felix Ableitner <me@nutomic.com> | 2018-08-28 02:01:35 -0500 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-28 09:01:35 +0200 |
commit | bee0abffff73804d816b90c7fd599e0a51c09d61 (patch) | |
tree | fae6d58637f9c63a3800090277f8e130b43442dd /server | |
parent | c907c2fa3fd7c0a741117a0204d0ebca675124bd (diff) | |
download | PeerTube-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.ts | 7 | ||||
-rw-r--r-- | server/controllers/api/users/index.ts | 7 | ||||
-rw-r--r-- | server/controllers/api/users/me.ts | 4 | ||||
-rw-r--r-- | server/helpers/custom-validators/users.ts | 5 | ||||
-rw-r--r-- | server/initializers/checker.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 4 | ||||
-rw-r--r-- | server/initializers/installer.ts | 3 | ||||
-rw-r--r-- | server/initializers/migrations/0260-upload_quota_daily.ts | 23 | ||||
-rw-r--r-- | server/middlewares/validators/users.ts | 5 | ||||
-rw-r--r-- | server/models/account/user.ts | 68 | ||||
-rw-r--r-- | server/tests/api/check-params/config.ts | 3 | ||||
-rw-r--r-- | server/tests/api/check-params/users.ts | 56 | ||||
-rw-r--r-- | server/tests/api/server/config.ts | 5 | ||||
-rw-r--r-- | server/tests/utils/server/config.ts | 3 | ||||
-rw-r--r-- | server/tests/utils/users/users.ts | 6 |
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 | ||
18 | function isUserVideoQuotaDailyValid (value: string) { | ||
19 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA_DAILY) | ||
20 | } | ||
21 | |||
18 | function isUserUsernameValid (value: string) { | 22 | function 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { CONSTRAINTS_FIELDS } from '../constants' | ||
3 | |||
4 | async 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 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | 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 { | |||
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' |
17 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 18 | import { isVideoExist } from '../../helpers/custom-validators/videos' |
18 | import { logger } from '../../helpers/logger' | 19 | import { 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' |
32 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 33 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
33 | import { OAuthTokenModel } from '../oauth/oauth-token' | 34 | import { 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({ |