diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index e7209fa01..09f0389fb 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -9,6 +9,7 @@ import { AdminData, AdminMarketData, AdminMarketDataDetails, + EnhancedSymbolProfile, Filter } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -21,6 +22,7 @@ import { HttpException, Inject, Param, + Patch, Post, Put, Query, @@ -33,6 +35,7 @@ import { isDate } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; +import { UpdateAssetProfileDto } from './update-asset-profile.dto'; import { UpdateMarketDataDto } from './update-market-data.dto'; @Controller('admin') @@ -332,6 +335,32 @@ export class AdminController { return this.adminService.deleteProfileData({ dataSource, symbol }); } + @Patch('profile-data/:dataSource/:symbol') + @UseGuards(AuthGuard('jwt')) + public async patchProfileData( + @Body() assetProfileData: UpdateAssetProfileDto, + @Param('dataSource') dataSource: DataSource, + @Param('symbol') symbol: string + ): Promise { + if ( + !hasPermission( + this.request.user.permissions, + permissions.accessAdminControl + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.adminService.patchProfileData({ + ...assetProfileData, + dataSource, + symbol + }); + } + @Put('settings/:key') @UseGuards(AuthGuard('jwt')) public async updateProperty( diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index dbf50c767..d57ef1570 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -189,6 +189,27 @@ export class AdminService { }; } + public async patchProfileData({ + dataSource, + symbol, + symbolMapping + }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { + await this.symbolProfileService.updateSymbolProfile({ + dataSource, + symbol, + symbolMapping + }); + + const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([ + { + dataSource, + symbol + } + ]); + + return symbolProfile; + } + public async putSetting(key: string, value: string) { let response: Property; diff --git a/apps/api/src/app/admin/update-asset-profile.dto.ts b/apps/api/src/app/admin/update-asset-profile.dto.ts new file mode 100644 index 000000000..a4767e136 --- /dev/null +++ b/apps/api/src/app/admin/update-asset-profile.dto.ts @@ -0,0 +1,7 @@ +import { IsObject, IsOptional } from 'class-validator'; + +export class UpdateAssetProfileDto { + @IsObject() + @IsOptional() + symbolMapping?: object; +} diff --git a/apps/api/src/services/symbol-profile.service.ts b/apps/api/src/services/symbol-profile.service.ts index ebd388825..edf6528ba 100644 --- a/apps/api/src/services/symbol-profile.service.ts +++ b/apps/api/src/services/symbol-profile.service.ts @@ -8,25 +8,14 @@ import { import { Country } from '@ghostfolio/common/interfaces/country.interface'; import { Sector } from '@ghostfolio/common/interfaces/sector.interface'; import { Injectable } from '@nestjs/common'; -import { - DataSource, - Prisma, - SymbolProfile, - SymbolProfileOverrides -} from '@prisma/client'; +import { Prisma, SymbolProfile, SymbolProfileOverrides } from '@prisma/client'; import { continents, countries } from 'countries-list'; @Injectable() export class SymbolProfileService { public constructor(private readonly prismaService: PrismaService) {} - public async delete({ - dataSource, - symbol - }: { - dataSource: DataSource; - symbol: string; - }) { + public async delete({ dataSource, symbol }: UniqueAsset) { return this.prismaService.symbolProfile.delete({ where: { dataSource_symbol: { dataSource, symbol } } }); @@ -114,6 +103,17 @@ export class SymbolProfileService { .then((symbolProfiles) => this.getSymbols(symbolProfiles)); } + public updateSymbolProfile({ + dataSource, + symbol, + symbolMapping + }: Prisma.SymbolProfileUpdateInput & UniqueAsset) { + return this.prismaService.symbolProfile.update({ + data: { symbolMapping }, + where: { dataSource_symbol: { dataSource, symbol } } + }); + } + private getSymbols( symbolProfiles: (SymbolProfile & { _count: { Order: number }; diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 78e4e5df3..d991e4c53 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -8,6 +8,7 @@ import { } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { EnhancedSymbolProfile } from '@ghostfolio/common/interfaces'; import { MarketData } from '@prisma/client'; @@ -61,11 +62,27 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } public onSubmit() { - const assetProfile = { - symbolMapping: this.assetProfileForm.controls['symbolMapping'].value + let symbolMapping = {}; + + try { + symbolMapping = JSON.parse( + this.assetProfileForm.controls['symbolMapping'].value + ); + } catch {} + + const assetProfileData: UpdateAssetProfileDto = { + symbolMapping }; - console.log(assetProfile); + this.adminService + .patchAssetProfile({ + ...assetProfileData, + dataSource: this.data.dataSource, + symbol: this.data.symbol + }) + .subscribe(() => { + this.initialize(); + }); } public ngOnDestroy() { diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 610be4147..a897df97f 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -1,11 +1,13 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; +import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminJobs, AdminMarketDataDetails, + EnhancedSymbolProfile, UniqueAsset } from '@ghostfolio/common/interfaces'; import { DataSource, MarketData } from '@prisma/client'; @@ -124,6 +126,17 @@ export class AdminService { return this.http.get(url); } + public patchAssetProfile({ + dataSource, + symbol, + symbolMapping + }: UniqueAsset & UpdateAssetProfileDto) { + return this.http.patch( + `/api/v1/admin/profile-data/${dataSource}/${symbol}`, + { symbolMapping } + ); + } + public putMarketData({ dataSource, date,