From 22529ba0e85e5f9b98f33329f6ea3b9ac6c454a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Sun, 5 Jul 2026 22:03:47 +0300 Subject: [PATCH 1/2] =?UTF-8?q?BUR-61=20current.value=20=D1=81=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BD=20nullable=20=D0=B2=20=D0=B1=D0=B4.=20=D0=A1?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D1=83?= =?UTF-8?q?=D1=8E=D1=89=D0=B8=D0=B5=20=D1=81=D1=83=D1=89=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D1=81=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B1=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/0002_current_value_nullable.sql | 2 ++ src/common/is-nullable-number.decorator.ts | 11 +++++++++++ src/current/current.types.ts | 2 +- src/current/dto/current-response.dto.ts | 2 +- src/ingest/dto/ingest-edge-values.dto.ts | 2 +- src/ingest/dto/ingest-point.dto.ts | 9 ++++----- src/ingest/ingest.controller.ts | 2 +- src/ingest/ingest.service.ts | 18 ++++++++++-------- 8 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 migrations/0002_current_value_nullable.sql create mode 100644 src/common/is-nullable-number.decorator.ts diff --git a/migrations/0002_current_value_nullable.sql b/migrations/0002_current_value_nullable.sql new file mode 100644 index 0000000..2c4e961 --- /dev/null +++ b/migrations/0002_current_value_nullable.sql @@ -0,0 +1,2 @@ +ALTER TABLE current + ALTER COLUMN value DROP NOT NULL; diff --git a/src/common/is-nullable-number.decorator.ts b/src/common/is-nullable-number.decorator.ts new file mode 100644 index 0000000..96106db --- /dev/null +++ b/src/common/is-nullable-number.decorator.ts @@ -0,0 +1,11 @@ +import { applyDecorators } from '@nestjs/common'; +import { Transform } from 'class-transformer'; +import { IsNumber, ValidateIf, ValidationOptions } from 'class-validator'; + +export function IsNullableNumber(options?: ValidationOptions): PropertyDecorator { + return applyDecorators( + Transform(({ value }) => (value === null ? null : Number(value))), + ValidateIf((_, value) => value !== null), + IsNumber({}, options), + ); +} diff --git a/src/current/current.types.ts b/src/current/current.types.ts index e62ebf0..663707f 100644 --- a/src/current/current.types.ts +++ b/src/current/current.types.ts @@ -1,7 +1,7 @@ export type CurrentRow = { edge: string; tag: string; - value: number; + value: number | null; createdAt: Date; updatedAt: Date; name: string | null; diff --git a/src/current/dto/current-response.dto.ts b/src/current/dto/current-response.dto.ts index c0ed131..d368a4e 100644 --- a/src/current/dto/current-response.dto.ts +++ b/src/current/dto/current-response.dto.ts @@ -1,7 +1,7 @@ export type CurrentItemDto = { edge: string; tag: string; - value: number; + value: number | null; createdAt: Date; updatedAt: Date; time: Date; diff --git a/src/ingest/dto/ingest-edge-values.dto.ts b/src/ingest/dto/ingest-edge-values.dto.ts index fabdd5a..c49fb58 100644 --- a/src/ingest/dto/ingest-edge-values.dto.ts +++ b/src/ingest/dto/ingest-edge-values.dto.ts @@ -5,5 +5,5 @@ export class IngestEdgeValuesDto { timestamp!: string; @IsObject() - values!: Record; + values!: Record; } diff --git a/src/ingest/dto/ingest-point.dto.ts b/src/ingest/dto/ingest-point.dto.ts index 8d1d7c4..d193354 100644 --- a/src/ingest/dto/ingest-point.dto.ts +++ b/src/ingest/dto/ingest-point.dto.ts @@ -1,5 +1,5 @@ -import { Type } from 'class-transformer'; -import { IsDateString, IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { IsDateString, IsNotEmpty, IsString } from 'class-validator'; +import { IsNullableNumber } from '../../common/is-nullable-number.decorator'; export class IngestPointDto { @IsNotEmpty() @@ -13,7 +13,6 @@ export class IngestPointDto { @IsDateString() timestamp!: string; - @Type(() => Number) - @IsNumber() - value!: number; + @IsNullableNumber() + value!: number | null; } diff --git a/src/ingest/ingest.controller.ts b/src/ingest/ingest.controller.ts index 90a9129..3b8180d 100644 --- a/src/ingest/ingest.controller.ts +++ b/src/ingest/ingest.controller.ts @@ -22,7 +22,7 @@ export class IngestController { const points = Object.entries(body.values).map(([tag, value]) => ({ edge, tag, - value: Number(value), + value: value === null ? null : Number(value), timestamp: body.timestamp, })); diff --git a/src/ingest/ingest.service.ts b/src/ingest/ingest.service.ts index 3c1d3f6..8130679 100644 --- a/src/ingest/ingest.service.ts +++ b/src/ingest/ingest.service.ts @@ -10,15 +10,17 @@ type IngestResult = { export class IngestService { constructor(private readonly db: DbService) {} - /** Пишет одну входящую точку в history и обновляет текущий снимок current для UI/SSE. */ + /** Пишет числовую точку в history и всегда обновляет текущий снимок current для UI/SSE. */ async ingestPoint(point: IngestPointDto): Promise { - await this.db.query( - ` - INSERT INTO history (timestamp, edge, tag, value) - VALUES ($1, $2, $3, $4) - `, - [point.timestamp, point.edge, point.tag, point.value], - ); + if (point.value !== null) { + await this.db.query( + ` + INSERT INTO history (timestamp, edge, tag, value) + VALUES ($1, $2, $3, $4) + `, + [point.timestamp, point.edge, point.tag, point.value], + ); + } await this.db.query( ` -- 2.49.1 From b2d3bbddfc0929cb849d1bdb77ece0cdd4db96e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=BE=D0=B2=20=D0=90=D1=80=D1=82?= =?UTF-8?q?=D0=B5=D0=BC?= Date: Mon, 6 Jul 2026 17:02:35 +0300 Subject: [PATCH 2/2] =?UTF-8?q?BUR-61=20=D0=98=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8F=20value=20=D0=B2=20ingest.controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ingest/ingest.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ingest/ingest.controller.ts b/src/ingest/ingest.controller.ts index 3b8180d..faf3cf9 100644 --- a/src/ingest/ingest.controller.ts +++ b/src/ingest/ingest.controller.ts @@ -22,7 +22,7 @@ export class IngestController { const points = Object.entries(body.values).map(([tag, value]) => ({ edge, tag, - value: value === null ? null : Number(value), + value, timestamp: body.timestamp, })); -- 2.49.1