Browse Source

Bugfix/validation of update activity endpoint (#8345)

* Fix validation of update activity endpoint

* Update changelog
pull/8352/head
Thomas Kaul 4 days ago
committed by GitHub
parent
commit
abc7b59e3c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 106
      apps/api/src/app/endpoints/activities/activities.controller.ts

1
CHANGELOG.md

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### 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 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 ## 3.94.0 - 2026-10-28

106
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 { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; 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 { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import { SubscriptionType } from '@ghostfolio/common/enums'; import { SubscriptionType } from '@ghostfolio/common/enums';
import { import {
@ -241,36 +244,12 @@ export class ActivitiesController {
userSubscription userSubscription
}: ImpersonationContext }: ImpersonationContext
): Promise<CreateActivityResponse> { ): Promise<CreateActivityResponse> {
// Evaluate the more restrictive subscription of the authenticated user await this.validateActivity({
// and the owner of the activity authenticatedUserSubscription,
const subscription =
userSubscription?.type === SubscriptionType.Basic
? userSubscription
: authenticatedUserSubscription;
try {
await this.dataProviderService.validateActivities({
subscription,
userId, userId,
activitiesDto: [ userSubscription,
{ activityDto: data
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
);
}
const currency = data.currency; const currency = data.currency;
const customCurrency = data.customCurrency; const customCurrency = data.customCurrency;
@ -344,7 +323,12 @@ export class ActivitiesController {
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
public async updateActivity( public async updateActivity(
@Body() data: UpdateOrderDto, @Body() data: UpdateOrderDto,
@Impersonation() { userId }: ImpersonationContext, @Impersonation()
{
authenticatedUserSubscription,
userId,
userSubscription
}: ImpersonationContext,
@Param('id') id: string @Param('id') id: string
): Promise<UpdateActivityResponse> { ): Promise<UpdateActivityResponse> {
const originalActivity = await this.activitiesService.order({ 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 date = parseISO(data.date);
const accountId = data.accountId; const accountId = data.accountId;
@ -428,4 +435,43 @@ export class ActivitiesController {
'userId' '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
);
}
}
} }

Loading…
Cancel
Save