HSB-450 feature: user last active on (#4121)
* feat: userLastActiveOnInterceptor added and update func added in userService * chore: changed user model parameter description * chore: commented out docker volume for hopp-old-service * chore: changed backend to work with secure cookies --------- Co-authored-by: Balu Babu <balub997@gmail.com>
This commit is contained in:
@@ -112,7 +112,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
dockerfile: packages/hoppscotch-backend/Dockerfile
|
dockerfile: packages/hoppscotch-backend/Dockerfile
|
||||||
context: .
|
context: .
|
||||||
target: dev
|
target: prod
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
restart: always
|
restart: always
|
||||||
@@ -122,7 +122,7 @@ services:
|
|||||||
- PORT=3000
|
- PORT=3000
|
||||||
volumes:
|
volumes:
|
||||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||||
- ./packages/hoppscotch-backend/:/usr/src/app
|
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||||
- /usr/src/app/node_modules/
|
- /usr/src/app/node_modules/
|
||||||
depends_on:
|
depends_on:
|
||||||
hoppscotch-db:
|
hoppscotch-db:
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "lastActiveOn" TIMESTAMP(3);
|
||||||
@@ -104,7 +104,8 @@ model User {
|
|||||||
userRequests UserRequest[]
|
userRequests UserRequest[]
|
||||||
currentRESTSession Json?
|
currentRESTSession Json?
|
||||||
currentGQLSession Json?
|
currentGQLSession Json?
|
||||||
lastLoggedOn DateTime?
|
lastLoggedOn DateTime? @db.Timestamp(3)
|
||||||
|
lastActiveOn DateTime? @db.Timestamp(3)
|
||||||
createdOn DateTime @default(now()) @db.Timestamp(3)
|
createdOn DateTime @default(now()) @db.Timestamp(3)
|
||||||
invitedUsers InvitedUsers[]
|
invitedUsers InvitedUsers[]
|
||||||
shortcodes Shortcode[]
|
shortcodes Shortcode[]
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { PosthogModule } from './posthog/posthog.module';
|
|||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
import { AccessTokenModule } from './access-token/access-token.module';
|
import { AccessTokenModule } from './access-token/access-token.module';
|
||||||
|
import { UserLastActiveOnInterceptor } from './interceptors/user-last-active-on.interceptor';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -105,7 +106,10 @@ import { AccessTokenModule } from './access-token/access-token.module';
|
|||||||
HealthModule,
|
HealthModule,
|
||||||
AccessTokenModule,
|
AccessTokenModule,
|
||||||
],
|
],
|
||||||
providers: [GQLComplexityPlugin],
|
providers: [
|
||||||
|
GQLComplexityPlugin,
|
||||||
|
{ provide: 'APP_INTERCEPTOR', useClass: UserLastActiveOnInterceptor },
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
|
import { UserService } from 'src/user/user.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserLastActiveOnInterceptor implements NestInterceptor {
|
||||||
|
constructor(private userService: UserService) {}
|
||||||
|
|
||||||
|
user: AuthUser; // 'user', who executed the request
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
if (context.getType() === 'http') {
|
||||||
|
return this.restHandler(context, next);
|
||||||
|
} else if (context.getType<GqlContextType>() === 'graphql') {
|
||||||
|
return this.graphqlHandler(context, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restHandler(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
this.user = request.user;
|
||||||
|
|
||||||
|
return next.handle().pipe(
|
||||||
|
tap(() => {
|
||||||
|
if (this.user && typeof this.user === 'object') {
|
||||||
|
this.userService.updateUserLastActiveOn(this.user.uid);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
if (this.user && typeof this.user === 'object') {
|
||||||
|
this.userService.updateUserLastActiveOn(this.user.uid);
|
||||||
|
}
|
||||||
|
return throwError(() => error);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphqlHandler(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler,
|
||||||
|
): Observable<any> {
|
||||||
|
const contextObject = GqlExecutionContext.create(context).getContext();
|
||||||
|
this.user = contextObject.req.user;
|
||||||
|
|
||||||
|
return next.handle().pipe(
|
||||||
|
tap(() => {
|
||||||
|
if (this.user && typeof this.user === 'object') {
|
||||||
|
this.userService.updateUserLastActiveOn(this.user.uid);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
if (this.user && typeof this.user === 'object') {
|
||||||
|
this.userService.updateUserLastActiveOn(this.user.uid);
|
||||||
|
}
|
||||||
|
return throwError(() => error);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ export class UserLastLoginInterceptor implements NestInterceptor {
|
|||||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
const user: AuthUser = context.switchToHttp().getRequest().user;
|
const user: AuthUser = context.switchToHttp().getRequest().user;
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
return next.handle().pipe(
|
return next.handle().pipe(
|
||||||
tap(() => {
|
tap(() => {
|
||||||
this.userService.updateUserLastLoggedOn(user.uid);
|
this.userService.updateUserLastLoggedOn(user.uid);
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ export class User {
|
|||||||
})
|
})
|
||||||
lastLoggedOn: Date;
|
lastLoggedOn: Date;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
nullable: true,
|
||||||
|
description: 'Date when the user last interacted with the app',
|
||||||
|
})
|
||||||
|
lastActiveOn: Date;
|
||||||
|
|
||||||
@Field({
|
@Field({
|
||||||
description: 'Date when the user account was created',
|
description: 'Date when the user account was created',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -334,6 +334,22 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user's lastActiveOn timestamp
|
||||||
|
* @param userUID User UID
|
||||||
|
*/
|
||||||
|
async updateUserLastActiveOn(userUid: string) {
|
||||||
|
try {
|
||||||
|
await this.prisma.user.update({
|
||||||
|
where: { uid: userUid },
|
||||||
|
data: { lastActiveOn: new Date() },
|
||||||
|
});
|
||||||
|
return E.right(true);
|
||||||
|
} catch (e) {
|
||||||
|
return E.left(USER_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate and parse currentRESTSession and currentGQLSession
|
* Validate and parse currentRESTSession and currentGQLSession
|
||||||
* @param sessionData string of the session
|
* @param sessionData string of the session
|
||||||
|
|||||||
Reference in New Issue
Block a user