Compare commits
7 Commits
docker-imp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b6802c5e1 | |||
| 0859a7a381 | |||
|
|
b2d3bbddfc | ||
| 54c8bf2e01 | |||
|
|
22529ba0e8 | ||
| 387f6c0613 | |||
|
|
203bdf193e |
8
migrations/0001_camera.sql
Normal file
8
migrations/0001_camera.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS camera (
|
||||
edge varchar NOT NULL,
|
||||
protocol varchar NOT NULL,
|
||||
source varchar NOT NULL,
|
||||
PRIMARY KEY (edge, protocol, source)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS camera_edge_idx ON camera (edge);
|
||||
2
migrations/0002_current_value_nullable.sql
Normal file
2
migrations/0002_current_value_nullable.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE current
|
||||
ALTER COLUMN value DROP NOT NULL;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CameraModule } from './camera/camera.module';
|
||||
import { CurrentModule } from './current/current.module';
|
||||
import { DbModule } from './db/db.module';
|
||||
import { EdgeModule } from './edge/edge.module';
|
||||
@@ -10,6 +11,7 @@ import { TagModule } from './tag/tag.module';
|
||||
@Module({
|
||||
imports: [
|
||||
DbModule,
|
||||
CameraModule,
|
||||
IngestModule,
|
||||
EdgeModule,
|
||||
CurrentModule,
|
||||
|
||||
16
src/camera/camera.controller.ts
Normal file
16
src/camera/camera.controller.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Controller, Get, Header, Query } from '@nestjs/common';
|
||||
import { CameraService } from './camera.service';
|
||||
import { GetCamerasDto } from './dto/get-cameras.dto';
|
||||
|
||||
@Controller('camera')
|
||||
export class CameraController {
|
||||
constructor(private readonly camera: CameraService) {}
|
||||
|
||||
@Get()
|
||||
@Header('Cache-Control', 'no-store')
|
||||
@Header('Pragma', 'no-cache')
|
||||
@Header('Expires', '0')
|
||||
findByEdge(@Query() query: GetCamerasDto) {
|
||||
return this.camera.findByEdge(query);
|
||||
}
|
||||
}
|
||||
12
src/camera/camera.mapper.ts
Normal file
12
src/camera/camera.mapper.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CameraResponseDto } from './dto/camera-response.dto';
|
||||
import { CameraRow } from './camera.types';
|
||||
|
||||
export function createCameraResponse(edge: string, rows: CameraRow[]): CameraResponseDto {
|
||||
return {
|
||||
edge,
|
||||
items: rows.map((row) => ({
|
||||
protocol: row.protocol,
|
||||
source: row.source,
|
||||
})),
|
||||
};
|
||||
}
|
||||
10
src/camera/camera.module.ts
Normal file
10
src/camera/camera.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CameraController } from './camera.controller';
|
||||
import { CameraRepository } from './camera.repository';
|
||||
import { CameraService } from './camera.service';
|
||||
|
||||
@Module({
|
||||
controllers: [CameraController],
|
||||
providers: [CameraService, CameraRepository],
|
||||
})
|
||||
export class CameraModule {}
|
||||
22
src/camera/camera.repository.ts
Normal file
22
src/camera/camera.repository.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DbService } from '../db/db.service';
|
||||
import { CameraRow } from './camera.types';
|
||||
|
||||
@Injectable()
|
||||
export class CameraRepository {
|
||||
constructor(private readonly db: DbService) {}
|
||||
|
||||
async findByEdge(edge: string): Promise<CameraRow[]> {
|
||||
const result = await this.db.query<CameraRow>(
|
||||
`
|
||||
SELECT protocol, source
|
||||
FROM camera
|
||||
WHERE edge = $1
|
||||
ORDER BY source ASC
|
||||
`,
|
||||
[edge],
|
||||
);
|
||||
|
||||
return result.rows;
|
||||
}
|
||||
}
|
||||
15
src/camera/camera.service.ts
Normal file
15
src/camera/camera.service.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CameraResponseDto } from './dto/camera-response.dto';
|
||||
import { GetCamerasDto } from './dto/get-cameras.dto';
|
||||
import { createCameraResponse } from './camera.mapper';
|
||||
import { CameraRepository } from './camera.repository';
|
||||
|
||||
@Injectable()
|
||||
export class CameraService {
|
||||
constructor(private readonly repository: CameraRepository) {}
|
||||
|
||||
async findByEdge(query: GetCamerasDto): Promise<CameraResponseDto> {
|
||||
const rows = await this.repository.findByEdge(query.edge);
|
||||
return createCameraResponse(query.edge, rows);
|
||||
}
|
||||
}
|
||||
4
src/camera/camera.types.ts
Normal file
4
src/camera/camera.types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type CameraRow = {
|
||||
protocol: string;
|
||||
source: string;
|
||||
};
|
||||
9
src/camera/dto/camera-response.dto.ts
Normal file
9
src/camera/dto/camera-response.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type CameraItemDto = {
|
||||
protocol: string;
|
||||
source: string;
|
||||
};
|
||||
|
||||
export type CameraResponseDto = {
|
||||
edge: string;
|
||||
items: CameraItemDto[];
|
||||
};
|
||||
7
src/camera/dto/get-cameras.dto.ts
Normal file
7
src/camera/dto/get-cameras.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class GetCamerasDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
edge!: string;
|
||||
}
|
||||
11
src/common/is-nullable-number.decorator.ts
Normal file
11
src/common/is-nullable-number.decorator.ts
Normal 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),
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export type CurrentRow = {
|
||||
edge: string;
|
||||
tag: string;
|
||||
value: number;
|
||||
value: number | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string | null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type CurrentItemDto = {
|
||||
edge: string;
|
||||
tag: string;
|
||||
value: number;
|
||||
value: number | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
time: Date;
|
||||
|
||||
@@ -5,5 +5,5 @@ export class IngestEdgeValuesDto {
|
||||
timestamp!: string;
|
||||
|
||||
@IsObject()
|
||||
values!: Record<string, number>;
|
||||
values!: Record<string, number | null>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ type IngestResult = {
|
||||
export class IngestService {
|
||||
constructor(private readonly db: DbService) {}
|
||||
|
||||
/** Пишет одну входящую точку в history и обновляет текущий снимок current для UI/SSE. */
|
||||
/** Пишет числовую точку в history и всегда обновляет текущий снимок current для UI/SSE. */
|
||||
async ingestPoint(point: IngestPointDto): Promise<IngestResult> {
|
||||
if (point.value !== null) {
|
||||
await this.db.query(
|
||||
`
|
||||
INSERT INTO history (timestamp, edge, tag, value)
|
||||
@@ -19,6 +20,7 @@ export class IngestService {
|
||||
`,
|
||||
[point.timestamp, point.edge, point.tag, point.value],
|
||||
);
|
||||
}
|
||||
|
||||
await this.db.query(
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user