Răsfoiți Sursa

Add stats route

Chocobozzz 6 ani în urmă
părinte
comite
09cababd79

+ 1 - 1
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts

@@ -13,7 +13,7 @@ import {
   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',

+ 1 - 1
client/src/app/+admin/config/shared/config.service.ts

@@ -1,6 +1,6 @@
 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'
 

+ 1 - 1
client/src/app/+admin/jobs/shared/job.service.ts

@@ -6,7 +6,7 @@ import 'rxjs/add/operator/map'
 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'
 

+ 1 - 1
client/src/app/core/server/server.service.ts

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
 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()

+ 2 - 0
server/controllers/activitypub/inbox.ts

@@ -56,6 +56,8 @@ async function inboxController (req: express.Request, res: express.Response, nex
     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()

+ 2 - 2
server/controllers/api/config.ts

@@ -1,8 +1,8 @@
 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'

+ 2 - 0
server/controllers/api/server/index.ts

@@ -1,9 +1,11 @@
 import * as express from 'express'
 import { serverFollowsRouter } from './follows'
+import { statsRouter } from './stats'
 
 const serverRouter = express.Router()
 
 serverRouter.use('/', serverFollowsRouter)
+serverRouter.use('/', statsRouter)
 
 // ---------------------------------------------------------------------------
 

+ 39 - 0
server/controllers/api/server/stats.ts

@@ -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
+}

+ 9 - 0
server/models/account/user.ts

@@ -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 { VideoCommentModel } from '../video/video-comment'
 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)
   }

+ 22 - 28
server/models/activitypub/actor-follow.ts

@@ -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 { getServerActor } from '../../helpers/utils'
 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)
   }
 
-  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,
@@ -296,6 +269,27 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     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[],

+ 26 - 0
server/models/video/video-comment.ts

@@ -326,6 +326,32 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
       .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
   }

+ 23 - 0
server/models/video/video.ts

@@ -761,6 +761,29 @@ export class VideoModel extends Model<VideoModel> {
       .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
 

+ 1 - 1
server/tests/api/check-params/config.ts

@@ -2,7 +2,7 @@
 
 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,

+ 2 - 1
server/tests/api/index-fast.ts

@@ -1,5 +1,5 @@
 // Order of the tests we want to execute
-import './server/config'
+import './server/stats'
 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 './server/config'

+ 0 - 1
server/tests/api/index-slow.ts

@@ -1,5 +1,4 @@
 // Order of the tests we want to execute
-// import './multiple-servers'
 import './videos/video-transcoder'
 import './videos/multiple-servers'
 import './server/follows'

+ 2 - 2
server/tests/api/server/config.ts

@@ -2,8 +2,8 @@
 
 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
 

+ 102 - 0
server/tests/api/server/stats.ts

@@ -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()
+    }
+  })
+})

+ 1 - 1
server/tests/utils/server/config.ts

@@ -1,5 +1,5 @@
 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'

+ 17 - 0
server/tests/utils/server/stats.ts

@@ -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
+}

+ 2 - 2
shared/models/index.ts

@@ -2,7 +2,7 @@ export * from './actors'
 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 './config/server-config.model'
+export * from './server/server-config.model'

+ 0 - 0
shared/models/config/about.model.ts → shared/models/server/about.model.ts


+ 0 - 0
shared/models/config/custom-config.model.ts → shared/models/server/custom-config.model.ts


+ 0 - 0
shared/models/config/customization.model.ts → shared/models/server/customization.model.ts


+ 0 - 0
shared/models/job.model.ts → shared/models/server/job.model.ts


+ 0 - 0
shared/models/config/server-config.model.ts → shared/models/server/server-config.model.ts


+ 12 - 0
shared/models/server/server-stats.model.ts

@@ -0,0 +1,12 @@
+export interface ServerStats {
+  totalUsers: number
+  totalLocalVideos: number
+  totalLocalVideoViews: number
+  totalLocalVideoComments: number
+
+  totalVideos: number
+  totalVideoComments: number
+
+  totalInstanceFollowers: number
+  totalInstanceFollowing: number
+}