From bd8ae6142bd8bcd216e7694d17da12e2429706e5 Mon Sep 17 00:00:00 2001 From: Matthias Pfeil Date: Fri, 13 Dec 2024 11:49:38 +0100 Subject: [PATCH 1/2] add mqtt and ttn integration --- app/schema/device.ts | 5 +++ app/schema/enum.ts | 23 ++++++++--- app/schema/index.ts | 3 +- app/schema/integration.ts | 83 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 app/schema/integration.ts diff --git a/app/schema/device.ts b/app/schema/device.ts index f4cd483e..20a88a7a 100644 --- a/app/schema/device.ts +++ b/app/schema/device.ts @@ -16,6 +16,7 @@ import { import { user } from "./user"; import { sensor } from "./sensor"; import { logEntry } from "./log-entry"; +import { deviceToIntegrations } from "./integration"; /** * Table @@ -55,6 +56,10 @@ export const deviceRelations = relations(device, ({ one, many }) => ({ }), sensors: many(sensor), logEntries: many(logEntry), + integrations: one(deviceToIntegrations, { + fields: [device.id], + references: [deviceToIntegrations.deviceId], + }), })); /** diff --git a/app/schema/enum.ts b/app/schema/enum.ts index bcd36498..03b7ee0b 100644 --- a/app/schema/enum.ts +++ b/app/schema/enum.ts @@ -9,6 +9,23 @@ export const DeviceExposureEnum = pgEnum("exposure", [ "unknown", ]); +export const MqttMessageFormatEnum = pgEnum("message_format", [ + "json", + "csv", + "application/json", + "text/csv", + "debug_plain", + "", +]); + +export const TtnProfileEnum = pgEnum("ttn_profile", [ + "json", + "debug", + "sensebox/home", + "lora-serialization", + "cayenne-lpp", +]); + // Zod schema for validating device exposure types export const DeviceExposureZodEnum = z.enum(DeviceExposureEnum.enumValues); @@ -16,11 +33,7 @@ export const DeviceExposureZodEnum = z.enum(DeviceExposureEnum.enumValues); export type DeviceExposureType = z.infer; // Enum for device status types -export const DeviceStatusEnum = pgEnum("status", [ - "active", - "inactive", - "old", -]); +export const DeviceStatusEnum = pgEnum("status", ["active", "inactive", "old"]); // Zod schema for validating device status types export const DeviceStatusZodEnum = z.enum(DeviceStatusEnum.enumValues); diff --git a/app/schema/index.ts b/app/schema/index.ts index fc0ffcb2..f8755179 100644 --- a/app/schema/index.ts +++ b/app/schema/index.ts @@ -7,4 +7,5 @@ export * from "./profile-image"; export * from "./types"; export * from "./sensor"; export * from "./user"; -export * from "./log-entry"; \ No newline at end of file +export * from "./log-entry"; +export * from "./integration"; diff --git a/app/schema/integration.ts b/app/schema/integration.ts new file mode 100644 index 00000000..8788ebc1 --- /dev/null +++ b/app/schema/integration.ts @@ -0,0 +1,83 @@ +import { createId } from "@paralleldrive/cuid2"; +import { + boolean, + integer, + pgTable, + primaryKey, + text, +} from "drizzle-orm/pg-core"; +import { MqttMessageFormatEnum, TtnProfileEnum } from "./enum"; +import { device } from "./device"; +import { relations } from "drizzle-orm"; + +export const mqttIntegration = pgTable("mqtt_integration", { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + enabled: boolean("enabled").default(false).notNull(), + url: text("url").notNull(), + topic: text("topic").notNull(), + messageFormat: MqttMessageFormatEnum("message_format") + .default("json") + .notNull(), + decodeOptions: text("decode_options").notNull(), + connectionOptions: text("connection_options").notNull(), + deviceId: text("device_id").references(() => device.id, { + onDelete: "cascade", + }), +}); + +export const ttnIntegration = pgTable("ttn_integration", { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + enabled: boolean("enabled").default(false).notNull(), + devId: text("dev_id").notNull(), + appId: text("app_id").notNull(), + port: integer("port"), + profile: TtnProfileEnum("profile").default("json").notNull(), + decodeOptions: text("decode_options").notNull(), + deviceId: text("device_id").references(() => device.id, { + onDelete: "cascade", + }), +}); + +export const deviceToIntegrations = pgTable( + "device_to_integrations", + { + deviceId: text("device_id") + .notNull() + .references(() => device.id, { onDelete: "cascade" }), + mqttIntegrationId: text("mqtt_integration_id").references( + () => mqttIntegration.id, + { + onDelete: "set null", + }, + ), + ttnIntegrationId: text("ttn_integration_id").references( + () => ttnIntegration.id, + { + onDelete: "set null", + }, + ), + }, + (t) => ({ + pk: primaryKey({ columns: [t.deviceId] }), + }), +); + +export const deviceToIntegrationsRelations = relations( + deviceToIntegrations, + ({ one }) => ({ + mqttIntegration: one(mqttIntegration, { + fields: [deviceToIntegrations.mqttIntegrationId], + references: [mqttIntegration.id], + }), + ttnIntegration: one(ttnIntegration, { + fields: [deviceToIntegrations.ttnIntegrationId], + references: [ttnIntegration.id], + }), + }), +); From eb35e9866a8cb6f79887ea359ca3b7f657b61ee0 Mon Sep 17 00:00:00 2001 From: Matthias Pfeil Date: Fri, 13 Dec 2024 13:17:21 +0100 Subject: [PATCH 2/2] update data types --- app/schema/integration.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/schema/integration.ts b/app/schema/integration.ts index 8788ebc1..b0cdbd41 100644 --- a/app/schema/integration.ts +++ b/app/schema/integration.ts @@ -2,13 +2,14 @@ import { createId } from "@paralleldrive/cuid2"; import { boolean, integer, + json, pgTable, primaryKey, text, } from "drizzle-orm/pg-core"; import { MqttMessageFormatEnum, TtnProfileEnum } from "./enum"; import { device } from "./device"; -import { relations } from "drizzle-orm"; +import { relations, sql } from "drizzle-orm"; export const mqttIntegration = pgTable("mqtt_integration", { id: text("id") @@ -21,8 +22,8 @@ export const mqttIntegration = pgTable("mqtt_integration", { messageFormat: MqttMessageFormatEnum("message_format") .default("json") .notNull(), - decodeOptions: text("decode_options").notNull(), - connectionOptions: text("connection_options").notNull(), + decodeOptions: json("decode_options"), + connectionOptions: json("connection_options"), deviceId: text("device_id").references(() => device.id, { onDelete: "cascade", }), @@ -38,7 +39,9 @@ export const ttnIntegration = pgTable("ttn_integration", { appId: text("app_id").notNull(), port: integer("port"), profile: TtnProfileEnum("profile").default("json").notNull(), - decodeOptions: text("decode_options").notNull(), + decodeOptions: json("decode_options") + .$type() + .default(sql`'{}'::json`), deviceId: text("device_id").references(() => device.id, { onDelete: "cascade", }),