-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implemented user activity tracker middleware & tests, connected…
… middleware to app
- Loading branch information
1 parent
08428bb
commit 916021e
Showing
9 changed files
with
336 additions
and
189 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
...rkflows-service/prisma/migrations/20230628134448_add_last_active_at_to_user/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
-- AlterTable | ||
ALTER TABLE "User" ADD COLUMN "lastActiveAt" TIMESTAMP(3); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
services/workflows-service/src/common/middlewares/user-activity-tracker.middleware.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { AppLoggerService } from '@/common/app-logger/app-logger.service'; | ||
import { UserService } from '@/user/user.service'; | ||
import { Injectable, NestMiddleware } from '@nestjs/common'; | ||
import { User } from '@prisma/client'; | ||
import { Request, Response } from 'express'; | ||
|
||
@Injectable() | ||
export class UserActivityTrackerMiddleware implements NestMiddleware { | ||
private FIVE_MINUTES_IN_MS = 1000 * 60 * 5; | ||
UPDATE_INTERVAL = this.FIVE_MINUTES_IN_MS; | ||
|
||
constructor( | ||
private readonly logger: AppLoggerService, | ||
private readonly userService: UserService, | ||
) {} | ||
|
||
async use(req: Request, res: Response, next: (error?: any) => void) { | ||
if (req.session && req.user) { | ||
if (this.isUpdateCanBePerformed((req.user as User).lastActiveAt)) { | ||
await this.trackUserActivity(req.user as User); | ||
} | ||
} | ||
|
||
next(); | ||
} | ||
|
||
private isUpdateCanBePerformed( | ||
lastUpdate: Date | null, | ||
updateIntervalInMs: number = this.UPDATE_INTERVAL, | ||
) { | ||
if (!lastUpdate) return true; | ||
|
||
const now = Date.now(); | ||
const pastDate = Number(new Date(lastUpdate)); | ||
|
||
return now - pastDate >= updateIntervalInMs; | ||
} | ||
|
||
private async trackUserActivity(user: User, activeDate = new Date()) { | ||
this.logger.log(`Updating activity`, { userId: user.id }); | ||
await this.userService.updateById(user.id, { data: { lastActiveAt: activeDate } }); | ||
this.logger.log(`Updated activity`, { userId: user.id }); | ||
} | ||
} |
117 changes: 117 additions & 0 deletions
117
...es/workflows-service/src/common/middlewares/user-activity-tracker.middleware.unit.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { AppLoggerService } from '@/common/app-logger/app-logger.service'; | ||
import { UserActivityTrackerMiddleware } from '@/common/middlewares/user-activity-tracker.middleware'; | ||
import { PrismaModule } from '@/prisma/prisma.module'; | ||
import { commonTestingModules } from '@/test/helpers/nest-app-helper'; | ||
import { UserModule } from '@/user/user.module'; | ||
import { UserService } from '@/user/user.service'; | ||
import { INestApplication } from '@nestjs/common'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
import { User } from '@prisma/client'; | ||
import { Request, Response } from 'express'; | ||
import dayjs from 'dayjs'; | ||
|
||
describe('UserActivityTrackerMiddleware', () => { | ||
const testUserPayload = { | ||
firstName: 'Test', | ||
lastName: 'User', | ||
password: '', | ||
email: 'example@mail.com', | ||
roles: [], | ||
} as unknown as User; | ||
let moduleRef: TestingModule; | ||
let app: INestApplication; | ||
let testUser: User; | ||
let middleware: UserActivityTrackerMiddleware; | ||
let userService: UserService; | ||
let callback: jest.Mock; | ||
|
||
beforeEach(async () => { | ||
moduleRef = await Test.createTestingModule({ | ||
imports: [UserModule, PrismaModule, ...commonTestingModules], | ||
}).compile(); | ||
app = moduleRef.createNestApplication(); | ||
middleware = new UserActivityTrackerMiddleware(app.get(AppLoggerService), app.get(UserService)); | ||
userService = app.get(UserService); | ||
callback = jest.fn(() => null); | ||
}); | ||
|
||
describe('when request not includes session and user', () => { | ||
it('will call callback', async () => { | ||
await middleware.use({} as Request, {} as Response, callback); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe('when session and user in request', () => { | ||
describe('when lastActiveAt unset', () => { | ||
beforeEach(async () => { | ||
testUser = await app.get(UserService).create({ data: testUserPayload as any }); | ||
}); | ||
|
||
afterEach(async () => { | ||
await app.get(UserService).deleteById(testUser.id); | ||
}); | ||
|
||
it('will be set on middleware call', async () => { | ||
await middleware.use( | ||
{ user: testUser, session: testUser } as any, | ||
{} as Response, | ||
callback, | ||
); | ||
|
||
const updatedUser = await app.get(UserService).getById(testUser.id); | ||
|
||
expect(updatedUser.lastActiveAt).toBeTruthy(); | ||
expect(callback).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
|
||
describe('when lastActiveAt is set', () => { | ||
beforeEach(async () => { | ||
testUser = await app.get(UserService).create({ data: testUserPayload as any }); | ||
app.use(middleware.use.bind(middleware)); | ||
}); | ||
|
||
afterEach(async () => { | ||
await app.get(UserService).deleteById(testUser.id); | ||
}); | ||
|
||
it('will not be changed when lastActiveAt not expired', async () => { | ||
const nonExpiredDate = dayjs().subtract(middleware.UPDATE_INTERVAL - 10, 'ms'); | ||
|
||
testUser = await userService.updateById(testUser.id, { | ||
data: { lastActiveAt: nonExpiredDate.toDate() }, | ||
}); | ||
|
||
await middleware.use( | ||
{ user: testUser, session: testUser } as any, | ||
{} as Response, | ||
callback, | ||
); | ||
|
||
const user = await userService.getById(testUser.id); | ||
|
||
expect(user.lastActiveAt).toEqual(nonExpiredDate.toDate()); | ||
expect(callback).toBeCalledTimes(1); | ||
}); | ||
|
||
it('will be updated when lastActiveAt expired', async () => { | ||
const expiredDate = dayjs().subtract(middleware.UPDATE_INTERVAL + 10, 'ms'); | ||
|
||
testUser.lastActiveAt = expiredDate.toDate(); | ||
|
||
await middleware.use( | ||
{ user: testUser, session: testUser } as any, | ||
{} as Response, | ||
callback, | ||
); | ||
|
||
const updatedUser = await userService.getById(testUser.id); | ||
|
||
expect(Number(updatedUser.lastActiveAt)).toBeGreaterThan(Number(expiredDate.toDate())); | ||
expect(callback).toBeCalledTimes(1); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters