Compare commits
8 Commits
5260b8a9c5
...
bur-58
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
203bdf193e | ||
|
|
3bf4cebbe6 | ||
|
|
907c24c45b | ||
|
|
b057ea154a | ||
|
|
0e2a7eeeec | ||
|
|
c702946393 | ||
| 32ea0d7c4d | |||
| 15fe566d53 |
19
docker-compose.dev.yml
Normal file
19
docker-compose.dev.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
services:
|
||||
drill-cloud-v3-dev:
|
||||
container_name: container-drill-cloud-v3${branch:+-${branch}}
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: ${PORT}
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
INGEST_API_KEY: ${INGEST_API_KEY}
|
||||
CURRENT_EVENTS_POLL_MS: ${CURRENT_EVENTS_POLL_MS}
|
||||
expose:
|
||||
- "3101"
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
drill-cloud-v3:
|
||||
container_name: drill-cloud-v3${branch:+-${branch}}
|
||||
container_name: container-drill-cloud-v3${branch:+-${branch}}
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -8,7 +8,6 @@ services:
|
||||
PORT: ${PORT}
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
INGEST_API_KEY: ${INGEST_API_KEY}
|
||||
INGEST_DEBUG_LOG: ${INGEST_DEBUG_LOG:-}
|
||||
CURRENT_EVENTS_POLL_MS: ${CURRENT_EVENTS_POLL_MS}
|
||||
expose:
|
||||
- "3101"
|
||||
|
||||
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);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Body, Controller, Logger, Param, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { IngestApiKeyGuard } from '../common/ingest-api-key.guard';
|
||||
import { IngestEdgeValuesDto } from './dto/ingest-edge-values.dto';
|
||||
import { IngestPointDto } from './dto/ingest-point.dto';
|
||||
@@ -8,19 +7,10 @@ import { IngestService } from './ingest.service';
|
||||
@Controller('ingest')
|
||||
@UseGuards(IngestApiKeyGuard)
|
||||
export class IngestController {
|
||||
private readonly logger = new Logger(IngestController.name);
|
||||
|
||||
constructor(private readonly ingest: IngestService) {}
|
||||
|
||||
@Post()
|
||||
ingestPoint(@Body() point: IngestPointDto, @Req() request: Request) {
|
||||
this.logIngestRequest(request, {
|
||||
kind: 'point',
|
||||
edge: point.edge,
|
||||
tag: point.tag,
|
||||
timestamp: point.timestamp,
|
||||
});
|
||||
|
||||
ingestPoint(@Body() point: IngestPointDto) {
|
||||
return this.ingest.ingestPoint(point);
|
||||
}
|
||||
|
||||
@@ -28,17 +18,7 @@ export class IngestController {
|
||||
ingestEdgeValues(
|
||||
@Param('edge') edge: string,
|
||||
@Body() body: IngestEdgeValuesDto,
|
||||
@Req() request: Request,
|
||||
) {
|
||||
const tags = Object.keys(body.values);
|
||||
this.logIngestRequest(request, {
|
||||
kind: 'edge-values',
|
||||
edge,
|
||||
tagCount: tags.length,
|
||||
firstTags: tags.slice(0, 5),
|
||||
timestamp: body.timestamp,
|
||||
});
|
||||
|
||||
const points = Object.entries(body.values).map(([tag, value]) => ({
|
||||
edge,
|
||||
tag,
|
||||
@@ -48,33 +28,4 @@ export class IngestController {
|
||||
|
||||
return this.ingest.ingestPoints(points);
|
||||
}
|
||||
|
||||
private logIngestRequest(
|
||||
request: Request,
|
||||
payload: Record<string, unknown>,
|
||||
): void {
|
||||
if (!this.isDebugLogEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log({
|
||||
event: 'ingest.received',
|
||||
method: request.method,
|
||||
url: request.originalUrl,
|
||||
host: request.get('host'),
|
||||
ip: request.ip,
|
||||
remoteAddress: request.socket.remoteAddress,
|
||||
forwardedFor: request.get('x-forwarded-for'),
|
||||
realIp: request.get('x-real-ip'),
|
||||
userAgent: request.get('user-agent'),
|
||||
contentLength: request.get('content-length'),
|
||||
...payload,
|
||||
});
|
||||
}
|
||||
|
||||
private isDebugLogEnabled(): boolean {
|
||||
return ['1', 'true', 'yes', 'on'].includes(
|
||||
(process.env.INGEST_DEBUG_LOG ?? '').toLowerCase(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { NestFactory } from '@nestjs/core';
|
||||
import compression from 'compression';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
/** Запускает HTTP API со сжатием и глобальной валидацией DTO. */
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.use(compression());
|
||||
|
||||
Reference in New Issue
Block a user