Browse Source

Add total video file size column in users list

Chocobozzz 3 months ago
parent
commit
0648d57870

+ 5 - 0
client/src/app/+admin/overview/users/user-list/user-list.component.html

@@ -68,6 +68,7 @@
       <th scope="col" *ngIf="isSelected('email')">{{ getColumn('email').label }}</th>
       <th scope="col" *ngIf="isSelected('quota')" style="width: 160px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
       <th scope="col" *ngIf="isSelected('quotaDaily')" style="width: 160px;">{{ getColumn('quotaDaily').label }}</th>
+      <th scope="col" *ngIf="isSelected('totalVideoFileSize')" style="width: 100px;">{{ getColumn('totalVideoFileSize').label }}</th>
       <th scope="col" *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn >{{ getColumn('pluginAuth').label }}</th>
       <th scope="col" *ngIf="isSelected('createdAt')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
       <th scope="col" *ngIf="isSelected('lastLoginDate')" style="width: 150px;" [ngbTooltip]="sortTooltip" container="body" pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
@@ -140,6 +141,10 @@
         </div>
       </td>
 
+      <td *ngIf="isSelected('totalVideoFileSize')">
+        {{ user.totalVideoFileSize | bytes }}
+      </td>
+
       <td *ngIf="isSelected('pluginAuth')">
         <span *ngIf="user.pluginAuth" [ngbTooltip]="user.pluginAuth">{{ user.pluginAuth }}</span>
       </td>

+ 2 - 1
client/src/app/+admin/overview/users/user-list/user-list.component.ts

@@ -131,6 +131,7 @@ export class UserListComponent extends RestTable <User> 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 <User> implements OnInit {
     }
 
     // Default behaviour
-    this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'createdAt', 'lastLoginDate' ]
+    this.selectedColumns = [ 'username', 'role', 'email', 'quota', 'totalVideoFileSize', 'createdAt', 'lastLoginDate' ]
     return
   }
 

+ 2 - 0
packages/models/src/users/user.model.ts

@@ -37,6 +37,8 @@ export interface User {
   videoQuotaUsed?: number
   videoQuotaUsedDaily?: number
 
+  totalVideoFileSize?: number
+
   videosCount?: number
 
   abusesCount?: number

+ 4 - 0
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 () {

+ 1 - 1
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
 

+ 2 - 2
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: {

+ 40 - 8
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<Partial<AttributesOnly<UserModel>>> {
 
     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<Partial<AttributesOnly<UserModel>>> {
     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<Partial<AttributesOnly<UserModel>>> {
   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<Partial<AttributesOnly<UserModel>>> {
       '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<Partial<AttributesOnly<UserModel>>> {
   }) {
     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<Partial<AttributesOnly<UserModel>>> {
     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<Partial<AttributesOnly<UserModel>>> {
       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