From 0648d578705c795db22b7e614eed43ccb0e03e69 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 21 Feb 2024 15:28:38 +0100 Subject: [PATCH] Add total video file size column in users list --- .../users/user-list/user-list.component.html | 5 ++ .../users/user-list/user-list.component.ts | 3 +- packages/models/src/users/user.model.ts | 2 + packages/tests/src/api/users/users.ts | 4 ++ server/core/lib/live/live-quota-store.ts | 2 +- server/core/lib/user.ts | 4 +- server/core/models/user/user.ts | 48 +++++++++++++++---- 7 files changed, 56 insertions(+), 12 deletions(-) diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.html b/client/src/app/+admin/overview/users/user-list/user-list.component.html index 142057141..2954a681d 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.html +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.html @@ -68,6 +68,7 @@ {{ getColumn('email').label }} {{ getColumn('quota').label }} {{ getColumn('quotaDaily').label }} + {{ getColumn('totalVideoFileSize').label }} {{ getColumn('pluginAuth').label }} {{ getColumn('createdAt').label }} {{ getColumn('lastLoginDate').label }} @@ -140,6 +141,10 @@ + + {{ user.totalVideoFileSize | bytes }} + + {{ user.pluginAuth }} diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index a5a1552da..36849011d 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts @@ -131,6 +131,7 @@ export class UserListComponent extends RestTable implements OnInit { { id: 'role', label: $localize`Role` }, { id: 'email', label: $localize`Email` }, { id: 'quota', label: $localize`Video quota` }, + { id: 'totalVideoFileSize', label: $localize`Total size` }, { id: 'createdAt', label: $localize`Created` }, { id: 'lastLoginDate', label: $localize`Last login` }, @@ -154,7 +155,7 @@ export class UserListComponent extends RestTable implements OnInit { } // Default behaviour - this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ] + this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'totalVideoFileSize', 'createdAt', 'lastLoginDate' ] return } diff --git a/packages/models/src/users/user.model.ts b/packages/models/src/users/user.model.ts index 57b4c1aab..ea03e08ca 100644 --- a/packages/models/src/users/user.model.ts +++ b/packages/models/src/users/user.model.ts @@ -37,6 +37,8 @@ export interface User { videoQuotaUsed?: number videoQuotaUsedDaily?: number + totalVideoFileSize?: number + videosCount?: number abusesCount?: number diff --git a/packages/tests/src/api/users/users.ts b/packages/tests/src/api/users/users.ts index a0090a463..cf26f444c 100644 --- a/packages/tests/src/api/users/users.ts +++ b/packages/tests/src/api/users/users.ts @@ -90,6 +90,8 @@ describe('Test users', function () { expect(user.email).to.equal('user_1@example.com') expect(user.nsfwPolicy).to.equal('display') + expect(user.totalVideoFileSize).to.equal(0) + const rootUser = data[1] expect(rootUser.username).to.equal('root') expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') @@ -484,6 +486,7 @@ describe('Test users', function () { expect(user.abusesCount).to.equal(0) expect(user.abusesCreatedCount).to.equal(0) expect(user.abusesAcceptedCount).to.equal(0) + expect(user.totalVideoFileSize).to.equal(0) }) it('Should report correct videos count', async function () { @@ -495,6 +498,7 @@ describe('Test users', function () { const user = await server.users.get({ userId: user17Id, withStats: true }) expect(user.videosCount).to.equal(1) + expect(user.totalVideoFileSize).to.not.equal(0) }) it('Should report correct video comments for user', async function () { diff --git a/server/core/lib/live/live-quota-store.ts b/server/core/lib/live/live-quota-store.ts index 44539faaa..464bc6b03 100644 --- a/server/core/lib/live/live-quota-store.ts +++ b/server/core/lib/live/live-quota-store.ts @@ -31,7 +31,7 @@ class LiveQuotaStore { live.size += size } - getLiveQuotaOf (userId: number) { + getLiveQuotaOfUser (userId: number) { const currentLives = this.livesPerUser.get(userId) if (!currentLives) return 0 diff --git a/server/core/lib/user.ts b/server/core/lib/user.ts index 68a64d4fe..3c3b0de0f 100644 --- a/server/core/lib/user.ts +++ b/server/core/lib/user.ts @@ -198,14 +198,14 @@ async function sendVerifyRegistrationEmail (registration: MRegistration) { async function getOriginalVideoFileTotalFromUser (user: MUserId) { const base = await UserModel.getUserQuota({ userId: user.id, daily: false }) - return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) + return base + LiveQuotaStore.Instance.getLiveQuotaOfUser(user.id) } // Returns cumulative size of all video files uploaded in the last 24 hours. async function getOriginalVideoFileTotalDailyFromUser (user: MUserId) { const base = await UserModel.getUserQuota({ userId: user.id, daily: true }) - return base + LiveQuotaStore.Instance.getLiveQuotaOf(user.id) + return base + LiveQuotaStore.Instance.getLiveQuotaOfUser(user.id) } async function isUserQuotaValid (options: { diff --git a/server/core/models/user/user.ts b/server/core/models/user/user.ts index 8a7c1cf6b..8a1e201c4 100644 --- a/server/core/models/user/user.ts +++ b/server/core/models/user/user.ts @@ -83,6 +83,7 @@ enum ScopeNames { FOR_ME_API = 'FOR_ME_API', WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS', WITH_QUOTA = 'WITH_QUOTA', + WITH_TOTAL_FILE_SIZES = 'WITH_TOTAL_FILE_SIZES', WITH_STATS = 'WITH_STATS' } @@ -168,7 +169,8 @@ enum ScopeNames { '(' + UserModel.generateUserQuotaBaseSQL({ whereUserId: '"UserModel"."id"', - daily: false + daily: false, + onlyMaxResolution: true }) + ')' ), @@ -179,7 +181,8 @@ enum ScopeNames { '(' + UserModel.generateUserQuotaBaseSQL({ whereUserId: '"UserModel"."id"', - daily: true + daily: true, + onlyMaxResolution: true }) + ')' ), @@ -188,6 +191,24 @@ enum ScopeNames { ] } }, + [ScopeNames.WITH_TOTAL_FILE_SIZES]: { + attributes: { + include: [ + [ + literal( + '(' + + UserModel.generateUserQuotaBaseSQL({ + whereUserId: '"UserModel"."id"', + daily: false, + onlyMaxResolution: false + }) + + ')' + ), + 'totalVideoFileSize' + ] + ] + } + }, [ScopeNames.WITH_STATS]: { attributes: { include: [ @@ -521,7 +542,7 @@ export class UserModel extends Model>> { return Promise.all([ UserModel.unscoped().count(query), - UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query) + UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA, ScopeNames.WITH_TOTAL_FILE_SIZES ]).findAll(query) ]).then(([ total, data ]) => ({ total, data })) } @@ -607,6 +628,7 @@ export class UserModel extends Model>> { if (withStats) { scopes.push(ScopeNames.WITH_QUOTA) scopes.push(ScopeNames.WITH_STATS) + scopes.push(ScopeNames.WITH_TOTAL_FILE_SIZES) } return UserModel.scope(scopes).findByPk(id) @@ -805,8 +827,9 @@ export class UserModel extends Model>> { static generateUserQuotaBaseSQL (options: { daily: boolean whereUserId: '$userId' | '"UserModel"."id"' + onlyMaxResolution: boolean }) { - const { daily, whereUserId } = options + const { daily, whereUserId, onlyMaxResolution } = options const andWhere = daily === true ? 'AND "video"."createdAt" > now() - interval \'24 hours\'' @@ -825,9 +848,13 @@ export class UserModel extends Model>> { 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" AND "video"."isLive" IS FALSE ' + videoChannelJoin + const sizeSelect = onlyMaxResolution + ? 'MAX("t1"."size")' + : 'SUM("t1"."size")' + return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + 'FROM (' + - `SELECT MAX("t1"."size") AS "size" FROM (${webVideoFiles} UNION ${hlsFiles}) t1 ` + + `SELECT ${sizeSelect} AS "size" FROM (${webVideoFiles} UNION ${hlsFiles}) t1 ` + 'GROUP BY "t1"."videoId"' + ') t2' } @@ -838,7 +865,7 @@ export class UserModel extends Model>> { }) { const { daily, userId } = options - const sql = this.generateUserQuotaBaseSQL({ daily, whereUserId: '$userId' }) + const sql = this.generateUserQuotaBaseSQL({ daily, whereUserId: '$userId', onlyMaxResolution: true }) const queryOptions = { bind: { userId }, @@ -914,6 +941,7 @@ export class UserModel extends Model>> { const [ abusesCount, abusesAcceptedCount ] = (this.get('abusesCount') as string || ':').split(':') const abusesCreatedCount = this.get('abusesCreatedCount') const videoCommentsCount = this.get('videoCommentsCount') + const totalVideoFileSize = this.get('totalVideoFileSize') const json: User = { id: this.id, @@ -943,12 +971,16 @@ export class UserModel extends Model>> { videoQuota: this.videoQuota, videoQuotaDaily: this.videoQuotaDaily, + totalVideoFileSize: totalVideoFileSize !== undefined + ? forceNumber(totalVideoFileSize) + : undefined, + videoQuotaUsed: videoQuotaUsed !== undefined - ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) + ? forceNumber(videoQuotaUsed) + LiveQuotaStore.Instance.getLiveQuotaOfUser(this.id) : undefined, videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined - ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOf(this.id) + ? forceNumber(videoQuotaUsedDaily) + LiveQuotaStore.Instance.getLiveQuotaOfUser(this.id) : undefined, videosCount: videosCount !== undefined