Tie points to locator serial numbers and store receiver telemetry

Adds devices/<serial>/log as a job-anchored MQTT ingest path: a locator
identifies itself by serial in the topic (auto-registered on first
sight, org resolved from the job in the payload) rather than by a
pre-provisioned broker credential. Device.serialNumber is now globally
unique so the bare topic segment is enough to resolve identity.
Locator lookup/auto-register logic is shared (LocatorRegistryService)
between this and the existing devices/<mqttUsername>/points path.

New /sim page (own header, outside the main app nav) simulates a
transmitter: pick an open job or create one, set a serial number and
telemetry defaults, and send points one at a time or on an interval
along a simulated walking path. It calls a new authenticated backend
endpoint (POST /orgs/:orgId/sim/publish) that publishes onto the real
broker rather than writing the DB directly, so the simulator exercises
the actual ingest pipeline end-to-end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ulhub
2026-07-14 19:41:58 +00:00
parent b7479fa68f
commit e91c91e037
16 changed files with 672 additions and 50 deletions

View File

@@ -0,0 +1,8 @@
-- Locator serial numbers are globally unique (not per-org), so a bare
-- devices/<serial>/log MQTT topic can identify the device without org context.
-- DropIndex
DROP INDEX "devices_orgId_serialNumber_key";
-- CreateIndex
CREATE UNIQUE INDEX "devices_serialNumber_key" ON "devices"("serialNumber");

View File

@@ -132,7 +132,10 @@ model Device {
id String @id @default(cuid())
orgId String
name String
serialNumber String?
// Globally unique (manufacturer serials aren't org-scoped in the real world) —
// this lets a bare devices/<serial>/log MQTT topic identify the device without
// any org context in the topic itself.
serialNumber String? @unique
mqttUsername String? @unique
isActive Boolean @default(true)
lastSeenAt DateTime? @db.Timestamptz(6)
@@ -142,7 +145,6 @@ model Device {
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
points LocatePoint[]
@@unique([orgId, serialNumber])
@@map("devices")
}