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..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: Number(value), + 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( `