From 3b9bb8a195b3a359644f79eec83c7c11f17dc31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20Jord=C3=A3o?= Date: Thu, 28 Aug 2025 12:22:35 +0100 Subject: [PATCH 1/4] add platforms to ImportDataDto --- apps/api/src/app/import/import-data.dto.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index 330bc52f6..904a0799a 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -4,6 +4,7 @@ import { Type } from 'class-transformer'; import { IsArray, IsOptional, ValidateNested } from 'class-validator'; import { CreateTagDto } from '../endpoints/tags/create-tag.dto'; +import { CreatePlatformDto } from '../platform/create-platform.dto'; import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto'; import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto'; @@ -30,4 +31,10 @@ export class ImportDataDto { @Type(() => CreateTagDto) @ValidateNested({ each: true }) tags?: CreateTagDto[]; + + @IsArray() + @IsOptional() + @Type(() => CreatePlatformDto) + @ValidateNested({ each: true }) + platforms?: CreatePlatformDto[]; } From 0281b08374e3f6dda0449d5e1a28e1d1134ed706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20Jord=C3=A3o?= Date: Thu, 28 Aug 2025 12:45:48 +0100 Subject: [PATCH 2/4] verify createPlatform permissions --- apps/api/src/app/import/import.controller.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 2681444df..3b223b4fe 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -56,6 +56,17 @@ export class ImportController { ); } + if ( + importData.platforms && + importData.platforms.length > 0 && + !hasPermission(this.request.user.permissions, permissions.createPlatform) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + let maxActivitiesToImport = this.configurationService.get( 'MAX_ACTIVITIES_TO_IMPORT' ); From b310dfc385bac20a1cd4f596b105d9a6abd4a8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20Jord=C3=A3o?= Date: Thu, 28 Aug 2025 22:41:48 +0100 Subject: [PATCH 3/4] move permission check to import service --- apps/api/src/app/import/import.controller.ts | 23 ++--------------- apps/api/src/app/import/import.service.ts | 25 +++++++++++++++++++ .../src/app/platform/create-platform.dto.ts | 6 ++++- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 3b223b4fe..038ca0455 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -4,7 +4,7 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ImportResponse } from '@ghostfolio/common/interfaces'; -import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { permissions } from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -47,26 +47,6 @@ export class ImportController { ): Promise { const isDryRun = isDryRunParam === 'true'; - if ( - !hasPermission(this.request.user.permissions, permissions.createAccount) - ) { - throw new HttpException( - getReasonPhrase(StatusCodes.FORBIDDEN), - StatusCodes.FORBIDDEN - ); - } - - if ( - importData.platforms && - importData.platforms.length > 0 && - !hasPermission(this.request.user.permissions, permissions.createPlatform) - ) { - throw new HttpException( - getReasonPhrase(StatusCodes.FORBIDDEN), - StatusCodes.FORBIDDEN - ); - } - let maxActivitiesToImport = this.configurationService.get( 'MAX_ACTIVITIES_TO_IMPORT' ); @@ -86,6 +66,7 @@ export class ImportController { activitiesDto: importData.activities, assetProfilesWithMarketDataDto: importData.assetProfiles ?? [], tagsDto: importData.tags ?? [], + platformsDto: importData.platforms ?? [], 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 a96200261..7ff0afaee 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -158,6 +158,7 @@ export class ImportService { isDryRun = false, maxActivitiesToImport, tagsDto, + platformsDto, user }: { accountsWithBalancesDto: ImportDataDto['accounts']; @@ -166,6 +167,7 @@ export class ImportService { isDryRun?: boolean; maxActivitiesToImport: number; tagsDto: ImportDataDto['tags']; + platformsDto: ImportDataDto['platforms']; user: UserWithSettings; }): Promise { const accountIdMapping: { [oldAccountId: string]: string } = {}; @@ -299,6 +301,29 @@ export class ImportService { } } + if (platformsDto?.length) { + const canCreatePlatform = hasPermission( + user.permissions, + permissions.createPlatform + ); + + for (const platform of platformsDto) { + const existingPlatform = await this.platformService.getPlatform({ + id: platform.id + }); + + if (!existingPlatform) { + continue; + } + + if (!canCreatePlatform) { + throw new Error( + `Insufficient permissions to create platform ("${platform.name}")` + ); + } + } + } + if (tagsDto?.length) { const existingTagsOfUser = await this.tagService.getTagsForUser(user.id); diff --git a/apps/api/src/app/platform/create-platform.dto.ts b/apps/api/src/app/platform/create-platform.dto.ts index 941354c11..a8c70c042 100644 --- a/apps/api/src/app/platform/create-platform.dto.ts +++ b/apps/api/src/app/platform/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; From 597c07271df4e124f73e15dbaa757489bdeec368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernardo=20Jord=C3=A3o?= Date: Fri, 29 Aug 2025 12:29:07 +0100 Subject: [PATCH 4/4] sort platform/tag arguments alphabetically --- apps/api/src/app/import/import-data.dto.ts | 8 ++++---- apps/api/src/app/import/import.controller.ts | 2 +- apps/api/src/app/import/import.service.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/api/src/app/import/import-data.dto.ts b/apps/api/src/app/import/import-data.dto.ts index 904a0799a..25d67fe8f 100644 --- a/apps/api/src/app/import/import-data.dto.ts +++ b/apps/api/src/app/import/import-data.dto.ts @@ -28,13 +28,13 @@ export class ImportDataDto { @IsArray() @IsOptional() - @Type(() => CreateTagDto) + @Type(() => CreatePlatformDto) @ValidateNested({ each: true }) - tags?: CreateTagDto[]; + platforms?: CreatePlatformDto[]; @IsArray() @IsOptional() - @Type(() => CreatePlatformDto) + @Type(() => CreateTagDto) @ValidateNested({ each: true }) - platforms?: CreatePlatformDto[]; + tags?: CreateTagDto[]; } diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 038ca0455..41c610a62 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -65,8 +65,8 @@ export class ImportController { accountsWithBalancesDto: importData.accounts ?? [], activitiesDto: importData.activities, assetProfilesWithMarketDataDto: importData.assetProfiles ?? [], - tagsDto: importData.tags ?? [], 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 7ff0afaee..8212d32f1 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -157,8 +157,8 @@ export class ImportService { assetProfilesWithMarketDataDto, isDryRun = false, maxActivitiesToImport, - tagsDto, platformsDto, + tagsDto, user }: { accountsWithBalancesDto: ImportDataDto['accounts']; @@ -166,8 +166,8 @@ export class ImportService { assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; isDryRun?: boolean; maxActivitiesToImport: number; - tagsDto: ImportDataDto['tags']; platformsDto: ImportDataDto['platforms']; + tagsDto: ImportDataDto['tags']; user: UserWithSettings; }): Promise { const accountIdMapping: { [oldAccountId: string]: string } = {};