Implement ingest-service: MQTT -> ClickHouse (batched) + RabbitMQ event

Subscribes to devices/+/telemetry, validates payloads, buffers writes into
ClickHouse (flush on size or interval), and emits a per-reading event on
RabbitMQ for rule-engine-service to consume later. Adds the initial Postgres
and ClickHouse schema migrations, a Dockerfile, and wires the service into
docker-compose. Verified end-to-end via docker compose + mosquitto_pub: row
lands in ClickHouse and the event reaches the telemetry.new_reading queue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 16:52:45 +05:00
co-authored by Claude Sonnet 5
parent 6ce487735f
commit 938700cf86
19 changed files with 990 additions and 2 deletions
View File
+59
View File
@@ -0,0 +1,59 @@
-- Core platform schema: users, zones, device types, devices, automation rules.
-- Deliberately generic — no growbox-specific tables or columns. A "growbox"
-- is just a row in zones with device_types like light/pump/fan attached to it.
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'owner' CHECK (role IN ('owner', 'viewer')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE zones (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE device_types (
id BIGSERIAL PRIMARY KEY,
code TEXT NOT NULL UNIQUE, -- light, pump, fan, sensor_temp_humidity, vacuum, ...
category TEXT NOT NULL CHECK (category IN ('actuator', 'sensor')),
capabilities JSONB NOT NULL DEFAULT '[]' -- e.g. ["turn_on","turn_off","set_level"]
);
CREATE TABLE devices (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
zone_id BIGINT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
device_type_id BIGINT NOT NULL REFERENCES device_types(id),
name TEXT NOT NULL,
external_id TEXT NOT NULL UNIQUE, -- physical device id (ESP32 chip id, etc.)
protocol TEXT NOT NULL DEFAULT 'mqtt',
status TEXT NOT NULL DEFAULT 'offline' CHECK (status IN ('online', 'offline')),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX devices_zone_id_idx ON devices(zone_id);
CREATE INDEX devices_external_id_idx ON devices(external_id);
CREATE TABLE automation_rules (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
zone_id BIGINT NOT NULL REFERENCES zones(id) ON DELETE CASCADE,
target_device_id BIGINT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
condition_source_device_id BIGINT NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
condition_sensor_type TEXT NOT NULL,
condition_operator TEXT NOT NULL CHECK (condition_operator IN ('>', '<', '>=', '<=', '=', '!=')),
condition_value DOUBLE PRECISION NOT NULL,
action_type TEXT NOT NULL, -- e.g. turn_on, turn_off, set_level
action_params JSONB NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX automation_rules_zone_id_idx ON automation_rules(zone_id);
CREATE INDEX automation_rules_active_idx ON automation_rules(is_active) WHERE is_active;