Browse Source

Bugfix/base currency in impersonation mode (#6964)

* Fix base currency in impersonation mode

* Update changelog
pull/6980/head
Thomas Kaul 5 hours ago
committed by GitHub
parent
commit
9996d8a8d2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 11
      apps/api/src/app/account/account.controller.ts
  3. 4
      apps/api/src/app/account/account.module.ts
  4. 11
      apps/api/src/app/portfolio/portfolio.controller.ts
  5. 7
      apps/api/src/app/portfolio/portfolio.service.ts
  6. 31
      apps/api/src/app/user/user.service.ts

1
CHANGELOG.md

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Resolved an issue in the impersonation mode where the values did not match the owner’s currency
- Fixed the environment variable expansion in the `.env` file when debugging via _Visual Studio Code_ - Fixed the environment variable expansion in the `.env` file when debugging via _Visual Studio Code_
## 3.6.0 - 2026-05-28 ## 3.6.0 - 2026-05-28

11
apps/api/src/app/account/account.controller.ts

@ -1,5 +1,6 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; 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 { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
@ -50,7 +51,8 @@ export class AccountController {
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly impersonationService: ImpersonationService, private readonly impersonationService: ImpersonationService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser @Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService
) {} ) {}
@Delete(':id') @Delete(':id')
@ -137,11 +139,14 @@ export class AccountController {
): Promise<AccountBalancesResponse> { ): Promise<AccountBalancesResponse> {
const impersonationUserId = const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId); await this.impersonationService.validateImpersonationId(impersonationId);
const userId = impersonationUserId || this.request.user.id;
const { settings } = await this.userService.user({ id: userId });
return this.accountBalanceService.getAccountBalances({ return this.accountBalanceService.getAccountBalances({
userId,
filters: [{ id, type: 'ACCOUNT' }], filters: [{ id, type: 'ACCOUNT' }],
userCurrency: this.request.user.settings.settings.baseCurrency, userCurrency: settings.settings.baseCurrency
userId: impersonationUserId || this.request.user.id
}); });
} }

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

@ -1,5 +1,6 @@
import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module'; import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module';
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
@ -23,7 +24,8 @@ import { AccountService } from './account.service';
ImpersonationModule, ImpersonationModule,
PortfolioModule, PortfolioModule,
PrismaModule, PrismaModule,
RedactValuesInResponseModule RedactValuesInResponseModule,
UserModule
], ],
providers: [AccountService] providers: [AccountService]
}) })

11
apps/api/src/app/portfolio/portfolio.controller.ts

@ -1,4 +1,5 @@
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { import {
@ -70,7 +71,8 @@ export class PortfolioController {
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly impersonationService: ImpersonationService, private readonly impersonationService: ImpersonationService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser @Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService
) {} ) {}
@Get('details') @Get('details')
@ -340,7 +342,10 @@ export class PortfolioController {
const impersonationUserId = const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId); await this.impersonationService.validateImpersonationId(impersonationId);
const userCurrency = this.request.user.settings.settings.baseCurrency; const userId = impersonationUserId || this.request.user.id;
const { settings } = await this.userService.user({ id: userId });
const userCurrency = settings.settings.baseCurrency;
const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); const { endDate, startDate } = getIntervalFromDateRange({ dateRange });
@ -349,7 +354,7 @@ export class PortfolioController {
filters, filters,
startDate, startDate,
userCurrency, userCurrency,
userId: impersonationUserId || this.request.user.id, userId,
types: ['DIVIDEND'] types: ['DIVIDEND']
}); });

7
apps/api/src/app/portfolio/portfolio.service.ts

@ -164,7 +164,7 @@ export class PortfolioService {
}; };
} }
const [accounts, details] = await Promise.all([ const [accounts, details, user] = await Promise.all([
this.accountService.accounts({ this.accountService.accounts({
where, where,
include: { include: {
@ -178,10 +178,11 @@ export class PortfolioService {
withExcludedAccounts, withExcludedAccounts,
impersonationId: userId, impersonationId: userId,
userId: this.request.user.id userId: this.request.user.id
}) }),
this.userService.user({ id: userId })
]); ]);
const userCurrency = this.request.user.settings.settings.baseCurrency; const userCurrency = this.getUserCurrency(user);
return Promise.all( return Promise.all(
accounts.map(async (account) => { accounts.map(async (account) => {

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

@ -49,7 +49,7 @@ import { PerformanceCalculationType } from '@ghostfolio/common/types/performance
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; import { EventEmitter2 } from '@nestjs/event-emitter';
import { Prisma, Role, User } from '@prisma/client'; import { Prisma, Role, Settings, User } from '@prisma/client';
import { differenceInDays, subDays } from 'date-fns'; import { differenceInDays, subDays } from 'date-fns';
import { without } from 'lodash'; import { without } from 'lodash';
import { createHmac } from 'node:crypto'; import { createHmac } from 'node:crypto';
@ -109,7 +109,14 @@ export class UserService {
}): Promise<IUser> { }): Promise<IUser> {
const { id, permissions, settings, subscription } = user; const { id, permissions, settings, subscription } = user;
const userData = await Promise.all([ const [
access,
accounts,
activitiesCount,
firstActivity,
impersonationUserSettings,
tagsForUser
] = await Promise.all([
this.prismaService.access.findMany({ this.prismaService.access.findMany({
include: { include: {
user: true user: true
@ -134,16 +141,17 @@ export class UserService {
}, },
where: { userId: impersonationUserId || user.id } where: { userId: impersonationUserId || user.id }
}), }),
impersonationUserId
? this.prismaService.settings.findUnique({
where: { userId: impersonationUserId }
})
: Promise.resolve<Settings>(null),
this.tagService.getTagsForUser(impersonationUserId || user.id) this.tagService.getTagsForUser(impersonationUserId || user.id)
]); ]);
const access = userData[0]; const baseCurrency =
const accounts = userData[1]; (impersonationUserSettings?.settings as UserSettings)?.baseCurrency ??
const activitiesCount = userData[2]; (settings.settings as UserSettings)?.baseCurrency;
const firstActivity = userData[3];
let tags = userData[4].filter((tag) => {
return tag.id !== TAG_ID_EXCLUDE_FROM_ANALYSIS;
});
let systemMessage: SystemMessage; let systemMessage: SystemMessage;
@ -156,6 +164,10 @@ export class UserService {
systemMessage = systemMessageProperty; systemMessage = systemMessageProperty;
} }
let tags = tagsForUser.filter((tag) => {
return tag.id !== TAG_ID_EXCLUDE_FROM_ANALYSIS;
});
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
subscription.type === SubscriptionType.Basic subscription.type === SubscriptionType.Basic
@ -183,6 +195,7 @@ export class UserService {
dateOfFirstActivity: firstActivity?.date ?? new Date(), dateOfFirstActivity: firstActivity?.date ?? new Date(),
settings: { settings: {
...(settings.settings as UserSettings), ...(settings.settings as UserSettings),
baseCurrency,
locale: (settings.settings as UserSettings)?.locale ?? locale locale: (settings.settings as UserSettings)?.locale ?? locale
} }
}; };

Loading…
Cancel
Save