From 9e7c8d3112cb56922917bba410a13f93e56bc6ca Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:41:04 +0200 Subject: [PATCH 1/2] Normalize dates --- apps/api/src/app/admin/admin.controller.ts | 9 ++--- .../market-data/market-data.controller.ts | 33 ++++++++++++++----- .../lib/dtos/update-bulk-market-data.dto.ts | 3 +- .../src/lib/dtos/update-market-data.dto.ts | 5 ++- libs/common/src/lib/helper.ts | 2 +- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 9009ded54..30bc26145 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -19,7 +19,8 @@ import { } from '@ghostfolio/common/dtos'; import { canDeleteAssetProfile, - getAssetProfileIdentifier + getAssetProfileIdentifier, + getStartOfUtcDate } from '@ghostfolio/common/helper'; import { AdminData, @@ -55,7 +56,7 @@ import { import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client'; -import { isDate, parseISO } from 'date-fns'; +import { isValid } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { AdminService } from './admin.service'; @@ -201,9 +202,9 @@ export class AdminController { @Param('dateString') dateString: string, @Param('symbol') symbol: string ): Promise { - const date = parseISO(dateString); + const date = getStartOfUtcDate(dateString); - if (!isDate(date)) { + if (!isValid(date)) { throw new HttpException( getReasonPhrase(StatusCodes.BAD_REQUEST), StatusCodes.BAD_REQUEST diff --git a/apps/api/src/app/endpoints/market-data/market-data.controller.ts b/apps/api/src/app/endpoints/market-data/market-data.controller.ts index 03d50c284..935a2c5f2 100644 --- a/apps/api/src/app/endpoints/market-data/market-data.controller.ts +++ b/apps/api/src/app/endpoints/market-data/market-data.controller.ts @@ -4,7 +4,11 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos'; -import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper'; +import { + getCurrencyFromSymbol, + getStartOfUtcDate, + isCurrency +} from '@ghostfolio/common/helper'; import { MarketDataOfMarketsResponse } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { RequestWithUser } from '@ghostfolio/common/types'; @@ -24,7 +28,7 @@ import { import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { DataSource, Prisma } from '@prisma/client'; -import { parseISO } from 'date-fns'; +import { isValid } from 'date-fns'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; @Controller('market-data') @@ -95,13 +99,24 @@ export class MarketDataController { } const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map( - ({ date, marketPrice }) => ({ - dataSource, - marketPrice, - symbol, - date: parseISO(date), - state: 'CLOSE' - }) + ({ date, marketPrice }) => { + const startOfUtcDate = getStartOfUtcDate(date); + + if (!isValid(startOfUtcDate)) { + throw new HttpException( + getReasonPhrase(StatusCodes.BAD_REQUEST), + StatusCodes.BAD_REQUEST + ); + } + + return { + dataSource, + marketPrice, + symbol, + date: startOfUtcDate, + state: 'CLOSE' + }; + } ); return this.marketDataService.updateMany({ diff --git a/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts b/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts index f92112f24..bc84b30e8 100644 --- a/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts +++ b/libs/common/src/lib/dtos/update-bulk-market-data.dto.ts @@ -1,11 +1,12 @@ import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; import { Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray } from 'class-validator'; +import { ArrayNotEmpty, IsArray, ValidateNested } from 'class-validator'; export class UpdateBulkMarketDataDto { @ArrayNotEmpty() @IsArray() @Type(() => UpdateMarketDataDto) + @ValidateNested({ each: true }) marketData: UpdateMarketDataDto[]; } diff --git a/libs/common/src/lib/dtos/update-market-data.dto.ts b/libs/common/src/lib/dtos/update-market-data.dto.ts index c2a6de11e..30523f440 100644 --- a/libs/common/src/lib/dtos/update-market-data.dto.ts +++ b/libs/common/src/lib/dtos/update-market-data.dto.ts @@ -1,9 +1,8 @@ -import { IsISO8601, IsNumber, IsOptional } from 'class-validator'; +import { IsISO8601, IsNumber } from 'class-validator'; export class UpdateMarketDataDto { @IsISO8601() - @IsOptional() - date?: string; + date: string; @IsNumber() marketPrice: number; diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 44fd89aa2..3663be852 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -403,7 +403,7 @@ export function getNumberFormatGroup(aLocale = getLocale()) { })?.value; } -export function getStartOfUtcDate(aDate: Date) { +export function getStartOfUtcDate(aDate: Date | string) { const date = new Date(aDate); date.setUTCHours(0, 0, 0, 0); From 48d8e17a7db266c0f17d439b2324c9811edbce1b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:42:25 +0200 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc8628c2..9ca953b7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Improved the validation of the market data items in the market data update endpoint + +### Fixed + +- Fixed the date normalization in the market data endpoints in time zones with a non-zero _UTC_ offset + ## 3.45.0 - 2026-08-08 ### Added