aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/shared/users/user.model.ts3
-rw-r--r--server/controllers/api/server/stats.ts8
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/migrations/0505-user-last-login-date.ts26
-rw-r--r--server/lib/oauth-model.ts4
-rw-r--r--server/models/account/user.ts29
-rw-r--r--server/tests/api/server/stats.ts35
-rw-r--r--server/tests/api/users/users.ts3
-rw-r--r--shared/models/server/server-stats.model.ts4
-rw-r--r--shared/models/users/user.model.ts2
10 files changed, 107 insertions, 9 deletions
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 3f6743bef..3348fe75f 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -71,6 +71,8 @@ export class User implements UserServerModel {
71 71
72 pluginAuth: string | null 72 pluginAuth: string | null
73 73
74 lastLoginDate: Date | null
75
74 createdAt: Date 76 createdAt: Date
75 77
76 constructor (hash: Partial<UserServerModel>) { 78 constructor (hash: Partial<UserServerModel>) {
@@ -115,6 +117,7 @@ export class User implements UserServerModel {
115 this.createdAt = hash.createdAt 117 this.createdAt = hash.createdAt
116 118
117 this.pluginAuth = hash.pluginAuth 119 this.pluginAuth = hash.pluginAuth
120 this.lastLoginDate = hash.lastLoginDate
118 121
119 if (hash.account !== undefined) { 122 if (hash.account !== undefined) {
120 this.account = new Account(hash.account) 123 this.account = new Account(hash.account)
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index f6a85d0c0..f07301a04 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -22,7 +22,7 @@ statsRouter.get('/stats',
22async function getStats (req: express.Request, res: express.Response) { 22async function getStats (req: express.Request, res: express.Response) {
23 const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() 23 const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
24 const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() 24 const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
25 const { totalUsers } = await UserModel.getStats() 25 const { totalUsers, totalDailyActiveUsers, totalWeeklyActiveUsers, totalMonthlyActiveUsers } = await UserModel.getStats()
26 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() 26 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
27 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() 27 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
28 28
@@ -48,9 +48,15 @@ async function getStats (req: express.Request, res: express.Response) {
48 totalLocalVideoComments, 48 totalLocalVideoComments,
49 totalVideos, 49 totalVideos,
50 totalVideoComments, 50 totalVideoComments,
51
51 totalUsers, 52 totalUsers,
53 totalDailyActiveUsers,
54 totalWeeklyActiveUsers,
55 totalMonthlyActiveUsers,
56
52 totalInstanceFollowers, 57 totalInstanceFollowers,
53 totalInstanceFollowing, 58 totalInstanceFollowing,
59
54 videosRedundancy: videosRedundancyStats 60 videosRedundancy: videosRedundancyStats
55 } 61 }
56 62
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 298322e3d..134560717 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
14 14
15// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
16 16
17const LAST_MIGRATION_VERSION = 500 17const LAST_MIGRATION_VERSION = 505
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
diff --git a/server/initializers/migrations/0505-user-last-login-date.ts b/server/initializers/migrations/0505-user-last-login-date.ts
new file mode 100644
index 000000000..29d970802
--- /dev/null
+++ b/server/initializers/migrations/0505-user-last-login-date.ts
@@ -0,0 +1,26 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7}): Promise<void> {
8
9 {
10 const field = {
11 type: Sequelize.DATE,
12 allowNull: true
13 }
14 await utils.queryInterface.addColumn('user', 'lastLoginDate', field)
15 }
16
17}
18
19function down (options) {
20 throw new Error('Not implemented.')
21}
22
23export {
24 up,
25 down
26}
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index 136abd0c4..dbcba897a 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -180,6 +180,10 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User
180 } 180 }
181 181
182 const tokenCreated = await OAuthTokenModel.create(tokenToCreate) 182 const tokenCreated = await OAuthTokenModel.create(tokenToCreate)
183
184 user.lastLoginDate = new Date()
185 await user.save()
186
183 return Object.assign(tokenCreated, { client, user }) 187 return Object.assign(tokenCreated, { client, user })
184} 188}
185 189
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 260c1b28e..fbd3080c6 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -353,6 +353,11 @@ export class UserModel extends Model<UserModel> {
353 @Column 353 @Column
354 pluginAuth: string 354 pluginAuth: string
355 355
356 @AllowNull(true)
357 @Default(null)
358 @Column
359 lastLoginDate: Date
360
356 @CreatedAt 361 @CreatedAt
357 createdAt: Date 362 createdAt: Date
358 363
@@ -691,10 +696,28 @@ export class UserModel extends Model<UserModel> {
691 } 696 }
692 697
693 static async getStats () { 698 static async getStats () {
699 function getActiveUsers (days: number) {
700 const query = {
701 where: {
702 [Op.and]: [
703 literal(`"lastLoginDate" > NOW() - INTERVAL '${days}d'`)
704 ]
705 }
706 }
707
708 return UserModel.count(query)
709 }
710
694 const totalUsers = await UserModel.count() 711 const totalUsers = await UserModel.count()
712 const totalDailyActiveUsers = await getActiveUsers(1)
713 const totalWeeklyActiveUsers = await getActiveUsers(7)
714 const totalMonthlyActiveUsers = await getActiveUsers(30)
695 715
696 return { 716 return {
697 totalUsers 717 totalUsers,
718 totalDailyActiveUsers,
719 totalWeeklyActiveUsers,
720 totalMonthlyActiveUsers
698 } 721 }
699 } 722 }
700 723
@@ -808,7 +831,9 @@ export class UserModel extends Model<UserModel> {
808 831
809 createdAt: this.createdAt, 832 createdAt: this.createdAt,
810 833
811 pluginAuth: this.pluginAuth 834 pluginAuth: this.pluginAuth,
835
836 lastLoginDate: this.lastLoginDate
812 } 837 }
813 838
814 if (parameters.withAdminFlags) { 839 if (parameters.withAdminFlags) {
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
index fe956413c..637525ff8 100644
--- a/server/tests/api/server/stats.ts
+++ b/server/tests/api/server/stats.ts
@@ -12,7 +12,8 @@ import {
12 ServerInfo, unfollow, 12 ServerInfo, unfollow,
13 uploadVideo, 13 uploadVideo,
14 viewVideo, 14 viewVideo,
15 wait 15 wait,
16 userLogin
16} from '../../../../shared/extra-utils' 17} from '../../../../shared/extra-utils'
17import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' 18import { setAccessTokensToServers } from '../../../../shared/extra-utils/index'
18import { getStats } from '../../../../shared/extra-utils/server/stats' 19import { getStats } from '../../../../shared/extra-utils/server/stats'
@@ -23,6 +24,10 @@ const expect = chai.expect
23 24
24describe('Test stats (excluding redundancy)', function () { 25describe('Test stats (excluding redundancy)', function () {
25 let servers: ServerInfo[] = [] 26 let servers: ServerInfo[] = []
27 const user = {
28 username: 'user1',
29 password: 'super_password'
30 }
26 31
27 before(async function () { 32 before(async function () {
28 this.timeout(60000) 33 this.timeout(60000)
@@ -31,10 +36,6 @@ describe('Test stats (excluding redundancy)', function () {
31 36
32 await doubleFollow(servers[0], servers[1]) 37 await doubleFollow(servers[0], servers[1])
33 38
34 const user = {
35 username: 'user1',
36 password: 'super_password'
37 }
38 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) 39 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
39 40
40 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) 41 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
@@ -96,6 +97,8 @@ describe('Test stats (excluding redundancy)', function () {
96 }) 97 })
97 98
98 it('Should have the correct total videos stats after an unfollow', async function () { 99 it('Should have the correct total videos stats after an unfollow', async function () {
100 this.timeout(15000)
101
99 await unfollow(servers[2].url, servers[2].accessToken, servers[0]) 102 await unfollow(servers[2].url, servers[2].accessToken, servers[0])
100 await waitJobs(servers) 103 await waitJobs(servers)
101 104
@@ -105,6 +108,28 @@ describe('Test stats (excluding redundancy)', function () {
105 expect(data.totalVideos).to.equal(0) 108 expect(data.totalVideos).to.equal(0)
106 }) 109 })
107 110
111 it('Should have the correct active users stats', async function () {
112 const server = servers[0]
113
114 {
115 const res = await getStats(server.url)
116 const data: ServerStats = res.body
117 expect(data.totalDailyActiveUsers).to.equal(1)
118 expect(data.totalWeeklyActiveUsers).to.equal(1)
119 expect(data.totalMonthlyActiveUsers).to.equal(1)
120 }
121
122 {
123 await userLogin(server, user)
124
125 const res = await getStats(server.url)
126 const data: ServerStats = res.body
127 expect(data.totalDailyActiveUsers).to.equal(2)
128 expect(data.totalWeeklyActiveUsers).to.equal(2)
129 expect(data.totalMonthlyActiveUsers).to.equal(2)
130 }
131 })
132
108 after(async function () { 133 after(async function () {
109 await cleanupTests(servers) 134 await cleanupTests(servers)
110 }) 135 })
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index f3b732632..c0cbce360 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -418,6 +418,9 @@ describe('Test users', function () {
418 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') 418 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
419 expect(user.nsfwPolicy).to.equal('display') 419 expect(user.nsfwPolicy).to.equal('display')
420 420
421 expect(rootUser.lastLoginDate).to.exist
422 expect(user.lastLoginDate).to.exist
423
421 userId = user.id 424 userId = user.id
422 }) 425 })
423 426
diff --git a/shared/models/server/server-stats.model.ts b/shared/models/server/server-stats.model.ts
index 11778e6ed..75d7dc554 100644
--- a/shared/models/server/server-stats.model.ts
+++ b/shared/models/server/server-stats.model.ts
@@ -2,6 +2,10 @@ import { VideoRedundancyStrategyWithManual } from '../redundancy'
2 2
3export interface ServerStats { 3export interface ServerStats {
4 totalUsers: number 4 totalUsers: number
5 totalDailyActiveUsers: number
6 totalWeeklyActiveUsers: number
7 totalMonthlyActiveUsers: number
8
5 totalLocalVideos: number 9 totalLocalVideos: number
6 totalLocalVideoViews: number 10 totalLocalVideoViews: number
7 totalLocalVideoComments: number 11 totalLocalVideoComments: number
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 42be04289..6c959ceea 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -52,6 +52,8 @@ export interface User {
52 createdAt: Date 52 createdAt: Date
53 53
54 pluginAuth: string | null 54 pluginAuth: string | null
55
56 lastLoginDate: Date | null
55} 57}
56 58
57export interface MyUserSpecialPlaylist { 59export interface MyUserSpecialPlaylist {