]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Add stats route
authorChocobozzz <me@florianbigard.com>
Wed, 28 Feb 2018 17:04:46 +0000 (18:04 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 28 Feb 2018 17:04:55 +0000 (18:04 +0100)
26 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/+admin/config/shared/config.service.ts
client/src/app/+admin/jobs/shared/job.service.ts
client/src/app/core/server/server.service.ts
server/controllers/activitypub/inbox.ts
server/controllers/api/config.ts
server/controllers/api/server/index.ts
server/controllers/api/server/stats.ts [new file with mode: 0644]
server/models/account/user.ts
server/models/activitypub/actor-follow.ts
server/models/video/video-comment.ts
server/models/video/video.ts
server/tests/api/check-params/config.ts
server/tests/api/index-fast.ts
server/tests/api/index-slow.ts
server/tests/api/server/config.ts
server/tests/api/server/stats.ts [new file with mode: 0644]
server/tests/utils/server/config.ts
server/tests/utils/server/stats.ts [new file with mode: 0644]
shared/models/index.ts
shared/models/server/about.model.ts [moved from shared/models/config/about.model.ts with 100% similarity]
shared/models/server/custom-config.model.ts [moved from shared/models/config/custom-config.model.ts with 100% similarity]
shared/models/server/customization.model.ts [moved from shared/models/config/customization.model.ts with 100% similarity]
shared/models/server/job.model.ts [moved from shared/models/job.model.ts with 100% similarity]
shared/models/server/server-config.model.ts [moved from shared/models/config/server-config.model.ts with 100% similarity]
shared/models/server/server-stats.model.ts [new file with mode: 0644]

index ccec89a8ed9851ac4d89a57748fdd37fdef7eb7a..cf93b40602eb6e9d25b55451d3dddf420344c5b9 100644 (file)
@@ -13,7 +13,7 @@ import {
   TRANSCODING_THREADS
 } from '@app/shared/forms/form-validators/custom-config'
 import { NotificationsService } from 'angular2-notifications'
   TRANSCODING_THREADS
 } from '@app/shared/forms/form-validators/custom-config'
 import { NotificationsService } from 'angular2-notifications'
-import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model'
+import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model'
 
 @Component({
   selector: 'my-edit-custom-config',
 
 @Component({
   selector: 'my-edit-custom-config',
index 13f1f6cd2b76c1089c13ea237c9efd095df5bbf7..2a39c7155854d101b8c5cfe2d068bcc42d96ad44 100644 (file)
@@ -1,6 +1,6 @@
 import { HttpClient } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { HttpClient } from '@angular/common/http'
 import { Injectable } from '@angular/core'
-import { CustomConfig } from '../../../../../../shared/models/config/custom-config.model'
+import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model'
 import { environment } from '../../../../environments/environment'
 import { RestExtractor, RestService } from '../../../shared'
 
 import { environment } from '../../../../environments/environment'
 import { RestExtractor, RestService } from '../../../shared'
 
index 1c0915b5ec6b21a3310b6eb45e7c61530df78acb..98f29b7427c2b7c352447011235649b06a911608 100644 (file)
@@ -6,7 +6,7 @@ import 'rxjs/add/operator/map'
 import { Observable } from 'rxjs/Observable'
 import { ResultList } from '../../../../../../shared'
 import { JobState } from '../../../../../../shared/models'
 import { Observable } from 'rxjs/Observable'
 import { ResultList } from '../../../../../../shared'
 import { JobState } from '../../../../../../shared/models'
-import { Job } from '../../../../../../shared/models/job.model'
+import { Job } from '../../../../../../shared/models/server/job.model'
 import { environment } from '../../../../environments/environment'
 import { RestExtractor, RestPagination, RestService } from '../../../shared'
 
 import { environment } from '../../../../environments/environment'
 import { RestExtractor, RestPagination, RestService } from '../../../shared'
 
index 3c94f09c6d4890a0fb27bce36c6dfcef64b66f49..984738948295ff63e9c0fce7fc75cfce9e09025a 100644 (file)
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
 import 'rxjs/add/operator/do'
 import { ReplaySubject } from 'rxjs/ReplaySubject'
 import { ServerConfig } from '../../../../../shared'
 import 'rxjs/add/operator/do'
 import { ReplaySubject } from 'rxjs/ReplaySubject'
 import { ServerConfig } from '../../../../../shared'
-import { About } from '../../../../../shared/models/config/about.model'
+import { About } from '../../../../../shared/models/server/about.model'
 import { environment } from '../../../environments/environment'
 
 @Injectable()
 import { environment } from '../../../environments/environment'
 
 @Injectable()
index bd0d7a9c8bb6ac4b0f27114664eaeba227afabc6..0354d783369615d828a1ed958998afaca1d5f5f6 100644 (file)
@@ -56,6 +56,8 @@ async function inboxController (req: express.Request, res: express.Response, nex
     specificActor = res.locals.videoChannel
   }
 
     specificActor = res.locals.videoChannel
   }
 
+  logger.info('Receiving inbox requests for %d activities by %s.', activities.length, res.locals.signature.actor)
+
   await processActivities(activities, res.locals.signature.actor, specificActor)
 
   res.status(204).end()
   await processActivities(activities, res.locals.signature.actor, specificActor)
 
   res.status(204).end()
index 7ef0c19e3943462a1b718c2800de8e7688cd5995..42712581035512e049f11a441c5d337dbd1a5685 100644 (file)
@@ -1,8 +1,8 @@
 import * as express from 'express'
 import { omit } from 'lodash'
 import { ServerConfig, UserRight } from '../../../shared'
 import * as express from 'express'
 import { omit } from 'lodash'
 import { ServerConfig, UserRight } from '../../../shared'
-import { About } from '../../../shared/models/config/about.model'
-import { CustomConfig } from '../../../shared/models/config/custom-config.model'
+import { About } from '../../../shared/models/server/about.model'
+import { CustomConfig } from '../../../shared/models/server/custom-config.model'
 import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
 import { isSignupAllowed } from '../../helpers/utils'
 import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
 import { unlinkPromise, writeFilePromise } from '../../helpers/core-utils'
 import { isSignupAllowed } from '../../helpers/utils'
 import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers'
index 8dc1a0031488600748a66bec4973abcca887f31f..850a52cdb045b1ad1c68a9f75dc98c023cdd6095 100644 (file)
@@ -1,9 +1,11 @@
 import * as express from 'express'
 import { serverFollowsRouter } from './follows'
 import * as express from 'express'
 import { serverFollowsRouter } from './follows'
+import { statsRouter } from './stats'
 
 const serverRouter = express.Router()
 
 serverRouter.use('/', serverFollowsRouter)
 
 const serverRouter = express.Router()
 
 serverRouter.use('/', serverFollowsRouter)
+serverRouter.use('/', statsRouter)
 
 // ---------------------------------------------------------------------------
 
 
 // ---------------------------------------------------------------------------
 
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
new file mode 100644 (file)
index 0000000..6f4fe93
--- /dev/null
@@ -0,0 +1,39 @@
+import * as express from 'express'
+import { ServerStats } from '../../../../shared/models/server/server-stats.model'
+import { asyncMiddleware } from '../../../middlewares'
+import { UserModel } from '../../../models/account/user'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+import { VideoModel } from '../../../models/video/video'
+import { VideoCommentModel } from '../../../models/video/video-comment'
+
+const statsRouter = express.Router()
+
+statsRouter.get('/stats',
+  asyncMiddleware(getStats)
+)
+
+async function getStats (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
+  const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
+  const { totalUsers } = await UserModel.getStats()
+  const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
+
+  const data: ServerStats = {
+    totalLocalVideos,
+    totalLocalVideoViews,
+    totalVideos,
+    totalLocalVideoComments,
+    totalVideoComments,
+    totalUsers,
+    totalInstanceFollowers,
+    totalInstanceFollowing
+  }
+
+  return res.json(data).end()
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  statsRouter
+}
index 74cf0f4a8f7b0217bdff9dadaebf9fb5cf28c0e8..afa9d7be09519953571d351287ac82328eb5a748 100644 (file)
@@ -13,6 +13,7 @@ import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
 import { OAuthTokenModel } from '../oauth/oauth-token'
 import { getSort, throwIfNotValid } from '../utils'
 import { VideoChannelModel } from '../video/video-channel'
 import { OAuthTokenModel } from '../oauth/oauth-token'
 import { getSort, throwIfNotValid } from '../utils'
 import { VideoChannelModel } from '../video/video-channel'
+import { VideoCommentModel } from '../video/video-comment'
 import { AccountModel } from './account'
 
 @DefaultScope({
 import { AccountModel } from './account'
 
 @DefaultScope({
@@ -226,6 +227,14 @@ export class UserModel extends Model<UserModel> {
       })
   }
 
       })
   }
 
+  static async getStats () {
+    const totalUsers = await UserModel.count()
+
+    return {
+      totalUsers
+    }
+  }
+
   hasRight (right: UserRight) {
     return hasUserRight(this.role, right)
   }
   hasRight (right: UserRight) {
     return hasUserRight(this.role, right)
   }
index 8260904a156ceeb9857f7d9aa40df8a523be1c0c..3c11d1b6722d5ba58974e04506e7bcba7385fee8 100644 (file)
@@ -8,6 +8,7 @@ import {
 import { FollowState } from '../../../shared/models/actors'
 import { AccountFollow } from '../../../shared/models/actors/follow.model'
 import { logger } from '../../helpers/logger'
 import { FollowState } from '../../../shared/models/actors'
 import { AccountFollow } from '../../../shared/models/actors/follow.model'
 import { logger } from '../../helpers/logger'
+import { getServerActor } from '../../helpers/utils'
 import { ACTOR_FOLLOW_SCORE } from '../../initializers'
 import { FOLLOW_STATES } from '../../initializers/constants'
 import { ServerModel } from '../server/server'
 import { ACTOR_FOLLOW_SCORE } from '../../initializers'
 import { FOLLOW_STATES } from '../../initializers/constants'
 import { ServerModel } from '../server/server'
@@ -182,34 +183,6 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     return ActorFollowModel.findOne(query)
   }
 
     return ActorFollowModel.findOne(query)
   }
 
-  static loadByFollowerInbox (url: string, t?: Sequelize.Transaction) {
-    const query = {
-      where: {
-        state: 'accepted'
-      },
-      include: [
-        {
-          model: ActorModel,
-          required: true,
-          as: 'ActorFollower',
-          where: {
-            [Sequelize.Op.or]: [
-              {
-                inboxUrl: url
-              },
-              {
-                sharedInboxUrl: url
-              }
-            ]
-          }
-        }
-      ],
-      transaction: t
-    } as any // FIXME: typings does not work
-
-    return ActorFollowModel.findOne(query)
-  }
-
   static listFollowingForApi (id: number, start: number, count: number, sort: string) {
     const query = {
       distinct: true,
   static listFollowingForApi (id: number, start: number, count: number, sort: string) {
     const query = {
       distinct: true,
@@ -296,6 +269,27 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count)
   }
 
     return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count)
   }
 
+  static async getStats () {
+    const serverActor = await getServerActor()
+
+    const totalInstanceFollowing = await ActorFollowModel.count({
+      where: {
+        actorId: serverActor.id
+      }
+    })
+
+    const totalInstanceFollowers = await ActorFollowModel.count({
+      where: {
+        targetActorId: serverActor.id
+      }
+    })
+
+    return {
+      totalInstanceFollowing,
+      totalInstanceFollowers
+    }
+  }
+
   private static async createListAcceptedFollowForApiQuery (
     type: 'followers' | 'following',
     actorIds: number[],
   private static async createListAcceptedFollowForApiQuery (
     type: 'followers' | 'following',
     actorIds: number[],
index 47e3211a3f35304f87a09d04e000d9a7fb79a0e5..bf8da924d5555ee5d06911941100f5ee47881c7d 100644 (file)
@@ -326,6 +326,32 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
       .findAll(query)
   }
 
       .findAll(query)
   }
 
+  static async getStats () {
+    const totalLocalVideoComments = await VideoCommentModel.count({
+      include: [
+        {
+          model: AccountModel,
+          required: true,
+          include: [
+            {
+              model: ActorModel,
+              required: true,
+              where: {
+                serverId: null
+              }
+            }
+          ]
+        }
+      ]
+    })
+    const totalVideoComments = await VideoCommentModel.count()
+
+    return {
+      totalLocalVideoComments,
+      totalVideoComments
+    }
+  }
+
   getThreadId (): number {
     return this.originCommentId || this.id
   }
   getThreadId (): number {
     return this.originCommentId || this.id
   }
index 80ca513bf8263bba59b311277faef0f5e4015133..f6a21814cf362768911f49c16eaa5cd794abb983 100644 (file)
@@ -761,6 +761,29 @@ export class VideoModel extends Model<VideoModel> {
       .findOne(options)
   }
 
       .findOne(options)
   }
 
+  static async getStats () {
+    const totalLocalVideos = await VideoModel.count({
+      where: {
+        remote: false
+      }
+    })
+    const totalVideos = await VideoModel.count()
+
+    let totalLocalVideoViews = await VideoModel.sum('views', {
+      where: {
+        remote: false
+      }
+    })
+    // Sequelize could return null...
+    if (!totalLocalVideoViews) totalLocalVideoViews = 0
+
+    return {
+      totalLocalVideos,
+      totalLocalVideoViews,
+      totalVideos
+    }
+  }
+
   getOriginalFile () {
     if (Array.isArray(this.VideoFiles) === false) return undefined
 
   getOriginalFile () {
     if (Array.isArray(this.VideoFiles) === false) return undefined
 
index c1c0a3f59cc426d953555fd6b339965a29995127..a66e51a6a53f502cc203832b2fee6800e4a87016 100644 (file)
@@ -2,7 +2,7 @@
 
 import { omit } from 'lodash'
 import 'mocha'
 
 import { omit } from 'lodash'
 import 'mocha'
-import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
+import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
 
 import {
   createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
 
 import {
   createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
index 9f52310dd0dd89ac39b22348e4f8005e336be5cb..464dcb5e066b18e44d293c69ea63a06a3a2fa16d 100644 (file)
@@ -1,5 +1,5 @@
 // Order of the tests we want to execute
 // Order of the tests we want to execute
-import './server/config'
+import './server/stats'
 import './check-params'
 import './users/users'
 import './videos/single-server'
 import './check-params'
 import './users/users'
 import './videos/single-server'
@@ -10,3 +10,4 @@ import './videos/video-description'
 import './videos/video-privacy'
 import './videos/services'
 import './server/email'
 import './videos/video-privacy'
 import './videos/services'
 import './server/email'
+import './server/config'
index 0082bcb561bf1aa7785dbcc73918a478d924f8a0..cde5468564f23f9b5b9fd2028e881aafacb351c4 100644 (file)
@@ -1,5 +1,4 @@
 // Order of the tests we want to execute
 // Order of the tests we want to execute
-// import './multiple-servers'
 import './videos/video-transcoder'
 import './videos/multiple-servers'
 import './server/follows'
 import './videos/video-transcoder'
 import './videos/multiple-servers'
 import './server/follows'
index 048135a345f2bc82497e3a59d31fe347dd864d70..3d90580d8782b509963e97c04b64219d2cc9ca93 100644 (file)
@@ -2,8 +2,8 @@
 
 import 'mocha'
 import * as chai from 'chai'
 
 import 'mocha'
 import * as chai from 'chai'
-import { About } from '../../../../shared/models/config/about.model'
-import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
+import { About } from '../../../../shared/models/server/about.model'
+import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
 import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
 const expect = chai.expect
 
 import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
 const expect = chai.expect
 
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
new file mode 100644 (file)
index 0000000..71d54c0
--- /dev/null
@@ -0,0 +1,102 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { ServerStats } from '../../../../shared/models/server/server-stats.model'
+import {
+  createUser,
+  doubleFollow,
+  flushAndRunMultipleServers,
+  follow,
+  killallServers,
+  ServerInfo,
+  uploadVideo,
+  viewVideo,
+  wait
+} from '../../utils'
+import { flushTests, setAccessTokensToServers } from '../../utils/index'
+import { getStats } from '../../utils/server/stats'
+import { addVideoCommentThread } from '../../utils/videos/video-comments'
+
+const expect = chai.expect
+
+describe('Test stats', function () {
+  let servers: ServerInfo[] = []
+
+  before(async function () {
+    this.timeout(60000)
+
+    await flushTests()
+    servers = await flushAndRunMultipleServers(3)
+    await setAccessTokensToServers(servers)
+
+    await doubleFollow(servers[0], servers[1])
+
+    const user = {
+      username: 'user1',
+      password: 'super_password'
+    }
+    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+
+    const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, {})
+    const videoUUID = resVideo.body.video.uuid
+
+    await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'comment')
+
+    await viewVideo(servers[0].url, videoUUID)
+
+    await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken)
+    await wait(5000)
+  })
+
+  it('Should have the correct stats on instance 1', async function () {
+    const res = await getStats(servers[0].url)
+    const data: ServerStats = res.body
+
+    expect(data.totalLocalVideoComments).to.equal(1)
+    expect(data.totalLocalVideos).to.equal(1)
+    expect(data.totalLocalVideoViews).to.equal(1)
+    expect(data.totalUsers).to.equal(2)
+    expect(data.totalVideoComments).to.equal(1)
+    expect(data.totalVideos).to.equal(1)
+    expect(data.totalInstanceFollowers).to.equal(2)
+    expect(data.totalInstanceFollowing).to.equal(1)
+  })
+
+  it('Should have the correct stats on instance 2', async function () {
+    const res = await getStats(servers[1].url)
+    const data: ServerStats = res.body
+
+    expect(data.totalLocalVideoComments).to.equal(0)
+    expect(data.totalLocalVideos).to.equal(0)
+    expect(data.totalLocalVideoViews).to.equal(0)
+    expect(data.totalUsers).to.equal(1)
+    expect(data.totalVideoComments).to.equal(1)
+    expect(data.totalVideos).to.equal(1)
+    expect(data.totalInstanceFollowers).to.equal(1)
+    expect(data.totalInstanceFollowing).to.equal(1)
+  })
+
+  it('Should have the correct stats on instance 3', async function () {
+    const res = await getStats(servers[2].url)
+    const data: ServerStats = res.body
+
+    expect(data.totalLocalVideoComments).to.equal(0)
+    expect(data.totalLocalVideos).to.equal(0)
+    expect(data.totalLocalVideoViews).to.equal(0)
+    expect(data.totalUsers).to.equal(1)
+    expect(data.totalVideoComments).to.equal(1)
+    expect(data.totalVideos).to.equal(1)
+    expect(data.totalInstanceFollowing).to.equal(1)
+    expect(data.totalInstanceFollowers).to.equal(0)
+  })
+
+  after(async function () {
+    killallServers(servers)
+
+    // Keep the logs if the test failed
+    if (this['ok']) {
+      await flushTests()
+    }
+  })
+})
index e5411117a655d6f2c365b7c96fed67146cb3e04c..57f95a6033aa08376d2110a5d69f32849fc968e2 100644 (file)
@@ -1,5 +1,5 @@
 import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../'
 import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../'
-import { CustomConfig } from '../../../../shared/models/config/custom-config.model'
+import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
 
 function getConfig (url: string) {
   const path = '/api/v1/config'
 
 function getConfig (url: string) {
   const path = '/api/v1/config'
diff --git a/server/tests/utils/server/stats.ts b/server/tests/utils/server/stats.ts
new file mode 100644 (file)
index 0000000..9cdec6c
--- /dev/null
@@ -0,0 +1,17 @@
+import { makeGetRequest } from '../'
+
+function getStats (url: string) {
+  const path = '/api/v1/server/stats'
+
+  return makeGetRequest({
+    url,
+    path,
+    statusCodeExpected: 200
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  getStats
+}
index 1b877774c58949bf81f44628e8f4dce29b239e2c..ae3a44777967c624d78cbdf4bce867c537f6fba2 100644 (file)
@@ -2,7 +2,7 @@ export * from './actors'
 export * from './activitypub'
 export * from './users'
 export * from './videos'
 export * from './activitypub'
 export * from './users'
 export * from './videos'
-export * from './job.model'
+export * from './server/job.model'
 export * from './oauth-client-local.model'
 export * from './result-list.model'
 export * from './oauth-client-local.model'
 export * from './result-list.model'
-export * from './config/server-config.model'
+export * from './server/server-config.model'
diff --git a/shared/models/server/server-stats.model.ts b/shared/models/server/server-stats.model.ts
new file mode 100644 (file)
index 0000000..5c1bf34
--- /dev/null
@@ -0,0 +1,12 @@
+export interface ServerStats {
+  totalUsers: number
+  totalLocalVideos: number
+  totalLocalVideoViews: number
+  totalLocalVideoComments: number
+
+  totalVideos: number
+  totalVideoComments: number
+
+  totalInstanceFollowers: number
+  totalInstanceFollowing: number
+}