diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 67e106ff8..95d03da95 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -43,6 +43,7 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; import { UpdateAssetProfileDto } from './update-asset-profile.dto'; +import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto'; import { UpdateMarketDataDto } from './update-market-data.dto'; @Controller('admin') @@ -313,6 +314,43 @@ export class AdminController { return this.adminService.getMarketDataBySymbol({ dataSource, symbol }); } + @UseGuards(AuthGuard('jwt')) + @Post('market-data/:dataSource/:symbol') + public async updateMarketData( + @Param('dataSource') dataSourceParam: DataSource, + @Param('symbol') symbolParam: string, + @Body() data: UpdateBulkMarketDataDto + ) { + console.log('hit post new endoint nice'); + if ( + !hasPermission( + this.request.user.permissions, + permissions.accessAdminControl + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = []; + data.marketData.forEach((entry) => { + dataBulkUpdate.push({ + dataSource: dataSourceParam, + symbol: symbolParam, + date: entry.date, + marketPrice: entry.marketPrice, + state: 'CLOSE' + }); + }); + return this.marketDataService.updateMany({ + data: dataBulkUpdate + }); + } + + /** + * @deprecated + */ @Put('market-data/:dataSource/:symbol/:dateString') @UseGuards(AuthGuard('jwt')) public async update( diff --git a/apps/api/src/app/admin/update-bulk-market-data.dto.ts b/apps/api/src/app/admin/update-bulk-market-data.dto.ts new file mode 100644 index 000000000..721d49c76 --- /dev/null +++ b/apps/api/src/app/admin/update-bulk-market-data.dto.ts @@ -0,0 +1,10 @@ +import { ArrayNotEmpty, IsArray, isNotEmptyObject } from 'class-validator'; +import { Type } from 'class-transformer'; +import { UpdateMarketDataDto } from './update-market-data.dto'; + +export class UpdateBulkMarketDataDto { + @IsArray() + @ArrayNotEmpty() + @Type(() => UpdateMarketDataDto) + marketData: UpdateMarketDataDto[]; +} diff --git a/apps/api/src/app/admin/update-market-data.dto.ts b/apps/api/src/app/admin/update-market-data.dto.ts index 79779a318..c0463de31 100644 --- a/apps/api/src/app/admin/update-market-data.dto.ts +++ b/apps/api/src/app/admin/update-market-data.dto.ts @@ -1,6 +1,10 @@ -import { IsNumber } from 'class-validator'; +import { IsDate, IsNumber, IsOptional } from 'class-validator'; export class UpdateMarketDataDto { + @IsDate() + @IsOptional() + date?: Date; + @IsNumber() marketPrice: 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 792025e9b..db1f4ef98 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,7 +8,10 @@ import { } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { parseISO } from 'date-fns'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; +import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; + import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { @@ -43,6 +46,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { [code: string]: { name: string; value: number }; }; public isBenchmark = false; + public historicalDataAsString: string; public marketDataDetails: MarketData[] = []; public sectors: { [name: string]: { name: string; value: number }; @@ -203,4 +207,31 @@ export class AssetProfileDialog implements OnDestroy, OnInit { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } + + public importHistoricalData() { + const inputHistoricalData = this.historicalDataAsString; + const inputSplittedByLine = inputHistoricalData.split('\n'); + const dataBulkUpdate: UpdateMarketDataDto[] = []; + inputSplittedByLine.forEach((line) => { + const inputSplittedBySeparator = line.split(';'); + const inputDate = parseISO(inputSplittedBySeparator[0]); + dataBulkUpdate.push({ + date: inputDate, + marketPrice: Number(inputSplittedBySeparator[1]) + }); + }); + + this.adminService + .postMarketData({ + dataSource: this.data.dataSource, + marketData: { marketData: dataBulkUpdate }, + symbol: this.data.symbol + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.dataService.updateInfo(); + }); + + this.historicalDataAsString = ''; + } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 6682d004d..0dab9f0e5 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -51,6 +51,28 @@ [symbol]="data.symbol" (marketDataChanged)="onMarketDataChanged($event)" > + +
+ + Historical Data + + +
+ +
+ +
+
(url, marketData); + } + public postPlatform(aPlatform: CreatePlatformDto) { return this.http.post(`/api/v1/platform`, aPlatform); }