diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d66b2815..3fbd3700ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the sign-out logic which deleted all cookies of the origin and did not delete the _Bull Board_ cookie on plain HTTP +- Fixed the validation of the asset profile in the update activity endpoint ## 3.94.0 - 2026-10-28 diff --git a/apps/api/src/app/endpoints/activities/activities.controller.ts b/apps/api/src/app/endpoints/activities/activities.controller.ts index 6ac820874e..80f2fdd474 100644 --- a/apps/api/src/app/endpoints/activities/activities.controller.ts +++ b/apps/api/src/app/endpoints/activities/activities.controller.ts @@ -12,7 +12,10 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; -import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config'; +import { + DATA_GATHERING_QUEUE_PRIORITY_HIGH, + NON_INVESTMENT_ACTIVITY_TYPES +} from '@ghostfolio/common/config'; import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { SubscriptionType } from '@ghostfolio/common/enums'; import { @@ -241,36 +244,12 @@ export class ActivitiesController { userSubscription }: ImpersonationContext ): Promise { - // Evaluate the more restrictive subscription of the authenticated user - // and the owner of the activity - const subscription = - userSubscription?.type === SubscriptionType.Basic - ? userSubscription - : authenticatedUserSubscription; - - try { - await this.dataProviderService.validateActivities({ - subscription, - userId, - activitiesDto: [ - { - currency: data.currency, - dataSource: data.dataSource, - symbol: data.symbol, - type: data.type - } - ], - maxActivitiesToImport: 1 - }); - } catch (error) { - throw new HttpException( - { - error: getReasonPhrase(StatusCodes.BAD_REQUEST), - message: [error.message] - }, - StatusCodes.BAD_REQUEST - ); - } + await this.validateActivity({ + authenticatedUserSubscription, + userId, + userSubscription, + activityDto: data + }); const currency = data.currency; const customCurrency = data.customCurrency; @@ -344,7 +323,12 @@ export class ActivitiesController { @UseInterceptors(TransformDataSourceInRequestInterceptor) public async updateActivity( @Body() data: UpdateOrderDto, - @Impersonation() { userId }: ImpersonationContext, + @Impersonation() + { + authenticatedUserSubscription, + userId, + userSubscription + }: ImpersonationContext, @Param('id') id: string ): Promise { const originalActivity = await this.activitiesService.order({ @@ -359,6 +343,29 @@ export class ActivitiesController { ); } + const isAssetProfileConnected = + !NON_INVESTMENT_ACTIVITY_TYPES.includes(data.type) && + !(data.dataSource === 'MANUAL' && data.type === 'BUY'); + + if (isAssetProfileConnected) { + const [originalAssetProfile] = + await this.symbolProfileService.getSymbolProfilesByIds([ + originalActivity.symbolProfileId + ]); + + if ( + originalAssetProfile.dataSource !== data.dataSource || + originalAssetProfile.symbol !== data.symbol + ) { + await this.validateActivity({ + authenticatedUserSubscription, + userId, + userSubscription, + activityDto: data + }); + } + } + const date = parseISO(data.date); const accountId = data.accountId; @@ -428,4 +435,43 @@ export class ActivitiesController { 'userId' ]); } + + private async validateActivity({ + activityDto: { currency, dataSource, symbol, type }, + authenticatedUserSubscription, + userId, + userSubscription + }: Pick< + ImpersonationContext, + 'authenticatedUserSubscription' | 'userId' | 'userSubscription' + > & { + activityDto: Pick< + CreateOrderDto, + 'currency' | 'dataSource' | 'symbol' | 'type' + >; + }) { + // Evaluate the more restrictive subscription of the authenticated user + // and the owner of the activity + const subscription = + userSubscription?.type === SubscriptionType.Basic + ? userSubscription + : authenticatedUserSubscription; + + try { + await this.dataProviderService.validateActivities({ + subscription, + userId, + activitiesDto: [{ currency, dataSource, symbol, type }], + maxActivitiesToImport: 1 + }); + } catch (error) { + throw new HttpException( + { + error: getReasonPhrase(StatusCodes.BAD_REQUEST), + message: [error.message] + }, + StatusCodes.BAD_REQUEST + ); + } + } }