tabletcommand-backend-models 7.4.106 → 7.4.107

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.
Files changed (45) hide show
  1. package/build/constants.js +1 -0
  2. package/build/constants.js.map +1 -1
  3. package/build/index.js +1 -0
  4. package/build/index.js.map +1 -1
  5. package/build/models/department.js +18 -1
  6. package/build/models/department.js.map +1 -1
  7. package/build/models/location.js +14 -0
  8. package/build/models/location.js.map +1 -1
  9. package/build/models/tracker-responder.js +96 -0
  10. package/build/models/tracker-responder.js.map +1 -0
  11. package/build/test/location.js +20 -0
  12. package/build/test/location.js.map +1 -1
  13. package/build/test/tracker-responder.js +136 -0
  14. package/build/test/tracker-responder.js.map +1 -0
  15. package/build/types/tracker-responder.js +3 -0
  16. package/build/types/tracker-responder.js.map +1 -0
  17. package/cspell.json +1 -0
  18. package/definitions/constants.d.ts +1 -0
  19. package/definitions/constants.d.ts.map +1 -1
  20. package/definitions/index.d.ts +6 -0
  21. package/definitions/index.d.ts.map +1 -1
  22. package/definitions/models/department.d.ts +3 -0
  23. package/definitions/models/department.d.ts.map +1 -1
  24. package/definitions/models/location.d.ts.map +1 -1
  25. package/definitions/models/tracker-responder.d.ts +13 -0
  26. package/definitions/models/tracker-responder.d.ts.map +1 -0
  27. package/definitions/test/tracker-responder.d.ts +2 -0
  28. package/definitions/test/tracker-responder.d.ts.map +1 -0
  29. package/definitions/types/department.d.ts +4 -0
  30. package/definitions/types/department.d.ts.map +1 -1
  31. package/definitions/types/location.d.ts +1 -0
  32. package/definitions/types/location.d.ts.map +1 -1
  33. package/definitions/types/tracker-responder.d.ts +20 -0
  34. package/definitions/types/tracker-responder.d.ts.map +1 -0
  35. package/package.json +1 -1
  36. package/src/constants.ts +1 -0
  37. package/src/index.ts +2 -0
  38. package/src/models/department.ts +20 -0
  39. package/src/models/location.ts +15 -0
  40. package/src/models/tracker-responder.ts +107 -0
  41. package/src/test/location.ts +22 -0
  42. package/src/test/tracker-responder.ts +144 -0
  43. package/src/types/department.ts +5 -0
  44. package/src/types/location.ts +1 -0
  45. package/src/types/tracker-responder.ts +28 -0
@@ -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> { }
@@ -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
+ });
@@ -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,
@@ -40,6 +40,7 @@ export interface LocationType {
40
40
  username: string,
41
41
  uuid: string,
42
42
  personnelUUID: string,
43
+ transponderUUID: string,
43
44
  version: number,
44
45
  visibility: string[],
45
46
  }
@@ -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
+ }