add mapper. add v2 handler. add tag description short and full versions. delete topic[1-5]. #5
@@ -3,8 +3,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "node --env-file-if-exists=.env ./node_modules/tsx/dist/cli.mjs watch src/index.ts",
|
||||||
|
NBizyaev marked this conversation as resolved
Outdated
|
|||||||
"start": "tsx src/index.ts",
|
"start": "node --env-file-if-exists=.env ./node_modules/tsx/dist/cli.mjs src/index.ts",
|
||||||
|
NBizyaev marked this conversation as resolved
Outdated
NBizyaev
commented
Скрипт start тут вообще не нужен. а переменные окружения возьмутся собственно из окружения (например из портейнера) Скрипт start тут вообще не нужен.
В продакшне
```
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build # tsc → dist/
CMD ["node", "dist/index.js"] # скомпилированный код запускается с помощью node
```
а переменные окружения возьмутся собственно из окружения (например из портейнера)
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
NBizyaev
commented
ну обычно так и оставляют ну обычно так и оставляют
```json
"scripts": {
"dev": "tsx watch --env-file=.env src/index.ts", // запуск во время разработки
"build": "tsc", // компиляция, сборка в бандл dist
"start": "node --env-file=.env dist/index.js" // проверка бандла
}
```
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
|
|||||||
25
app/src/cloud-ingest.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const DEFAULT_CLOUD_INGEST_URL = 'http://localhost:3100/ingest'
|
||||||
|
NBizyaev
commented
Значения по умолчанию для переменных окружения должны быть вынесены в отдельный модуль валидации значений переменных окружения. Значение переменной CLOUD_INGEST_URL является критическим для такого сервиса как mqtt-ingest, при не указанном значении CLOUD_INGEST_URL сервис не сможет отправлять данные, а значит - выполнять свою функцию, а значит в случае отсутствия значения CLOUD_INGEST_URL - приложение при старте должно ругнуться и не запуститься вовсе, чтобы при деплое на продакшн, это сразу было заметно. Значения по умолчанию для переменных окружения должны быть вынесены в отдельный модуль валидации значений переменных окружения.
Значение переменной CLOUD_INGEST_URL является критическим для такого сервиса как mqtt-ingest, при не указанном значении CLOUD_INGEST_URL сервис не сможет отправлять данные, а значит - выполнять свою функцию, а значит в случае отсутствия значения CLOUD_INGEST_URL - приложение при старте должно ругнуться и не запуститься вовсе, чтобы при деплое на продакшн, это сразу было заметно.
|
|||||||
|
|
||||||
|
export const cloudIngestUrl =
|
||||||
|
process.env.CLOUD_INGEST_URL ?? DEFAULT_CLOUD_INGEST_URL
|
||||||
|
|
||||||
|
function getHeaders(): Record<string, string> {
|
||||||
|
NBizyaev marked this conversation as resolved
NBizyaev
commented
function getHeaders(): Record<string, string> { function getHeaders(): Record<string, string> {
return {
'content-type': 'application/json',
'x-api-key': process.env.CLOUD_INGEST_API_KEY
}
}
|
|||||||
|
const headers: Record<string, string> = {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiKey = process.env.CLOUD_INGEST_API_KEY?.trim()
|
||||||
|
if (apiKey) {
|
||||||
|
headers['x-api-key'] = apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
export function postCloudIngest(body: unknown): Promise<Response> {
|
||||||
|
return fetch(cloudIngestUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getHeaders(),
|
||||||
|
NBizyaev
commented
headers: { headers: {
'content-type': 'application/json',
'x-api-key': process.env.CLOUD_INGEST_API_KEY
},
|
|||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import { postCloudIngest } from '../cloud-ingest.js'
|
||||||
import { parseJson } from '../helpers/parse.js'
|
import { parseJson } from '../helpers/parse.js'
|
||||||
import type { TopicHandler } from '../types.js'
|
import type { TopicHandler } from '../types.js'
|
||||||
|
|
||||||
export const TOPIC = 'data/demo/plc/v1'
|
export const TOPIC = 'data/demo/plc/v1'
|
||||||
|
|
||||||
const EDGE = 'demo'
|
const EDGE = 'demo'
|
||||||
const postUrl =
|
|
||||||
process.env.CLOUD_INGEST_URL ?? 'https://demo.backend.drill.greact.ru/ingest'
|
|
||||||
|
|
||||||
type DemoPlcPayload = {
|
type DemoPlcPayload = {
|
||||||
tag: string
|
tag: string
|
||||||
@@ -29,13 +28,7 @@ function toMetric(payload: DemoPlcPayload, timestamp: number): DemoPlcMetric {
|
|||||||
}
|
}
|
||||||
|
NBizyaev marked this conversation as resolved
NBizyaev
commented
Избыточная валидация Избыточная валидация
|
|||||||
|
|
||||||
async function postMetric(metric: DemoPlcMetric): Promise<void> {
|
async function postMetric(metric: DemoPlcMetric): Promise<void> {
|
||||||
const response = await fetch(postUrl, {
|
const response = await postCloudIngest([metric])
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(metric),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { postCloudIngest } from '../cloud-ingest.js'
|
||||||
import { createMetricMapper, type MappedMetric } from '../mapping.js'
|
import { createMetricMapper, type MappedMetric } from '../mapping.js'
|
||||||
import type { TopicHandler } from '../types.js'
|
import type { TopicHandler } from '../types.js'
|
||||||
|
|
||||||
@@ -9,17 +10,9 @@ const mapper = createMetricMapper({
|
|||||||
new URL('../mappings/edge5-modbus.json', import.meta.url),
|
new URL('../mappings/edge5-modbus.json', import.meta.url),
|
||||||
})
|
})
|
||||||
const REGISTER_COUNT = mapper.tagCount
|
const REGISTER_COUNT = mapper.tagCount
|
||||||
const postUrl =
|
|
||||||
process.env.CLOUD_INGEST_URL ?? 'https://demo.backend.drill.greact.ru/ingest'
|
|
||||||
|
|
||||||
function parseValues(payload: Buffer): number[] {
|
function parseValues(payload: Buffer): number[] {
|
||||||
let value: unknown
|
const value = JSON.parse(payload.toString('utf8')) as unknown
|
||||||
|
|
||||||
try {
|
|
||||||
value = JSON.parse(payload.toString('utf8')) as unknown
|
|
||||||
} catch {
|
|
||||||
throw new Error(`Expected JSON number[${REGISTER_COUNT}]`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
|
NBizyaev marked this conversation as resolved
NBizyaev
commented
избыточная валидация избыточная валидация
JSON.parse при некорректной JSON‑строке сам выкинет исключение SyntaxError
|
|||||||
@@ -33,13 +26,7 @@ function parseValues(payload: Buffer): number[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postMetrics(metrics: MappedMetric[]): Promise<void> {
|
async function postMetrics(metrics: MappedMetric[]): Promise<void> {
|
||||||
const response = await fetch(postUrl, {
|
const response = await postCloudIngest(metrics)
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(metrics),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
|
import { postCloudIngest } from '../cloud-ingest.js'
|
||||||
import type { TopicHandler } from '../types.js'
|
import type { TopicHandler } from '../types.js'
|
||||||
|
|
||||||
export const TOPIC = 'data/edge5/modbus/v1'
|
export const TOPIC = 'data/edge5/modbus/v1'
|
||||||
|
|
||||||
const EDGE = 'edge5'
|
const EDGE = 'edge5'
|
||||||
const REGISTER_COUNT = 125
|
const REGISTER_COUNT = 125
|
||||||
const postUrl =
|
|
||||||
process.env.CLOUD_INGEST_URL ?? 'https://demo.backend.drill.greact.ru/ingest'
|
|
||||||
|
|
||||||
type Edge5ModbusMetric = {
|
type Edge5ModbusMetric = {
|
||||||
edge: string
|
edge: string
|
||||||
@@ -44,13 +43,7 @@ function toMetrics(values: number[], timestamp: number): Edge5ModbusMetric[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postMetrics(metrics: Edge5ModbusMetric[]): Promise<void> {
|
async function postMetrics(metrics: Edge5ModbusMetric[]): Promise<void> {
|
||||||
const response = await fetch(postUrl, {
|
const response = await postCloudIngest(metrics)
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(metrics),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ services:
|
|||||||
HTTP_PORT: "80"
|
HTTP_PORT: "80"
|
||||||
WS_PORT: "9090"
|
WS_PORT: "9090"
|
||||||
CLOUD_INGEST_URL: ${CLOUD_INGEST_URL:-https://demo.backend.drill.greact.ru/ingest}
|
CLOUD_INGEST_URL: ${CLOUD_INGEST_URL:-https://demo.backend.drill.greact.ru/ingest}
|
||||||
|
CLOUD_INGEST_API_KEY: ${CLOUD_INGEST_API_KEY:-}
|
||||||
EDGE5_MODBUS_V2_MAPPING_FILE: ${EDGE5_MODBUS_V2_MAPPING_FILE:-/app/src/mappings/edge5-modbus.json}
|
EDGE5_MODBUS_V2_MAPPING_FILE: ${EDGE5_MODBUS_V2_MAPPING_FILE:-/app/src/mappings/edge5-modbus.json}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
флаги указывающие на
--env-file=.env--env-file-if-exists=.envпросто пробрасываются к node: