Browse Source

Bugfix/adapt accounts and tags of assistant in impersonation mode (#6231)

* Adapt accounts and tags of assistant in impersonation mode

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/6213/merge
Anurag Choudhury 1 day ago
committed by GitHub
parent
commit
d7349cd621
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      CHANGELOG.md
  2. 23
      apps/api/src/app/user/user.controller.ts
  3. 4
      apps/api/src/app/user/user.module.ts
  4. 51
      apps/api/src/app/user/user.service.ts
  5. 16
      apps/api/src/services/tag/tag.service.ts

5
CHANGELOG.md

@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed the `transactionCount` in the portfolio calculator and service
- Refreshed the cryptocurrencies list
### Fixed
- Fixed the accounts of the assistant for the impersonation mode
- Fixed the tags of the assistant for the impersonation mode
## 2.236.0 - 2026-02-05
### Changed

23
apps/api/src/app/user/user.controller.ts

@ -1,8 +1,11 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import {
DeleteOwnUserDto,
UpdateOwnAccessTokenDto,
@ -28,7 +31,8 @@ import {
Param,
Post,
Put,
UseGuards
UseGuards,
UseInterceptors
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
@ -43,6 +47,7 @@ import { UserService } from './user.service';
export class UserController {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly impersonationService: ImpersonationService,
private readonly jwtService: JwtService,
private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService,
@ -107,13 +112,19 @@ export class UserController {
@Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async getUser(
@Headers('accept-language') acceptLanguage: string
@Headers('accept-language') acceptLanguage: string,
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
): Promise<User> {
return this.userService.getUser(
this.request.user,
acceptLanguage?.split(',')?.[0]
);
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
return this.userService.getUser({
impersonationUserId,
locale: acceptLanguage?.split(',')?.[0],
user: this.request.user
});
}
@Post()

4
apps/api/src/app/user/user.module.ts

@ -1,7 +1,9 @@
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
@ -18,6 +20,7 @@ import { UserService } from './user.service';
imports: [
ConfigurationModule,
I18nModule,
ImpersonationModule,
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '30 days' }
@ -25,6 +28,7 @@ import { UserService } from './user.service';
OrderModule,
PrismaModule,
PropertyModule,
RedactValuesInResponseModule,
SubscriptionModule,
TagModule
],

51
apps/api/src/app/user/user.service.ts

@ -30,7 +30,7 @@ import {
PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_SYSTEM_MESSAGE,
TAG_ID_EXCLUDE_FROM_ANALYSIS,
locale
locale as defaultLocale
} from '@ghostfolio/common/config';
import {
User as IUser,
@ -49,7 +49,7 @@ import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Prisma, Role, User } from '@prisma/client';
import { differenceInDays, subDays } from 'date-fns';
import { sortBy, without } from 'lodash';
import { without } from 'lodash';
import { createHmac } from 'node:crypto';
@Injectable()
@ -96,10 +96,17 @@ export class UserService {
return { accessToken, hashedAccessToken };
}
public async getUser(
{ accounts, id, permissions, settings, subscription }: UserWithSettings,
aLocale = locale
): Promise<IUser> {
public async getUser({
impersonationUserId,
locale = defaultLocale,
user
}: {
impersonationUserId: string;
locale?: string;
user: UserWithSettings;
}): Promise<IUser> {
const { id, permissions, settings, subscription } = user;
const userData = await Promise.all([
this.prismaService.access.findMany({
include: {
@ -108,22 +115,31 @@ export class UserService {
orderBy: { alias: 'asc' },
where: { granteeUserId: id }
}),
this.prismaService.account.findMany({
orderBy: {
name: 'asc'
},
where: {
userId: impersonationUserId || user.id
}
}),
this.prismaService.order.count({
where: { userId: id }
where: { userId: impersonationUserId || user.id }
}),
this.prismaService.order.findFirst({
orderBy: {
date: 'asc'
},
where: { userId: id }
where: { userId: impersonationUserId || user.id }
}),
this.tagService.getTagsForUser(id)
this.tagService.getTagsForUser(impersonationUserId || user.id)
]);
const access = userData[0];
const activitiesCount = userData[1];
const firstActivity = userData[2];
let tags = userData[3].filter((tag) => {
const accounts = userData[1];
const activitiesCount = userData[2];
const firstActivity = userData[3];
let tags = userData[4].filter((tag) => {
return tag.id !== TAG_ID_EXCLUDE_FROM_ANALYSIS;
});
@ -146,7 +162,6 @@ export class UserService {
}
return {
accounts,
activitiesCount,
id,
permissions,
@ -160,10 +175,13 @@ export class UserService {
permissions: accessItem.permissions
};
}),
accounts: accounts.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}),
dateOfFirstActivity: firstActivity?.date ?? new Date(),
settings: {
...(settings.settings as UserSettings),
locale: (settings.settings as UserSettings)?.locale ?? aLocale
locale: (settings.settings as UserSettings)?.locale ?? locale
}
};
}
@ -516,9 +534,10 @@ export class UserService {
currentPermissions.push(permissions.impersonateAllUsers);
}
user.accounts = sortBy(user.accounts, ({ name }) => {
return name.toLowerCase();
user.accounts = user.accounts.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
user.permissions = currentPermissions.sort();
return user;

16
apps/api/src/services/tag/tag.service.ts

@ -75,12 +75,16 @@ export class TagService {
}
});
return tags.map(({ _count, id, name, userId }) => ({
id,
name,
userId,
isUsed: _count.activities > 0
}));
return tags
.map(({ _count, id, name, userId }) => ({
id,
name,
userId,
isUsed: _count.activities > 0
}))
.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
}
public async getTagsWithActivityCount() {

Loading…
Cancel
Save