diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index bf45c7cda7..1ab6fe3e5d 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -2,6 +2,7 @@ import { CreateAccountWithBalancesDto, CreateAssetProfileWithMarketDataDto, CreateOrderDto, + CreatePlatformDto, CreateTagDto } from '@ghostfolio/common/dtos'; @@ -26,6 +27,12 @@ export class ImportDataDto { @ValidateNested({ each: true }) assetProfiles?: CreateAssetProfileWithMarketDataDto[]; + @IsArray() + @IsOptional() + @Type(() => CreatePlatformDto) + @ValidateNested({ each: true }) + platforms?: CreatePlatformDto[]; + @IsArray() @IsOptional() @Type(() => CreateTagDto) diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index c2d53e3cb3..cd378d07df 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -77,6 +77,7 @@ export class ImportController { accountsWithBalancesDto: importData.accounts ?? [], activitiesDto: importData.activities, assetProfilesWithMarketDataDto: importData.assetProfiles ?? [], + platformsDto: importData.platforms ?? [], tagsDto: importData.tags ?? [], user: this.request.user }); diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index b8579b795d..23b5f7c1a0 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -177,6 +177,7 @@ export class ImportService { assetProfilesWithMarketDataDto, isDryRun = false, maxActivitiesToImport, + platformsDto, tagsDto, user }: { @@ -185,14 +186,49 @@ export class ImportService { assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; isDryRun?: boolean; maxActivitiesToImport: number; + platformsDto: ImportDataDto['platforms']; tagsDto: ImportDataDto['tags']; user: UserWithSettings; }): Promise { const accountIdMapping: { [oldAccountId: string]: string } = {}; const assetProfileSymbolMapping: { [oldSymbol: string]: string } = {}; + const platformIdMapping: { [oldPlatformId: string]: string } = {}; const tagIdMapping: { [oldTagId: string]: string } = {}; const userCurrency = user.settings.settings.baseCurrency; + if (platformsDto?.length) { + const canCreatePlatform = hasPermission( + user.permissions, + permissions.createPlatform + ); + + const existingPlatforms = await this.platformService.getPlatforms(); + + for (const platform of platformsDto) { + // Check if there is any existing platform with the same ID or URL + const existingPlatform = existingPlatforms.find(({ id, url }) => { + return id === platform.id || url === platform.url; + }); + + if (existingPlatform) { + // Store the new to old platform ID mappings for creating accounts + if (platform.id && existingPlatform.id !== platform.id) { + platformIdMapping[platform.id] = existingPlatform.id; + } + } else { + if (!canCreatePlatform) { + throw new Error( + `Insufficient permissions to create platform ("${platform.name}")` + ); + } + + if (!isDryRun) { + await this.platformService.createPlatform(platform); + } + } + } + } + const existingTagsOfUser = tagsDto?.length || (!isDryRun && accountsWithBalancesDto?.length) ? await this.tagService.getTagsForUser(user.id) @@ -282,7 +318,8 @@ export class ImportService { ]); let oldAccountId: string; - const platformId = account.platformId; + const platformId = + platformIdMapping[account.platformId] ?? account.platformId; delete account.platformId; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 77d889b1a6..48f1d5abd0 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -4,6 +4,7 @@ import { DEFAULT_DATE_RANGE } from '@ghostfolio/common/config'; import { CreateAccountWithBalancesDto, CreateAssetProfileWithMarketDataDto, + CreatePlatformDto, CreateTagDto } from '@ghostfolio/common/dtos'; import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces'; @@ -107,6 +108,7 @@ export class GfImportActivitiesDialogComponent { private accounts: CreateAccountWithBalancesDto[] = []; private activities: Activity[] = []; private assetProfiles: CreateAssetProfileWithMarketDataDto[] = []; + private platforms: CreatePlatformDto[] = []; private tags: CreateTagDto[] = []; private readonly changeDetectorRef = inject(ChangeDetectorRef); @@ -173,6 +175,7 @@ export class GfImportActivitiesDialogComponent { accounts: this.accounts, activities: this.selectedActivities, assetProfiles: this.assetProfiles, + platforms: this.platforms, tags: this.tags }); @@ -304,6 +307,7 @@ export class GfImportActivitiesDialogComponent { this.accounts = content.accounts; this.assetProfiles = content.assetProfiles; + this.platforms = content.platforms; this.tags = content.tags; if (!isArray(content.activities)) { @@ -337,6 +341,7 @@ export class GfImportActivitiesDialogComponent { activities: content.activities, assetProfiles: content.assetProfiles, isDryRun: true, + platforms: content.platforms, tags: content.tags }); diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index ff56797184..6a8f99bdad 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -2,6 +2,7 @@ import { CreateAccountWithBalancesDto, CreateAssetProfileWithMarketDataDto, CreateOrderDto, + CreatePlatformDto, CreateTagDto } from '@ghostfolio/common/dtos'; import { parseDate as parseDateHelper } from '@ghostfolio/common/helper'; @@ -115,12 +116,14 @@ export class ImportActivitiesService { activities, assetProfiles, isDryRun = false, + platforms, tags }: { activities: CreateOrderDto[]; accounts?: CreateAccountWithBalancesDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[]; isDryRun?: boolean; + platforms?: CreatePlatformDto[]; tags?: CreateTagDto[]; }): Promise<{ activities: Activity[]; @@ -131,6 +134,7 @@ export class ImportActivitiesService { accounts, activities, assetProfiles, + platforms, tags }, isDryRun @@ -142,11 +146,13 @@ export class ImportActivitiesService { accounts, activities, assetProfiles, + platforms, tags }: { accounts?: CreateAccountWithBalancesDto[]; activities: Activity[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[]; + platforms?: CreatePlatformDto[]; tags?: CreateTagDto[]; }): Promise<{ activities: Activity[]; @@ -158,6 +164,7 @@ export class ImportActivitiesService { return this.importJson({ accounts, assetProfiles, + platforms, tags, activities: importData }); @@ -457,6 +464,7 @@ export class ImportActivitiesService { accounts?: CreateAccountWithBalancesDto[]; activities: CreateOrderDto[]; assetProfiles?: CreateAssetProfileWithMarketDataDto[]; + platforms?: CreatePlatformDto[]; tags?: CreateTagDto[]; }, aIsDryRun = false diff --git a/libs/common/src/lib/dtos/create-platform.dto.ts b/libs/common/src/lib/dtos/create-platform.dto.ts index e482dd2413..5c6453f846 100644 --- a/libs/common/src/lib/dtos/create-platform.dto.ts +++ b/libs/common/src/lib/dtos/create-platform.dto.ts @@ -1,6 +1,10 @@ -import { IsString, IsUrl } from 'class-validator'; +import { IsOptional, IsString, IsUrl } from 'class-validator'; export class CreatePlatformDto { + @IsOptional() + @IsString() + id?: string; + @IsString() name: string;