Add last login date to users

pull/2732/head
Chocobozzz 2020-05-07 10:39:09 +02:00
parent ee8e602ef9
commit 3cc665f48f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
10 changed files with 107 additions and 9 deletions

View File

@ -71,6 +71,8 @@ export class User implements UserServerModel {
pluginAuth: string | null pluginAuth: string | null
lastLoginDate: Date | null
createdAt: Date createdAt: Date
constructor (hash: Partial<UserServerModel>) { constructor (hash: Partial<UserServerModel>) {
@ -115,6 +117,7 @@ export class User implements UserServerModel {
this.createdAt = hash.createdAt this.createdAt = hash.createdAt
this.pluginAuth = hash.pluginAuth this.pluginAuth = hash.pluginAuth
this.lastLoginDate = hash.lastLoginDate
if (hash.account !== undefined) { if (hash.account !== undefined) {
this.account = new Account(hash.account) this.account = new Account(hash.account)

View File

@ -22,7 +22,7 @@ statsRouter.get('/stats',
async function getStats (req: express.Request, res: express.Response) { async function getStats (req: express.Request, res: express.Response) {
const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
const { totalUsers } = await UserModel.getStats() const { totalUsers, totalDailyActiveUsers, totalWeeklyActiveUsers, totalMonthlyActiveUsers } = await UserModel.getStats()
const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
@ -48,9 +48,15 @@ async function getStats (req: express.Request, res: express.Response) {
totalLocalVideoComments, totalLocalVideoComments,
totalVideos, totalVideos,
totalVideoComments, totalVideoComments,
totalUsers, totalUsers,
totalDailyActiveUsers,
totalWeeklyActiveUsers,
totalMonthlyActiveUsers,
totalInstanceFollowers, totalInstanceFollowers,
totalInstanceFollowing, totalInstanceFollowing,
videosRedundancy: videosRedundancyStats videosRedundancy: videosRedundancyStats
} }

View File

@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 500 const LAST_MIGRATION_VERSION = 505
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -0,0 +1,26 @@
import * as Sequelize from 'sequelize'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
}): Promise<void> {
{
const field = {
type: Sequelize.DATE,
allowNull: true
}
await utils.queryInterface.addColumn('user', 'lastLoginDate', field)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export {
up,
down
}

View File

@ -180,6 +180,10 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User
} }
const tokenCreated = await OAuthTokenModel.create(tokenToCreate) const tokenCreated = await OAuthTokenModel.create(tokenToCreate)
user.lastLoginDate = new Date()
await user.save()
return Object.assign(tokenCreated, { client, user }) return Object.assign(tokenCreated, { client, user })
} }

View File

@ -353,6 +353,11 @@ export class UserModel extends Model<UserModel> {
@Column @Column
pluginAuth: string pluginAuth: string
@AllowNull(true)
@Default(null)
@Column
lastLoginDate: Date
@CreatedAt @CreatedAt
createdAt: Date createdAt: Date
@ -691,10 +696,28 @@ export class UserModel extends Model<UserModel> {
} }
static async getStats () { static async getStats () {
function getActiveUsers (days: number) {
const query = {
where: {
[Op.and]: [
literal(`"lastLoginDate" > NOW() - INTERVAL '${days}d'`)
]
}
}
return UserModel.count(query)
}
const totalUsers = await UserModel.count() const totalUsers = await UserModel.count()
const totalDailyActiveUsers = await getActiveUsers(1)
const totalWeeklyActiveUsers = await getActiveUsers(7)
const totalMonthlyActiveUsers = await getActiveUsers(30)
return { return {
totalUsers totalUsers,
totalDailyActiveUsers,
totalWeeklyActiveUsers,
totalMonthlyActiveUsers
} }
} }
@ -808,7 +831,9 @@ export class UserModel extends Model<UserModel> {
createdAt: this.createdAt, createdAt: this.createdAt,
pluginAuth: this.pluginAuth pluginAuth: this.pluginAuth,
lastLoginDate: this.lastLoginDate
} }
if (parameters.withAdminFlags) { if (parameters.withAdminFlags) {

View File

@ -12,7 +12,8 @@ import {
ServerInfo, unfollow, ServerInfo, unfollow,
uploadVideo, uploadVideo,
viewVideo, viewVideo,
wait wait,
userLogin
} from '../../../../shared/extra-utils' } from '../../../../shared/extra-utils'
import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' import { setAccessTokensToServers } from '../../../../shared/extra-utils/index'
import { getStats } from '../../../../shared/extra-utils/server/stats' import { getStats } from '../../../../shared/extra-utils/server/stats'
@ -23,6 +24,10 @@ const expect = chai.expect
describe('Test stats (excluding redundancy)', function () { describe('Test stats (excluding redundancy)', function () {
let servers: ServerInfo[] = [] let servers: ServerInfo[] = []
const user = {
username: 'user1',
password: 'super_password'
}
before(async function () { before(async function () {
this.timeout(60000) this.timeout(60000)
@ -31,10 +36,6 @@ describe('Test stats (excluding redundancy)', function () {
await doubleFollow(servers[0], servers[1]) await doubleFollow(servers[0], servers[1])
const user = {
username: 'user1',
password: 'super_password'
}
await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
@ -96,6 +97,8 @@ describe('Test stats (excluding redundancy)', function () {
}) })
it('Should have the correct total videos stats after an unfollow', async function () { it('Should have the correct total videos stats after an unfollow', async function () {
this.timeout(15000)
await unfollow(servers[2].url, servers[2].accessToken, servers[0]) await unfollow(servers[2].url, servers[2].accessToken, servers[0])
await waitJobs(servers) await waitJobs(servers)
@ -105,6 +108,28 @@ describe('Test stats (excluding redundancy)', function () {
expect(data.totalVideos).to.equal(0) expect(data.totalVideos).to.equal(0)
}) })
it('Should have the correct active users stats', async function () {
const server = servers[0]
{
const res = await getStats(server.url)
const data: ServerStats = res.body
expect(data.totalDailyActiveUsers).to.equal(1)
expect(data.totalWeeklyActiveUsers).to.equal(1)
expect(data.totalMonthlyActiveUsers).to.equal(1)
}
{
await userLogin(server, user)
const res = await getStats(server.url)
const data: ServerStats = res.body
expect(data.totalDailyActiveUsers).to.equal(2)
expect(data.totalWeeklyActiveUsers).to.equal(2)
expect(data.totalMonthlyActiveUsers).to.equal(2)
}
})
after(async function () { after(async function () {
await cleanupTests(servers) await cleanupTests(servers)
}) })

View File

@ -418,6 +418,9 @@ describe('Test users', function () {
expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
expect(user.nsfwPolicy).to.equal('display') expect(user.nsfwPolicy).to.equal('display')
expect(rootUser.lastLoginDate).to.exist
expect(user.lastLoginDate).to.exist
userId = user.id userId = user.id
}) })

View File

@ -2,6 +2,10 @@ import { VideoRedundancyStrategyWithManual } from '../redundancy'
export interface ServerStats { export interface ServerStats {
totalUsers: number totalUsers: number
totalDailyActiveUsers: number
totalWeeklyActiveUsers: number
totalMonthlyActiveUsers: number
totalLocalVideos: number totalLocalVideos: number
totalLocalVideoViews: number totalLocalVideoViews: number
totalLocalVideoComments: number totalLocalVideoComments: number

View File

@ -52,6 +52,8 @@ export interface User {
createdAt: Date createdAt: Date
pluginAuth: string | null pluginAuth: string | null
lastLoginDate: Date | null
} }
export interface MyUserSpecialPlaylist { export interface MyUserSpecialPlaylist {