Tie points to locator serial numbers and store receiver telemetry
Field setups often relay through a phone/gateway that owns the MQTT connection, so the publishing credential and the instrument are now separate concepts: - Points messages carry a "serial" identifying the locator receiver; unknown serials are auto-registered as devices in the publisher's org (name "Locator <serial>"). Device.mqttUsername is now optional and serialNumber is unique per org. - LocatePoint gains standard receiver telemetry: frequencyHz, currentMa, signalDb, gainDb, locateMode (PEAK/NULL/BROAD_PEAK/SONDE), phaseDeg, compassDeg, distortionPct, plus GPS quality columns vAccuracy, satellites, hdop. All optional; raw payload still kept. - Migration hand-edited to preserve the generated geom column and its GIST index (Prisma diff wanted to drop both). - REST create/point DTOs, map tooltips, job-detail latest-point readout, devices settings form, seed, and the sample publisher all carry the new fields. - Added .dockerignore for backend/web: COPY . . was clobbering the image's freshly generated Prisma client with the host's stale node_modules, breaking image builds after schema changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateDeviceDto, UpdateDeviceDto } from './dto/devices.dto';
|
||||
|
||||
@@ -14,23 +14,40 @@ export class DevicesService {
|
||||
}
|
||||
|
||||
async create(orgId: string, dto: CreateDeviceDto) {
|
||||
const existing = await this.prisma.device.findUnique({
|
||||
where: { mqttUsername: dto.mqttUsername },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('A device with this MQTT username already exists');
|
||||
if (!dto.mqttUsername && !dto.serialNumber) {
|
||||
throw new BadRequestException('A device needs a serial number, an MQTT username, or both');
|
||||
}
|
||||
if (dto.mqttUsername) {
|
||||
const existing = await this.prisma.device.findUnique({
|
||||
where: { mqttUsername: dto.mqttUsername },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('A device with this MQTT username already exists');
|
||||
}
|
||||
}
|
||||
if (dto.serialNumber) {
|
||||
const existing = await this.prisma.device.findUnique({
|
||||
where: { orgId_serialNumber: { orgId, serialNumber: dto.serialNumber } },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('A device with this serial number already exists in this organization');
|
||||
}
|
||||
}
|
||||
const device = await this.prisma.device.create({
|
||||
data: { orgId, name: dto.name, mqttUsername: dto.mqttUsername, serialNumber: dto.serialNumber },
|
||||
});
|
||||
return {
|
||||
...device,
|
||||
provisioning: {
|
||||
mqttUsername: device.mqttUsername,
|
||||
pointsTopic: `devices/${device.mqttUsername}/points`,
|
||||
jobsTopic: `devices/${device.mqttUsername}/jobs`,
|
||||
note: 'Broker credentials must be created separately (mosquitto_passwd) until dynamic broker auth lands.',
|
||||
},
|
||||
provisioning: device.mqttUsername
|
||||
? {
|
||||
mqttUsername: device.mqttUsername,
|
||||
pointsTopic: `devices/${device.mqttUsername}/points`,
|
||||
jobsTopic: `devices/${device.mqttUsername}/jobs`,
|
||||
note: 'Broker credentials must be created separately (mosquitto_passwd) until dynamic broker auth lands.',
|
||||
}
|
||||
: {
|
||||
note: `Relayed locator: points published by a gateway should carry "serial": "${device.serialNumber}".`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user