3 Commits

8 changed files with 31 additions and 17 deletions

View File

@@ -0,0 +1,2 @@
ALTER TABLE current
ALTER COLUMN value DROP NOT NULL;

View File

@@ -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),
);
}

View File

@@ -1,7 +1,7 @@
export type CurrentRow = { export type CurrentRow = {
edge: string; edge: string;
tag: string; tag: string;
value: number; value: number | null;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
name: string | null; name: string | null;

View File

@@ -1,7 +1,7 @@
export type CurrentItemDto = { export type CurrentItemDto = {
edge: string; edge: string;
tag: string; tag: string;
value: number; value: number | null;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
time: Date; time: Date;

View File

@@ -5,5 +5,5 @@ export class IngestEdgeValuesDto {
timestamp!: string; timestamp!: string;
@IsObject() @IsObject()
values!: Record<string, number>; values!: Record<string, number | null>;
} }

View File

@@ -1,5 +1,5 @@
import { Type } from 'class-transformer'; import { IsDateString, IsNotEmpty, IsString } from 'class-validator';
import { IsDateString, IsNotEmpty, IsNumber, IsString } from 'class-validator'; import { IsNullableNumber } from '../../common/is-nullable-number.decorator';
export class IngestPointDto { export class IngestPointDto {
@IsNotEmpty() @IsNotEmpty()
@@ -13,7 +13,6 @@ export class IngestPointDto {
@IsDateString() @IsDateString()
timestamp!: string; timestamp!: string;
@Type(() => Number) @IsNullableNumber()
@IsNumber() value!: number | null;
value!: number;
} }

View File

@@ -22,7 +22,7 @@ export class IngestController {
const points = Object.entries(body.values).map(([tag, value]) => ({ const points = Object.entries(body.values).map(([tag, value]) => ({
edge, edge,
tag, tag,
value: Number(value), value: value === null ? null : Number(value),
timestamp: body.timestamp, timestamp: body.timestamp,
})); }));

View File

@@ -10,8 +10,9 @@ type IngestResult = {
export class IngestService { export class IngestService {
constructor(private readonly db: DbService) {} constructor(private readonly db: DbService) {}
/** Пишет одну входящую точку в history и обновляет текущий снимок current для UI/SSE. */ /** Пишет числовую точку в history и всегда обновляет текущий снимок current для UI/SSE. */
async ingestPoint(point: IngestPointDto): Promise<IngestResult> { async ingestPoint(point: IngestPointDto): Promise<IngestResult> {
if (point.value !== null) {
await this.db.query( await this.db.query(
` `
INSERT INTO history (timestamp, edge, tag, value) INSERT INTO history (timestamp, edge, tag, value)
@@ -19,6 +20,7 @@ export class IngestService {
`, `,
[point.timestamp, point.edge, point.tag, point.value], [point.timestamp, point.edge, point.tag, point.value],
); );
}
await this.db.query( await this.db.query(
` `