From 879418b5a97bfdd9f9d570c130b1513695c8777e Mon Sep 17 00:00:00 2001 From: eugene-admin Date: Sat, 4 Apr 2026 22:42:16 +0300 Subject: [PATCH] init: add contract definitions and database schema with migrations skeleton --- .gitignore | 15 +++++- .../main/resources/application.conf.example | 16 +++++++ .../main/resources/application.yaml.example | 12 +++++ contract/api/common.schema.json | 17 +++++++ contract/api/device.schema.json | 0 contract/api/stats.schema.json | 0 contract/api/timeseries.schema.json | 18 ++++++++ contract/ingest/common.schema.json | 37 +++++++++++++++ contract/ingest/event.schema.json | 38 +++++++++++++++ contract/ingest/telemetry.schema.json | 46 +++++++++++++++++++ contract/readme.md | 18 ++++++++ db/README.md | 4 ++ db/schema.sql | 5 ++ protocol/schema.json | 33 ++++++++++--- 14 files changed, 251 insertions(+), 8 deletions(-) create mode 100644 backend/src/main/resources/application.conf.example create mode 100644 backend/src/main/resources/application.yaml.example create mode 100644 contract/api/common.schema.json create mode 100644 contract/api/device.schema.json create mode 100644 contract/api/stats.schema.json create mode 100644 contract/api/timeseries.schema.json create mode 100644 contract/ingest/common.schema.json create mode 100644 contract/ingest/event.schema.json create mode 100644 contract/ingest/telemetry.schema.json create mode 100644 contract/readme.md create mode 100644 db/README.md create mode 100644 db/schema.sql diff --git a/.gitignore b/.gitignore index eca1cbd..9ced54b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,17 @@ sdkconfig build/ sdkconfig sdkconfig.old -Kconfig \ No newline at end of file +Kconfig + +.git/ + +# ide +.idea/ +.vscode/ + +# env +.env +*.env + +application.yaml +application.conf \ No newline at end of file diff --git a/backend/src/main/resources/application.conf.example b/backend/src/main/resources/application.conf.example new file mode 100644 index 0000000..be53fdd --- /dev/null +++ b/backend/src/main/resources/application.conf.example @@ -0,0 +1,16 @@ +ktor { + host = "0.0.0.0" + port = 8080 +} + +app { + name = "iot-backend" +} + +api { + prefix = "/api/v1" +} + +ws { + path = "/ws" +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yaml.example b/backend/src/main/resources/application.yaml.example new file mode 100644 index 0000000..b2106fd --- /dev/null +++ b/backend/src/main/resources/application.yaml.example @@ -0,0 +1,12 @@ +ktor: + host: 0.0.0.0 + port: 8080 + +app: + name: iot-backend + +api: + prefix: /api/v1 + +ws: + path: /ws \ No newline at end of file diff --git a/contract/api/common.schema.json b/contract/api/common.schema.json new file mode 100644 index 0000000..33a23ee --- /dev/null +++ b/contract/api/common.schema.json @@ -0,0 +1,17 @@ +{ + "$id": "contract/api/common.schema.json", + "type": "object", "required": ["ok"], + "properties": { + "ok": { + "type": "boolean" + }, + "data": {}, + "error": { + "type": "object", + "properties": { + "code": { "type": "string" }, + "message": { "type": "string" } + } + } + } +} \ No newline at end of file diff --git a/contract/api/device.schema.json b/contract/api/device.schema.json new file mode 100644 index 0000000..e69de29 diff --git a/contract/api/stats.schema.json b/contract/api/stats.schema.json new file mode 100644 index 0000000..e69de29 diff --git a/contract/api/timeseries.schema.json b/contract/api/timeseries.schema.json new file mode 100644 index 0000000..41665fc --- /dev/null +++ b/contract/api/timeseries.schema.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "required": ["points"], + "properties": { + "points": { + "type": "array", + "items": { + "type": "object", + "required": ["ts", "value"], + "properties": { + "ts": { "type": "integer" }, + "value": {} + }, + "additionalProperties": false + } + } + } +} \ No newline at end of file diff --git a/contract/ingest/common.schema.json b/contract/ingest/common.schema.json new file mode 100644 index 0000000..5d4c005 --- /dev/null +++ b/contract/ingest/common.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "contract/ingest/common.schema.json", + "type": "object", + "required": ["v", "id", "type", "ts", "deviceId", "payload"], + "additionalProperties": false, + "properties": { + "v": { + "type": "integer", + "const": 1 + }, + "id": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": ["telemetry", "event"] + }, + "ts": { + "type": "integer", + "minimum": 1600000000000, + "description": "Unix time in milliseconds (UTC)" + }, + "deviceId": { + "type": "string", + "minLength": 1 + }, + "payload": { + "type": "object" + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } +} \ No newline at end of file diff --git a/contract/ingest/event.schema.json b/contract/ingest/event.schema.json new file mode 100644 index 0000000..4b1d2ef --- /dev/null +++ b/contract/ingest/event.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "contract/ingest/event.schema.json", + "allOf": [ + { "$ref": "contract/ingest/common.schema.json" }, + { + "type": "object", + "properties": { + "type": { + "const": "event" + }, + "payload": { + "type": "object", + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "description": "Event identifier, e.g. device.overheat" + }, + "severity": { + "type": "string", + "enum": ["debug", "info", "warn", "error", "fatal"] + }, + "message": { + "type": "string" + }, + "data": { + "type": "object", + "additionalProperties": true + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/contract/ingest/telemetry.schema.json b/contract/ingest/telemetry.schema.json new file mode 100644 index 0000000..39a4131 --- /dev/null +++ b/contract/ingest/telemetry.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "contract/ingest/telemetry.schema.json", + "allOf": [ + { "$ref": "contract/ingest/common.schema.json" }, + { + "type": "object", + "properties": { + "payload": { + "type": "object", + "required": ["measurements"], + "additionalProperties": false, + "properties": { + "measurements": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["metric", "value"], + "additionalProperties": false, + "properties": { + "metric": { + "type": "string", + "minLength": 1 + }, + "value": { + "type": ["number", "string", "boolean"] + }, + "unit": { + "type": "string" + }, + "source": { + "type": "string" + }, + "ts": { + "type": "integer" + } + } + } + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/contract/readme.md b/contract/readme.md new file mode 100644 index 0000000..3908a45 --- /dev/null +++ b/contract/readme.md @@ -0,0 +1,18 @@ +## Контракты + +В системе используются два независимых контракта: + +### 1. Ingest (devices → backend) + +- поток сырых данных +- оптимизирован под запись +- минимальный размер сообщений +- формат: telemetry / event + +### 2. API (backend → mobile) + +- агрегированные данные +- оптимизирован под чтение +- формат зависит от UI (графики, таблицы, статусы) + +⚠️ Эти контракты НЕ обязаны совпадать \ No newline at end of file diff --git a/db/README.md b/db/README.md new file mode 100644 index 0000000..29b8fc1 --- /dev/null +++ b/db/README.md @@ -0,0 +1,4 @@ +- все изменения только через migrations +- schema.sql = текущее состояние +- старые migrations не редактируем +- версия базы = schema_version \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql new file mode 100644 index 0000000..a6c57d9 --- /dev/null +++ b/db/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE schema_version ( + version INT PRIMARY KEY, + applied_at TIMESTAMP NOT NULL, + checksum VARCHAR(64) NOT NULL +); \ No newline at end of file diff --git a/protocol/schema.json b/protocol/schema.json index 2446895..9ba528a 100644 --- a/protocol/schema.json +++ b/protocol/schema.json @@ -1,12 +1,31 @@ { "type": "object", - "required": ["v", "id", "type", "ts", "deviceId", "payload"], + "required": ["v", "type", "ts", "deviceId", "payload"], "properties": { - "v": { "type": "integer" }, - "id": { "type": "string" }, - "type": { "type": "string" }, - "ts": { "type": "integer" }, - "deviceId": { "type": "string" }, - "payload": { "type": "object" } + "v": { + "type": "integer", + "description": "Protocol version" + }, + + "type": { + "type": "string", + "description": "Message type" + }, + + "ts": { + "type": "integer", + "description": "Unix time in milliseconds" + }, + + "deviceId": { + "type": "string", + "minLength": 1, + "description": "Unique device identifier" + }, + + "payload": { + "type": "object", + "description": "Message-specific data" + } } } \ No newline at end of file