diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index ad1620a23..f2fe18fc9 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -231,12 +231,27 @@ export class AdminService { } private async getUsersWithAnalytics(): Promise { - const usersWithAnalytics = await this.prismaService.user.findMany({ - orderBy: { + let orderBy: any = { + createdAt: 'desc' + }; + let where; + + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + orderBy = { Analytics: { updatedAt: 'desc' } - }, + }; + where = { + NOT: { + Analytics: null + } + }; + } + + const usersWithAnalytics = await this.prismaService.user.findMany({ + orderBy, + where, select: { _count: { select: { Account: true, Order: true } @@ -252,19 +267,16 @@ export class AdminService { id: true, Subscription: true }, - take: 30, - where: { - NOT: { - Analytics: null - } - } + take: 30 }); return usersWithAnalytics.map( ({ _count, Analytics, createdAt, id, Subscription }) => { const daysSinceRegistration = differenceInDays(new Date(), createdAt) + 1; - const engagement = Analytics.activityCount / daysSinceRegistration; + const engagement = Analytics + ? Analytics.activityCount / daysSinceRegistration + : undefined; const subscription = this.configurationService.get( 'ENABLE_FEATURE_SUBSCRIPTION' @@ -278,8 +290,8 @@ export class AdminService { id, subscription, accountCount: _count.Account || 0, - country: Analytics.country, - lastActivity: Analytics.updatedAt, + country: Analytics?.country, + lastActivity: Analytics?.updatedAt, transactionCount: _count.Order || 0 }; } diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/auth/jwt.strategy.ts index ee50e3b72..af21ecc0e 100644 --- a/apps/api/src/app/auth/jwt.strategy.ts +++ b/apps/api/src/app/auth/jwt.strategy.ts @@ -1,33 +1,46 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma.service'; +import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; +import * as countriesAndTimezones from 'countries-and-timezones'; import { ExtractJwt, Strategy } from 'passport-jwt'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { public constructor( - readonly configurationService: ConfigurationService, + private readonly configurationService: ConfigurationService, private readonly prismaService: PrismaService, private readonly userService: UserService ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + passReqToCallback: true, secretOrKey: configurationService.get('JWT_SECRET_KEY') }); } - public async validate({ id }: { id: string }) { + public async validate(request: Request, { id }: { id: string }) { try { + const timezone = request.headers[HEADER_KEY_TIMEZONE.toLowerCase()]; const user = await this.userService.user({ id }); if (user) { - await this.prismaService.analytics.upsert({ - create: { User: { connect: { id: user.id } } }, - update: { activityCount: { increment: 1 }, updatedAt: new Date() }, - where: { userId: user.id } - }); + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + const country = + countriesAndTimezones.getCountryForTimezone(timezone)?.id; + + await this.prismaService.analytics.upsert({ + create: { country, User: { connect: { id: user.id } } }, + update: { + country, + activityCount: { increment: 1 }, + updatedAt: new Date() + }, + where: { userId: user.id } + }); + } return user; } else { diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index a13e3395f..191c841bd 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -59,9 +59,7 @@ export class InfoService { } if (this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX')) { - if ( - this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true - ) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { info.fearAndGreedDataSource = encodeDataSource( ghostfolioFearAndGreedIndexDataSource ); diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index df81f08f9..44d21e9c9 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -1,5 +1,4 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config'; import { User, UserSettings } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; @@ -66,9 +65,7 @@ export class UserController { } @Post() - public async signupUser( - @Headers(HEADER_KEY_TIMEZONE.toLowerCase()) timezone - ): Promise { + public async signupUser(): Promise { const isUserSignupEnabled = await this.propertyService.isUserSignupEnabled(); @@ -82,7 +79,6 @@ export class UserController { const hasAdmin = await this.userService.hasAdmin(); const { accessToken, id, role } = await this.userService.createUser({ - timezone, data: { role: hasAdmin ? 'USER' : 'ADMIN' } }); diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 61d01cf34..1630a74aa 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -16,7 +16,6 @@ import { } from '@ghostfolio/common/permissions'; import { Injectable } from '@nestjs/common'; import { Prisma, Role, User } from '@prisma/client'; -import * as countriesAndTimezones from 'countries-and-timezones'; import { sortBy } from 'lodash'; const crypto = require('crypto'); @@ -233,11 +232,9 @@ export class UserService { } public async createUser({ - data, - timezone + data }: { data: Prisma.UserCreateInput; - timezone?: string; }): Promise { if (!data?.provider) { data.provider = 'ANONYMOUS'; @@ -266,7 +263,6 @@ export class UserService { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { await this.prismaService.analytics.create({ data: { - country: countriesAndTimezones.getCountryForTimezone(timezone)?.id, User: { connect: { id: user.id } } } }); diff --git a/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts index aa9952473..ecef35bb6 100644 --- a/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-request.interceptor.ts @@ -24,7 +24,7 @@ export class TransformDataSourceInRequestInterceptor const http = context.switchToHttp(); const request = http.getRequest(); - if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (request.body.dataSource) { request.body.dataSource = decodeDataSource(request.body.dataSource); } diff --git a/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts index d02c26fca..0d8b8f264 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response.interceptor.ts @@ -26,9 +26,7 @@ export class TransformDataSourceInResponseInterceptor ): Observable { return next.handle().pipe( map((data: any) => { - if ( - this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') === true - ) { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { data = redactAttributes({ options: [ { diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 317086c90..15127802e 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -28,7 +28,13 @@ > Engagement per Day - Last Request + + Last Request + @@ -86,7 +92,10 @@ [value]="userItem.engagement" > - + {{ formatDistanceToNow(userItem.lastActivity) }}