tabletcommand-backend-models 7.4.106 → 7.4.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/constants.js +1 -0
- package/build/constants.js.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/models/department.js +18 -1
- package/build/models/department.js.map +1 -1
- package/build/models/device-mapping.js +21 -0
- package/build/models/device-mapping.js.map +1 -1
- package/build/models/location.js +14 -0
- package/build/models/location.js.map +1 -1
- package/build/models/tracker-responder.js +96 -0
- package/build/models/tracker-responder.js.map +1 -0
- package/build/models/user.js +1 -1
- package/build/test/location.js +20 -0
- package/build/test/location.js.map +1 -1
- package/build/test/tracker-responder.js +136 -0
- package/build/test/tracker-responder.js.map +1 -0
- package/build/types/tracker-responder.js +3 -0
- package/build/types/tracker-responder.js.map +1 -0
- package/cspell.json +1 -0
- package/definitions/constants.d.ts +1 -0
- package/definitions/constants.d.ts.map +1 -1
- package/definitions/index.d.ts +6 -0
- package/definitions/index.d.ts.map +1 -1
- package/definitions/models/department.d.ts +3 -0
- package/definitions/models/department.d.ts.map +1 -1
- package/definitions/models/device-mapping.d.ts.map +1 -1
- package/definitions/models/location.d.ts.map +1 -1
- package/definitions/models/tracker-responder.d.ts +13 -0
- package/definitions/models/tracker-responder.d.ts.map +1 -0
- package/definitions/test/tracker-responder.d.ts +2 -0
- package/definitions/test/tracker-responder.d.ts.map +1 -0
- package/definitions/types/department.d.ts +4 -0
- package/definitions/types/department.d.ts.map +1 -1
- package/definitions/types/device-mapping.d.ts +6 -0
- package/definitions/types/device-mapping.d.ts.map +1 -1
- package/definitions/types/location.d.ts +1 -0
- package/definitions/types/location.d.ts.map +1 -1
- package/definitions/types/tracker-responder.d.ts +20 -0
- package/definitions/types/tracker-responder.d.ts.map +1 -0
- package/definitions/types/user.d.ts +1 -1
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/index.ts +2 -0
- package/src/models/department.ts +20 -0
- package/src/models/device-mapping.ts +23 -1
- package/src/models/location.ts +15 -0
- package/src/models/tracker-responder.ts +107 -0
- package/src/models/user.ts +1 -1
- package/src/test/location.ts +22 -0
- package/src/test/tracker-responder.ts +144 -0
- package/src/types/department.ts +5 -0
- package/src/types/device-mapping.ts +7 -0
- package/src/types/location.ts +1 -0
- package/src/types/tracker-responder.ts +28 -0
- package/src/types/user.ts +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import * as uuid from "uuid";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MongooseModule,
|
|
5
|
+
} from "../helpers";
|
|
6
|
+
import { Model } from "mongoose";
|
|
7
|
+
import { TrackerResponderType } from "../types/tracker-responder";
|
|
8
|
+
|
|
9
|
+
export interface TrackerResponder extends TrackerResponderType, Record<string, unknown> { }
|
|
10
|
+
|
|
11
|
+
export default async function TrackerResponderModule(mongoose: MongooseModule) {
|
|
12
|
+
const { Schema } = mongoose;
|
|
13
|
+
|
|
14
|
+
const modelSchema = new Schema<TrackerResponder>({
|
|
15
|
+
_id: {
|
|
16
|
+
type: Schema.Types.ObjectId,
|
|
17
|
+
auto: true,
|
|
18
|
+
},
|
|
19
|
+
departmentId: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
// Transponder MAC address, the vendor-side identity of the tracked seat
|
|
24
|
+
mac: {
|
|
25
|
+
type: String,
|
|
26
|
+
required: true,
|
|
27
|
+
uppercase: true,
|
|
28
|
+
},
|
|
29
|
+
// Stable identity carried by Location.transponderUUID
|
|
30
|
+
uuid: {
|
|
31
|
+
type: String,
|
|
32
|
+
required: true,
|
|
33
|
+
default: uuid.v4,
|
|
34
|
+
},
|
|
35
|
+
// Vendor personnelId verbatim (not a PersonnelKnown reference)
|
|
36
|
+
trackerPersonnelId: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: "",
|
|
39
|
+
},
|
|
40
|
+
// Vendor-owned names, taken verbatim from the TXRX webhook body
|
|
41
|
+
personnelName: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: "",
|
|
44
|
+
},
|
|
45
|
+
friendlyName: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: "",
|
|
48
|
+
},
|
|
49
|
+
// User-owned display name, editable on our end (eventually via iOS);
|
|
50
|
+
// the webhook never touches this
|
|
51
|
+
displayName: {
|
|
52
|
+
type: String,
|
|
53
|
+
default: "",
|
|
54
|
+
},
|
|
55
|
+
seatPosition: {
|
|
56
|
+
type: String,
|
|
57
|
+
default: "",
|
|
58
|
+
},
|
|
59
|
+
// "" = unassigned, otherwise PersonnelKnown.uuid
|
|
60
|
+
personnelUUID: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: "",
|
|
63
|
+
},
|
|
64
|
+
assignedBy: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: "",
|
|
67
|
+
},
|
|
68
|
+
assignedAt: {
|
|
69
|
+
type: Date,
|
|
70
|
+
default: null,
|
|
71
|
+
},
|
|
72
|
+
active: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: true,
|
|
75
|
+
},
|
|
76
|
+
source: {
|
|
77
|
+
type: String,
|
|
78
|
+
default: "",
|
|
79
|
+
},
|
|
80
|
+
}, {
|
|
81
|
+
timestamps: {
|
|
82
|
+
updatedAt: "modified",
|
|
83
|
+
},
|
|
84
|
+
autoIndex: false,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
modelSchema.index({
|
|
88
|
+
departmentId: 1,
|
|
89
|
+
mac: 1,
|
|
90
|
+
}, {
|
|
91
|
+
name: "departmentId_1_mac_1_unique",
|
|
92
|
+
unique: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Lookup path for resolving Location.transponderUUID back to a responder
|
|
96
|
+
modelSchema.index({
|
|
97
|
+
departmentId: 1,
|
|
98
|
+
uuid: 1,
|
|
99
|
+
}, {
|
|
100
|
+
name: "departmentId_1_uuid_1_unique",
|
|
101
|
+
unique: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return mongoose.model<TrackerResponder>("TrackerResponder", modelSchema, "massive_tracker_responders", { overwriteModels: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface TrackerResponderModel extends Model<TrackerResponder> { }
|
package/src/models/user.ts
CHANGED
package/src/test/location.ts
CHANGED
|
@@ -116,6 +116,28 @@ describe("Location", function() {
|
|
|
116
116
|
assert.equal(sut.kindColor?.text, "#FFFFFF");
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
it("defaults transponderUUID to empty and persists it", async function() {
|
|
120
|
+
const empty = await new models.Location({
|
|
121
|
+
departmentId: "d1",
|
|
122
|
+
userId: "u1",
|
|
123
|
+
username: "E3",
|
|
124
|
+
device_type: "ipad",
|
|
125
|
+
}).save();
|
|
126
|
+
assert.equal(empty.transponderUUID, "");
|
|
127
|
+
|
|
128
|
+
const tracked = await new models.Location({
|
|
129
|
+
departmentId: "d1",
|
|
130
|
+
userId: "TXRX-tr-uuid-1",
|
|
131
|
+
username: "Fire Hose 1",
|
|
132
|
+
device_type: "cad",
|
|
133
|
+
kindType: "person",
|
|
134
|
+
locationType: "person",
|
|
135
|
+
transponderUUID: "tr-uuid-1",
|
|
136
|
+
}).save();
|
|
137
|
+
assert.equal(tracked.transponderUUID, "tr-uuid-1");
|
|
138
|
+
assert.equal(tracked.personnelUUID, "");
|
|
139
|
+
});
|
|
140
|
+
|
|
119
141
|
it("does not auto-stamp kindColorChangedAt", async function() {
|
|
120
142
|
const item = new models.Location({
|
|
121
143
|
departmentId: "d1",
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
3
|
+
import * as m from "../index";
|
|
4
|
+
import * as config from "./config";
|
|
5
|
+
import mockModule from "./mock";
|
|
6
|
+
|
|
7
|
+
describe("TrackerResponder", function() {
|
|
8
|
+
let models: m.BackendModels, mongoose: m.MongooseModule;
|
|
9
|
+
beforeEach(async function() {
|
|
10
|
+
const c = await m.connect(config.url);
|
|
11
|
+
models = c.models;
|
|
12
|
+
mongoose = c.mongoose;
|
|
13
|
+
|
|
14
|
+
const mock = mockModule({
|
|
15
|
+
mongoose
|
|
16
|
+
});
|
|
17
|
+
await mock.beforeEach();
|
|
18
|
+
await models.TrackerResponder.deleteMany({});
|
|
19
|
+
});
|
|
20
|
+
afterEach(async function() {
|
|
21
|
+
await mongoose.disconnect();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("is saved with defaults", async function() {
|
|
25
|
+
const item = new models.TrackerResponder({
|
|
26
|
+
departmentId: "d1",
|
|
27
|
+
mac: "aa:bb:cc:dd:ee:01",
|
|
28
|
+
});
|
|
29
|
+
const sut = await item.save();
|
|
30
|
+
|
|
31
|
+
assert.isNotNull(sut._id);
|
|
32
|
+
assert.equal(sut.departmentId, "d1");
|
|
33
|
+
assert.equal(sut.mac, "AA:BB:CC:DD:EE:01");
|
|
34
|
+
assert.isTrue(sut.uuid !== "");
|
|
35
|
+
assert.equal(sut.trackerPersonnelId, "");
|
|
36
|
+
assert.equal(sut.personnelName, "");
|
|
37
|
+
assert.equal(sut.friendlyName, "");
|
|
38
|
+
assert.equal(sut.displayName, "");
|
|
39
|
+
assert.equal(sut.seatPosition, "");
|
|
40
|
+
assert.equal(sut.personnelUUID, "");
|
|
41
|
+
assert.equal(sut.assignedBy, "");
|
|
42
|
+
assert.isNull(sut.assignedAt);
|
|
43
|
+
assert.isTrue(sut.active);
|
|
44
|
+
assert.equal(sut.source, "");
|
|
45
|
+
assert.instanceOf(sut.createdAt, Date);
|
|
46
|
+
assert.instanceOf(sut.modified, Date);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("persists vendor and user-owned fields", async function() {
|
|
50
|
+
const item = new models.TrackerResponder({
|
|
51
|
+
departmentId: "d1",
|
|
52
|
+
mac: "AA:BB:CC:DD:EE:02",
|
|
53
|
+
trackerPersonnelId: "12345",
|
|
54
|
+
personnelName: "Fire Hose 1",
|
|
55
|
+
friendlyName: "FH-1",
|
|
56
|
+
displayName: "Engine 1 Nozzle",
|
|
57
|
+
seatPosition: "Fire Hose 1",
|
|
58
|
+
personnelUUID: "pk-uuid-1",
|
|
59
|
+
assignedBy: "user-1",
|
|
60
|
+
assignedAt: new Date("2026-07-23T00:00:00.000Z"),
|
|
61
|
+
source: "TXRX",
|
|
62
|
+
});
|
|
63
|
+
const sut = await item.save();
|
|
64
|
+
|
|
65
|
+
assert.equal(sut.trackerPersonnelId, "12345");
|
|
66
|
+
assert.equal(sut.personnelName, "Fire Hose 1");
|
|
67
|
+
assert.equal(sut.friendlyName, "FH-1");
|
|
68
|
+
assert.equal(sut.displayName, "Engine 1 Nozzle");
|
|
69
|
+
assert.equal(sut.seatPosition, "Fire Hose 1");
|
|
70
|
+
assert.equal(sut.personnelUUID, "pk-uuid-1");
|
|
71
|
+
assert.equal(sut.assignedBy, "user-1");
|
|
72
|
+
assert.equal(sut.assignedAt?.toISOString(), "2026-07-23T00:00:00.000Z");
|
|
73
|
+
assert.equal(sut.source, "TXRX");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("enforces unique (departmentId, mac)", async function() {
|
|
77
|
+
await models.TrackerResponder.createIndexes();
|
|
78
|
+
|
|
79
|
+
await new models.TrackerResponder({
|
|
80
|
+
departmentId: "d1",
|
|
81
|
+
mac: "AA:BB:CC:DD:EE:03",
|
|
82
|
+
}).save();
|
|
83
|
+
|
|
84
|
+
// Same MAC in another department is allowed
|
|
85
|
+
const other = await new models.TrackerResponder({
|
|
86
|
+
departmentId: "d2",
|
|
87
|
+
mac: "AA:BB:CC:DD:EE:03",
|
|
88
|
+
}).save();
|
|
89
|
+
assert.isNotNull(other._id);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
await new models.TrackerResponder({
|
|
93
|
+
departmentId: "d1",
|
|
94
|
+
mac: "aa:bb:cc:dd:ee:03",
|
|
95
|
+
}).save();
|
|
96
|
+
assert.isFalse(true, "Expecting duplicate (departmentId, mac) to fail.");
|
|
97
|
+
} catch (error) {
|
|
98
|
+
assert.match((error as Error).message, /E11000/);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("enforces unique (departmentId, uuid)", async function() {
|
|
103
|
+
await models.TrackerResponder.createIndexes();
|
|
104
|
+
|
|
105
|
+
const first = await new models.TrackerResponder({
|
|
106
|
+
departmentId: "d1",
|
|
107
|
+
mac: "AA:BB:CC:DD:EE:04",
|
|
108
|
+
}).save();
|
|
109
|
+
|
|
110
|
+
// Same uuid in another department is allowed
|
|
111
|
+
const other = await new models.TrackerResponder({
|
|
112
|
+
departmentId: "d2",
|
|
113
|
+
mac: "AA:BB:CC:DD:EE:04",
|
|
114
|
+
uuid: first.uuid,
|
|
115
|
+
}).save();
|
|
116
|
+
assert.isNotNull(other._id);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
await new models.TrackerResponder({
|
|
120
|
+
departmentId: "d1",
|
|
121
|
+
mac: "AA:BB:CC:DD:EE:05",
|
|
122
|
+
uuid: first.uuid,
|
|
123
|
+
}).save();
|
|
124
|
+
assert.isFalse(true, "Expecting duplicate (departmentId, uuid) to fail.");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
assert.match((error as Error).message, /E11000/);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("rejects an empty or null uuid", async function() {
|
|
131
|
+
for (const uuid of ["", null]) {
|
|
132
|
+
try {
|
|
133
|
+
await new models.TrackerResponder({
|
|
134
|
+
departmentId: "d1",
|
|
135
|
+
mac: "AA:BB:CC:DD:EE:06",
|
|
136
|
+
uuid,
|
|
137
|
+
}).save();
|
|
138
|
+
assert.isFalse(true, `Expecting uuid ${JSON.stringify(uuid)} to fail validation.`);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
assert.match((error as Error).message, /uuid.*required/i);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
package/src/types/department.ts
CHANGED
|
@@ -205,6 +205,10 @@ export interface SomewearType {
|
|
|
205
205
|
enabled: boolean
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
export interface PersonnelWebhookType {
|
|
209
|
+
enabled: boolean
|
|
210
|
+
}
|
|
211
|
+
|
|
208
212
|
export interface SkytracConfigType {
|
|
209
213
|
enabled: boolean
|
|
210
214
|
licenseKey: string,
|
|
@@ -672,6 +676,7 @@ export interface DepartmentType {
|
|
|
672
676
|
orientationMarkerColor: ColorSchemaType,
|
|
673
677
|
partialApiKey: string,
|
|
674
678
|
personnelStaffingEnabled: boolean,
|
|
679
|
+
personnelWebhook: PersonnelWebhookType,
|
|
675
680
|
pubNubV3: PubNubTokenSchemaType,
|
|
676
681
|
remoteLoggingEnabled: boolean,
|
|
677
682
|
reportNumberEnabled: boolean,
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Types } from "mongoose";
|
|
2
2
|
|
|
3
|
+
export interface DeviceMappingCADToCADType {
|
|
4
|
+
activeTransfer: boolean,
|
|
5
|
+
homeDepartmentId: string,
|
|
6
|
+
transferDate: Date,
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
export interface DeviceMappingType {
|
|
4
10
|
_id: Types.ObjectId,
|
|
5
11
|
departmentId: string,
|
|
@@ -19,4 +25,5 @@ export interface DeviceMappingType {
|
|
|
19
25
|
note: string,
|
|
20
26
|
mapHidden: boolean,
|
|
21
27
|
locationToCAD: boolean,
|
|
28
|
+
cadToCAD: DeviceMappingCADToCADType,
|
|
22
29
|
}
|
package/src/types/location.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Types } from "mongoose";
|
|
2
|
+
|
|
3
|
+
export interface TrackerResponderType {
|
|
4
|
+
_id: Types.ObjectId,
|
|
5
|
+
departmentId: string,
|
|
6
|
+
// Transponder MAC address, normalized uppercase. Unique per department.
|
|
7
|
+
mac: string,
|
|
8
|
+
// Stable identity carried by Location.transponderUUID.
|
|
9
|
+
uuid: string,
|
|
10
|
+
// Vendor personnelId verbatim (not a PersonnelKnown reference).
|
|
11
|
+
trackerPersonnelId: string,
|
|
12
|
+
// Vendor-owned names, taken verbatim from the TXRX webhook body.
|
|
13
|
+
personnelName: string,
|
|
14
|
+
friendlyName: string,
|
|
15
|
+
// User-owned display name, editable on our end (eventually via iOS);
|
|
16
|
+
// the webhook never touches this.
|
|
17
|
+
displayName: string,
|
|
18
|
+
seatPosition: string,
|
|
19
|
+
// "" = unassigned, otherwise PersonnelKnown.uuid.
|
|
20
|
+
personnelUUID: string,
|
|
21
|
+
assignedBy: string,
|
|
22
|
+
// null while unassigned
|
|
23
|
+
assignedAt: Date | null,
|
|
24
|
+
active: boolean,
|
|
25
|
+
source: string,
|
|
26
|
+
createdAt: Date,
|
|
27
|
+
modified: Date,
|
|
28
|
+
}
|
package/src/types/user.ts
CHANGED
|
@@ -106,7 +106,7 @@ export interface UserType {
|
|
|
106
106
|
beaconEnabled: boolean,
|
|
107
107
|
beacons: string[],
|
|
108
108
|
cadSimulatorAccess: boolean,
|
|
109
|
-
|
|
109
|
+
cadToCAD: UserCADToCADType,
|
|
110
110
|
canAddRemoveVehicle: boolean,
|
|
111
111
|
canBlockIncidents: boolean,
|
|
112
112
|
canApproveSharingAgreements: boolean,
|