Browse Source

Bugfix/validation of custom asset profile symbols (#7721)

* Improve validation of activities and asset profiles when combining custom asset profile symbol with data source other than MANUAL

* Update changelog
pull/7724/head
Thomas Kaul 3 days ago
committed by GitHub
parent
commit
5b6536ec85
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 64
      apps/api/src/app/import/import.service.ts
  3. 12
      apps/api/src/services/data-provider/data-provider.service.ts

1
CHANGELOG.md

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Harmonized the icons and labels in the access table to share the portfolio
- Improved the data source column in the historical market data table of the admin control panel by showing the name of the data provider
- Migrated the create and edit access dialogs to dedicated routes
- Improved the validation of activities and asset profiles when combining a custom asset profile symbol with a data source other than `MANUAL`
- Improved the response of the historical market data gathering endpoint for a specific date
- Introduced a timeout for the asset profile and the historical market data gathering jobs
- Reduced the number of attempts of the asset profile and the historical market data gathering jobs

64
apps/api/src/app/import/import.service.ts

@ -3,7 +3,9 @@ import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.ser
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { getTagsWithDraftTag } from '@ghostfolio/api/helper/activity.helper';
import { getMaskedGhostfolioDataSource } from '@ghostfolio/api/helper/data-source.helper';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
@ -56,6 +58,7 @@ export class ImportService {
private readonly accountService: AccountService,
private readonly activitiesService: ActivitiesService,
private readonly apiService: ApiService,
private readonly configurationService: ConfigurationService,
private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
@ -204,6 +207,9 @@ export class ImportService {
}): Promise<Activity[]> {
const accountIdMapping: { [oldAccountId: string]: string } = {};
const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {};
const ghostfolioDataSources = this.configurationService.get(
'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER'
);
const platformIdMapping: { [oldPlatformId: string]: string } = {};
const tagIdMapping: { [oldTagId: string]: string } = {};
const userCurrency = user.settings.settings.baseCurrency;
@ -212,34 +218,64 @@ export class ImportService {
for (const [index, assetProfileWithMarketData] of (
assetProfilesWithMarketDataDto ?? []
).entries()) {
const { dataSource, symbol } = assetProfileWithMarketData;
if (
assetProfileWithMarketData.dataSource === DataSource.MANUAL &&
!isValidCustomAssetProfileSymbol(assetProfileWithMarketData.symbol)
dataSource === DataSource.MANUAL &&
!isValidCustomAssetProfileSymbol(symbol)
) {
throw new Error(
`assetProfiles.${index}.symbol ("${symbol}") must be a UUID or start with the prefix "${ghostfolioPrefix}_" for the data source ("${DataSource.MANUAL}")`
);
} else if (
dataSource !== DataSource.MANUAL &&
isValidCustomAssetProfileSymbol(symbol)
) {
const maskedDataSource = getMaskedGhostfolioDataSource({
dataSource,
ghostfolioDataSources
});
throw new Error(
`assetProfiles.${index}.symbol ("${assetProfileWithMarketData.symbol}") must be a UUID or start with the prefix "${ghostfolioPrefix}_" for the data source ("${DataSource.MANUAL}")`
`assetProfiles.${index}.symbol ("${symbol}") is not valid for the data source ("${maskedDataSource}")`
);
}
}
// Validate the symbols before any data is persisted. Activities without a
// data source are excluded, since a symbol is generated in
// createActivity() if needed.
// Validate the symbols before any data is persisted. Activities without an
// explicit data source are excluded from the first check, since a symbol is
// generated in createActivity() if needed.
for (const [index, activity] of activitiesDto.entries()) {
if (!activity.dataSource) {
if (NON_INVESTMENT_ACTIVITY_TYPES.includes(activity.type)) {
activity.dataSource = DataSource.MANUAL;
} else {
activity.dataSource =
this.dataProviderService.getDataSourceForImport();
}
} else if (
const hasDataSource = Boolean(activity.dataSource);
if (!hasDataSource) {
activity.dataSource = NON_INVESTMENT_ACTIVITY_TYPES.includes(
activity.type
)
? DataSource.MANUAL
: this.dataProviderService.getDataSourceForImport();
}
if (
hasDataSource &&
activity.dataSource === DataSource.MANUAL &&
!isValidCustomAssetProfileSymbol(activity.symbol)
) {
throw new Error(
`activities.${index}.symbol ("${activity.symbol}") must be a UUID or start with the prefix "${ghostfolioPrefix}_" for the data source ("${DataSource.MANUAL}")`
);
} else if (
activity.dataSource !== DataSource.MANUAL &&
isValidCustomAssetProfileSymbol(activity.symbol)
) {
const maskedDataSource = getMaskedGhostfolioDataSource({
ghostfolioDataSources,
dataSource: activity.dataSource
});
throw new Error(
`activities.${index}.symbol ("${activity.symbol}") is not valid for the data source ("${maskedDataSource}")`
);
}
}

12
apps/api/src/services/data-provider/data-provider.service.ts

@ -23,6 +23,7 @@ import {
getStartOfUtcDate,
isCurrency,
isDerivedCurrency,
isValidCustomAssetProfileSymbol,
isValidSearchQuery
} from '@ghostfolio/common/helper';
import {
@ -254,6 +255,15 @@ export class DataProviderService implements OnModuleInit {
);
}
if (
dataSource !== DataSource.MANUAL &&
isValidCustomAssetProfileSymbol(symbol)
) {
throw new Error(
`${activityPath}.symbol ("${symbol}") is not valid for the data source ("${maskedDataSource}")`
);
}
if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
subscription?.type === SubscriptionType.Basic
@ -319,7 +329,7 @@ export class DataProviderService implements OnModuleInit {
if (!assetProfile?.name) {
throw new Error(
`${activityPath}.symbol ("${symbol}") is not valid for the specified data source ("${maskedDataSource}")`
`${activityPath}.symbol ("${symbol}") cannot be resolved by the data source ("${maskedDataSource}")`
);
}

Loading…
Cancel
Save