aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/lib/live/live-manager.ts2
-rw-r--r--server/lib/user.ts5
-rw-r--r--server/models/user/user.ts61
-rw-r--r--server/tests/api/live/live-constraints.ts25
4 files changed, 67 insertions, 26 deletions
diff --git a/server/lib/live/live-manager.ts b/server/lib/live/live-manager.ts
index df2804a0e..e04ae9fef 100644
--- a/server/lib/live/live-manager.ts
+++ b/server/lib/live/live-manager.ts
@@ -331,6 +331,8 @@ class LiveManager {
331 muxingSession.on('after-cleanup', ({ videoId }) => { 331 muxingSession.on('after-cleanup', ({ videoId }) => {
332 this.muxingSessions.delete(sessionId) 332 this.muxingSessions.delete(sessionId)
333 333
334 LiveQuotaStore.Instance.removeLive(user.id, videoLive.id)
335
334 muxingSession.destroy() 336 muxingSession.destroy()
335 337
336 return this.onAfterMuxingCleanup({ videoId, liveSession }) 338 return this.onAfterMuxingCleanup({ videoId, liveSession })
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 310a3c30c..586fd0f12 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -173,7 +173,8 @@ async function getOriginalVideoFileTotalFromUser (user: MUserId) {
173 // Don't use sequelize because we need to use a sub query 173 // Don't use sequelize because we need to use a sub query
174 const query = UserModel.generateUserQuotaBaseSQL({ 174 const query = UserModel.generateUserQuotaBaseSQL({
175 withSelect: true, 175 withSelect: true,
176 whereUserId: '$userId' 176 whereUserId: '$userId',
177 daily: false
177 }) 178 })
178 179
179 const base = await UserModel.getTotalRawQuery(query, user.id) 180 const base = await UserModel.getTotalRawQuery(query, user.id)
@@ -187,7 +188,7 @@ async function getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
187 const query = UserModel.generateUserQuotaBaseSQL({ 188 const query = UserModel.generateUserQuotaBaseSQL({
188 withSelect: true, 189 withSelect: true,
189 whereUserId: '$userId', 190 whereUserId: '$userId',
190 where: '"video"."createdAt" > now() - interval \'24 hours\'' 191 daily: true
191 }) 192 })
192 193
193 const base = await UserModel.getTotalRawQuery(query, user.id) 194 const base = await UserModel.getTotalRawQuery(query, user.id)
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index bcf56dfa1..85720abfb 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -72,10 +72,13 @@ import { VideoImportModel } from '../video/video-import'
72import { VideoLiveModel } from '../video/video-live' 72import { VideoLiveModel } from '../video/video-live'
73import { VideoPlaylistModel } from '../video/video-playlist' 73import { VideoPlaylistModel } from '../video/video-playlist'
74import { UserNotificationSettingModel } from './user-notification-setting' 74import { UserNotificationSettingModel } from './user-notification-setting'
75import { LiveQuotaStore } from '@server/lib/live'
76import { logger } from '@server/helpers/logger'
75 77
76enum ScopeNames { 78enum ScopeNames {
77 FOR_ME_API = 'FOR_ME_API', 79 FOR_ME_API = 'FOR_ME_API',
78 WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS', 80 WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS',
81 WITH_QUOTA = 'WITH_QUOTA',
79 WITH_STATS = 'WITH_STATS' 82 WITH_STATS = 'WITH_STATS'
80} 83}
81 84
@@ -153,7 +156,7 @@ enum ScopeNames {
153 } 156 }
154 ] 157 ]
155 }, 158 },
156 [ScopeNames.WITH_STATS]: { 159 [ScopeNames.WITH_QUOTA]: {
157 attributes: { 160 attributes: {
158 include: [ 161 include: [
159 [ 162 [
@@ -161,7 +164,8 @@ enum ScopeNames {
161 '(' + 164 '(' +
162 UserModel.generateUserQuotaBaseSQL({ 165 UserModel.generateUserQuotaBaseSQL({
163 withSelect: false, 166 withSelect: false,
164 whereUserId: '"UserModel"."id"' 167 whereUserId: '"UserModel"."id"',
168 daily: false
165 }) + 169 }) +
166 ')' 170 ')'
167 ), 171 ),
@@ -170,6 +174,24 @@ enum ScopeNames {
170 [ 174 [
171 literal( 175 literal(
172 '(' + 176 '(' +
177 UserModel.generateUserQuotaBaseSQL({
178 withSelect: false,
179 whereUserId: '"UserModel"."id"',
180 daily: true
181 }) +
182 ')'
183 ),
184 'videoQuotaUsedDaily'
185 ]
186 ]
187 }
188 },
189 [ScopeNames.WITH_STATS]: {
190 attributes: {
191 include: [
192 [
193 literal(
194 '(' +
173 'SELECT COUNT("video"."id") ' + 195 'SELECT COUNT("video"."id") ' +
174 'FROM "video" ' + 196 'FROM "video" ' +
175 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 197 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
@@ -474,21 +496,6 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
474 } 496 }
475 497
476 const query: FindOptions = { 498 const query: FindOptions = {
477 attributes: {
478 include: [
479 [
480 literal(
481 '(' +
482 UserModel.generateUserQuotaBaseSQL({
483 withSelect: false,
484 whereUserId: '"UserModel"."id"'
485 }) +
486 ')'
487 ),
488 'videoQuotaUsed'
489 ]
490 ]
491 },
492 offset: start, 499 offset: start,
493 limit: count, 500 limit: count,
494 order: getSort(sort), 501 order: getSort(sort),
@@ -497,7 +504,7 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
497 504
498 return Promise.all([ 505 return Promise.all([
499 UserModel.unscoped().count(query), 506 UserModel.unscoped().count(query),
500 UserModel.findAll(query) 507 UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query)
501 ]).then(([ total, data ]) => ({ total, data })) 508 ]).then(([ total, data ]) => ({ total, data }))
502 } 509 }
503 510
@@ -579,7 +586,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
579 ScopeNames.WITH_VIDEOCHANNELS 586 ScopeNames.WITH_VIDEOCHANNELS
580 ] 587 ]
581 588
582 if (withStats) scopes.push(ScopeNames.WITH_STATS) 589 if (withStats) {
590 scopes.push(ScopeNames.WITH_QUOTA)
591 scopes.push(ScopeNames.WITH_STATS)
592 }
583 593
584 return UserModel.scope(scopes).findByPk(id) 594 return UserModel.scope(scopes).findByPk(id)
585 } 595 }
@@ -760,10 +770,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
760 static generateUserQuotaBaseSQL (options: { 770 static generateUserQuotaBaseSQL (options: {
761 whereUserId: '$userId' | '"UserModel"."id"' 771 whereUserId: '$userId' | '"UserModel"."id"'
762 withSelect: boolean 772 withSelect: boolean
763 where?: string 773 daily: boolean
764 }) { 774 }) {
765 const andWhere = options.where 775 const andWhere = options.daily === true
766 ? 'AND ' + options.where 776 ? 'AND "video"."createdAt" > now() - interval \'24 hours\''
767 : '' 777 : ''
768 778
769 const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 779 const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
@@ -904,12 +914,15 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
904 914
905 videoQuota: this.videoQuota, 915 videoQuota: this.videoQuota,
906 videoQuotaDaily: this.videoQuotaDaily, 916 videoQuotaDaily: this.videoQuotaDaily,
917
907 videoQuotaUsed: videoQuotaUsed !== undefined 918 videoQuotaUsed: videoQuotaUsed !== undefined
908 ? parseInt(videoQuotaUsed + '', 10) 919 ? parseInt(videoQuotaUsed + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
909 : undefined, 920 : undefined,
921
910 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined 922 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
911 ? parseInt(videoQuotaUsedDaily + '', 10) 923 ? parseInt(videoQuotaUsedDaily + '', 10) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id)
912 : undefined, 924 : undefined,
925
913 videosCount: videosCount !== undefined 926 videosCount: videosCount !== undefined
914 ? parseInt(videosCount + '', 10) 927 ? parseInt(videosCount + '', 10)
915 : undefined, 928 : undefined,
diff --git a/server/tests/api/live/live-constraints.ts b/server/tests/api/live/live-constraints.ts
index cab76f425..cf43c262a 100644
--- a/server/tests/api/live/live-constraints.ts
+++ b/server/tests/api/live/live-constraints.ts
@@ -12,6 +12,7 @@ import {
12 PeerTubeServer, 12 PeerTubeServer,
13 setAccessTokensToServers, 13 setAccessTokensToServers,
14 setDefaultVideoChannel, 14 setDefaultVideoChannel,
15 stopFfmpeg,
15 waitJobs, 16 waitJobs,
16 waitUntilLiveReplacedByReplayOnAllServers, 17 waitUntilLiveReplacedByReplayOnAllServers,
17 waitUntilLiveWaitingOnAllServers 18 waitUntilLiveWaitingOnAllServers
@@ -169,6 +170,30 @@ describe('Test live constraints', function () {
169 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false }) 170 await servers[0].live.runAndTestStreamError({ token: userAccessToken, videoId: userVideoLiveoId, shouldHaveError: false })
170 }) 171 })
171 172
173 it('Should have the same quota in admin and as a user', async function () {
174 this.timeout(120000)
175
176 const userVideoLiveoId = await createLiveWrapper({ replay: true, permanent: false })
177 const ffmpegCommand = await servers[0].live.sendRTMPStreamInVideo({ token: userAccessToken, videoId: userVideoLiveoId })
178
179 await servers[0].live.waitUntilPublished({ videoId: userVideoLiveoId })
180
181 await wait(3000)
182
183 const quotaUser = await servers[0].users.getMyQuotaUsed({ token: userAccessToken })
184
185 const { data } = await servers[0].users.list()
186 const quotaAdmin = data.find(u => u.username === 'user1')
187
188 expect(quotaUser.videoQuotaUsed).to.equal(quotaAdmin.videoQuotaUsed)
189 expect(quotaUser.videoQuotaUsedDaily).to.equal(quotaAdmin.videoQuotaUsedDaily)
190
191 expect(quotaUser.videoQuotaUsed).to.be.above(10)
192 expect(quotaUser.videoQuotaUsedDaily).to.be.above(10)
193
194 await stopFfmpeg(ffmpegCommand)
195 })
196
172 it('Should have max duration limit', async function () { 197 it('Should have max duration limit', async function () {
173 this.timeout(60000) 198 this.timeout(60000)
174 199