@internetderdinge/api 1.224.2
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/.github/copilot-instructions.md +77 -0
- package/CHANGELOG.md +11 -0
- package/README.md +52 -0
- package/package.json +112 -0
- package/src/accounts/accounts.controller.ts +166 -0
- package/src/accounts/accounts.route.ts +107 -0
- package/src/accounts/accounts.schemas.ts +16 -0
- package/src/accounts/accounts.service.ts +85 -0
- package/src/accounts/accounts.validation.ts +118 -0
- package/src/accounts/auth0.service.ts +226 -0
- package/src/config/config.ts +49 -0
- package/src/config/logger.ts +33 -0
- package/src/config/morgan.ts +22 -0
- package/src/config/passport.cjs +30 -0
- package/src/config/roles.ts +13 -0
- package/src/config/tokens.cjs +10 -0
- package/src/devices/devices.controller.ts +276 -0
- package/src/devices/devices.model.ts +126 -0
- package/src/devices/devices.route.ts +198 -0
- package/src/devices/devices.schemas.ts +94 -0
- package/src/devices/devices.service.ts +320 -0
- package/src/devices/devices.validation.ts +221 -0
- package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
- package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
- package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
- package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
- package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
- package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
- package/src/email/email.service.ts +609 -0
- package/src/files/upload.service.ts +145 -0
- package/src/i18n/i18n.ts +51 -0
- package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
- package/src/index.ts +7 -0
- package/src/iotdevice/iotdevice.controller.ts +136 -0
- package/src/iotdevice/iotdevice.model.ts +32 -0
- package/src/iotdevice/iotdevice.route.ts +181 -0
- package/src/iotdevice/iotdevice.schemas.ts +79 -0
- package/src/iotdevice/iotdevice.service.ts +732 -0
- package/src/iotdevice/iotdevice.validation.ts +61 -0
- package/src/middlewares/auth.ts +110 -0
- package/src/middlewares/checkJwt.cjs +19 -0
- package/src/middlewares/error.js.legacy +44 -0
- package/src/middlewares/error.ts +41 -0
- package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
- package/src/middlewares/rateLimiter.ts +10 -0
- package/src/middlewares/validate.ts +25 -0
- package/src/middlewares/validateAction.ts +41 -0
- package/src/middlewares/validateAdmin.ts +21 -0
- package/src/middlewares/validateAi.ts +24 -0
- package/src/middlewares/validateCurrentAuthUser.ts +23 -0
- package/src/middlewares/validateCurrentUser.ts +35 -0
- package/src/middlewares/validateDevice.ts +191 -0
- package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
- package/src/middlewares/validateOrganization.ts +109 -0
- package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
- package/src/middlewares/validateTokens.ts +36 -0
- package/src/middlewares/validateUser.ts +75 -0
- package/src/middlewares/validateZod.ts +54 -0
- package/src/models/plugins/index.ts +7 -0
- package/src/models/plugins/paginate.plugin.ts +145 -0
- package/src/models/plugins/paginateNew.plugin.ts +206 -0
- package/src/models/plugins/simplePopulate.ts +12 -0
- package/src/models/plugins/toJSON.plugin.ts +51 -0
- package/src/organizations/organizations.controller.ts +101 -0
- package/src/organizations/organizations.model.ts +62 -0
- package/src/organizations/organizations.route.ts +119 -0
- package/src/organizations/organizations.schemas.ts +8 -0
- package/src/organizations/organizations.service.ts +85 -0
- package/src/organizations/organizations.validation.ts +76 -0
- package/src/pdf/pdf.controller.ts +18 -0
- package/src/pdf/pdf.route.ts +28 -0
- package/src/pdf/pdf.schemas.ts +7 -0
- package/src/pdf/pdf.service.ts +89 -0
- package/src/pdf/pdf.validation.ts +30 -0
- package/src/tokens/tokens.controller.ts +81 -0
- package/src/tokens/tokens.model.ts +24 -0
- package/src/tokens/tokens.route.ts +66 -0
- package/src/tokens/tokens.schemas.ts +15 -0
- package/src/tokens/tokens.service.ts +46 -0
- package/src/tokens/tokens.validation.ts +13 -0
- package/src/types/routeSpec.ts +1 -0
- package/src/users/users.controller.ts +234 -0
- package/src/users/users.model.ts +89 -0
- package/src/users/users.route.ts +171 -0
- package/src/users/users.schemas.ts +79 -0
- package/src/users/users.service.ts +393 -0
- package/src/users/users.validation.ts +166 -0
- package/src/utils/ApiError.ts +18 -0
- package/src/utils/buildRouterAndDocs.ts +85 -0
- package/src/utils/catchAsync.ts +9 -0
- package/src/utils/comparePapers.service.ts +48 -0
- package/src/utils/filterOptions.ts +37 -0
- package/src/utils/medicationName.ts +12 -0
- package/src/utils/pick.ts +16 -0
- package/src/utils/registerOpenApi.ts +32 -0
- package/src/utils/urlUtils.ts +14 -0
- package/src/utils/userName.ts +27 -0
- package/src/utils/zValidations.ts +89 -0
- package/src/validations/auth.validation.cjs +60 -0
- package/src/validations/custom.validation.ts +26 -0
- package/src/validations/index.cjs +2 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import Device from "./devices.model.js";
|
|
3
|
+
import ApiError from "../utils/ApiError.js";
|
|
4
|
+
import iotDevicesService from "../iotdevice/iotdevice.service.js";
|
|
5
|
+
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
import { getSignedFileUrl } from "../files/upload.service";
|
|
8
|
+
import { deviceByDeviceName, deviceKindHasFeature } from "@wirewire/helpers";
|
|
9
|
+
|
|
10
|
+
import type { DeviceDocument, DeviceInput } from "./devices.model.js";
|
|
11
|
+
import type { ApiErrorType } from "../utils/ApiError.js";
|
|
12
|
+
import type { IoTDeviceResponse } from "../iotdevice/iotdevice.service.js";
|
|
13
|
+
import * as usersService from "../users/users.service";
|
|
14
|
+
|
|
15
|
+
const setTimeoutAsync = promisify(setTimeout);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a new device
|
|
19
|
+
*/
|
|
20
|
+
export const registerDevice = async ({
|
|
21
|
+
id,
|
|
22
|
+
body,
|
|
23
|
+
}: {
|
|
24
|
+
id: string;
|
|
25
|
+
body: DeviceInput;
|
|
26
|
+
}): Promise<IoTDeviceResponse & { createdDevice?: DeviceDocument }> => {
|
|
27
|
+
const iotDevices = await iotDevicesService.activateDevice(
|
|
28
|
+
id,
|
|
29
|
+
body.organization,
|
|
30
|
+
body.enable,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
if (iotDevices.activation_status === "success") {
|
|
34
|
+
let patient;
|
|
35
|
+
if (body.patient) {
|
|
36
|
+
patient = await usersService.getById(body.patient);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const deviceListData = deviceByDeviceName(id);
|
|
40
|
+
|
|
41
|
+
const createdDevice = await Device.create({
|
|
42
|
+
deviceId: id,
|
|
43
|
+
...body,
|
|
44
|
+
kind: deviceListData.id,
|
|
45
|
+
meta: {
|
|
46
|
+
name:
|
|
47
|
+
patient && patient.meta?.firstName
|
|
48
|
+
? `${patient.meta.firstName} ${patient.meta.lastName} ANABOX smart`
|
|
49
|
+
: undefined,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return { ...iotDevices, createdDevice };
|
|
53
|
+
}
|
|
54
|
+
return iotDevices;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getAllDevices = async (): Promise<DeviceDocument[]> => {
|
|
58
|
+
return Device.find();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const createDevice = async (
|
|
62
|
+
userBody: DeviceInput,
|
|
63
|
+
): Promise<DeviceDocument> => {
|
|
64
|
+
return Device.create(userBody);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const populateIotDevice = async (e: {
|
|
68
|
+
deviceId: string;
|
|
69
|
+
}): Promise<IoTDeviceResponse | null> => {
|
|
70
|
+
const iotDevices = await iotDevicesService.getDevice([e.deviceId]);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
if (iotDevices) {
|
|
74
|
+
const iotDevice = iotDevices.message.find(
|
|
75
|
+
(c) => c.serialNumber === e.deviceId,
|
|
76
|
+
);
|
|
77
|
+
return iotDevice || null;
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(error);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const populateIotShadow = async (
|
|
87
|
+
e: { deviceId: string },
|
|
88
|
+
shadowName: string,
|
|
89
|
+
): Promise<any> => {
|
|
90
|
+
const iotDevices = await iotDevicesService.shadowAlarmGet(
|
|
91
|
+
e.deviceId,
|
|
92
|
+
shadowName,
|
|
93
|
+
);
|
|
94
|
+
return iotDevices;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const populateDeviceStatus = async (e: {
|
|
98
|
+
deviceId: string;
|
|
99
|
+
kind: string;
|
|
100
|
+
}): Promise<any> => {
|
|
101
|
+
if (!e.deviceId) return null;
|
|
102
|
+
const deviceStatus = await iotDevicesService.getDeviceStatus(
|
|
103
|
+
e.deviceId,
|
|
104
|
+
e.kind,
|
|
105
|
+
);
|
|
106
|
+
return deviceStatus;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const populateDeviceImage = async (
|
|
110
|
+
deviceId: string,
|
|
111
|
+
uuid: string,
|
|
112
|
+
): Promise<string> => {
|
|
113
|
+
const fileName = `ePaperImages/${deviceId}+${uuid}.png`;
|
|
114
|
+
const url = await getSignedFileUrl({ fileName });
|
|
115
|
+
return url;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const populateIotDevices = async (
|
|
119
|
+
data: DeviceDocument[],
|
|
120
|
+
): Promise<any[]> => {
|
|
121
|
+
const nrfList = data.map((e) => e.deviceId);
|
|
122
|
+
const iotDevices = await iotDevicesService.getDevice(nrfList);
|
|
123
|
+
|
|
124
|
+
if (iotDevices) {
|
|
125
|
+
return data.map((e) => {
|
|
126
|
+
const iotDevice = iotDevices.message.find(
|
|
127
|
+
(c) => c.serialNumber === e.deviceId,
|
|
128
|
+
);
|
|
129
|
+
if (e.kind === "anabox-smart") return { ...e.toJSON(), iotDevice };
|
|
130
|
+
return e;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return data;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const queryDevicesByUser = async (
|
|
137
|
+
filter: any,
|
|
138
|
+
options: any,
|
|
139
|
+
): Promise<any> => {
|
|
140
|
+
const devices = await Device.paginate(filter, options);
|
|
141
|
+
return devices;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const getDeviceByUserId = async (
|
|
145
|
+
patient: string,
|
|
146
|
+
): Promise<DeviceDocument | null> => {
|
|
147
|
+
return Device.findOne({ patient });
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export const getById = async (id: string): Promise<DeviceDocument> => {
|
|
151
|
+
const device = await Device.findById(id);
|
|
152
|
+
//if (!device) throw new ApiError(httpStatus.NOT_FOUND, `Device not found id: ${id}`);
|
|
153
|
+
return device;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const getImageById = async (
|
|
157
|
+
id: string,
|
|
158
|
+
uuid: string,
|
|
159
|
+
): Promise<{ url: string }> => {
|
|
160
|
+
const url = await populateDeviceImage(id, uuid);
|
|
161
|
+
return { url };
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const getByIdWithIoT = async (id: string): Promise<any> => {
|
|
165
|
+
const device = await getById(id);
|
|
166
|
+
if (!device) {
|
|
167
|
+
throw new ApiError(httpStatus.NOT_FOUND, `Device not found id: ${id}`);
|
|
168
|
+
}
|
|
169
|
+
const deviceJSON = device.toJSON();
|
|
170
|
+
const iotDevice = await populateIotDevice(device);
|
|
171
|
+
const shadow = await populateIotShadow(device, "settings");
|
|
172
|
+
const deviceStatus = await populateDeviceStatus(device);
|
|
173
|
+
|
|
174
|
+
deviceJSON.iotDevice = iotDevice;
|
|
175
|
+
deviceJSON.shadow = shadow;
|
|
176
|
+
deviceJSON.deviceStatus = deviceStatus;
|
|
177
|
+
|
|
178
|
+
return deviceJSON;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const getDeviceByDeviceId = async (
|
|
182
|
+
deviceId: string,
|
|
183
|
+
): Promise<DeviceDocument | null> => {
|
|
184
|
+
return Device.findOne({ deviceId });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const updatePaymentById = async (
|
|
188
|
+
deviceId: string,
|
|
189
|
+
updateBody: any,
|
|
190
|
+
): Promise<void> => {
|
|
191
|
+
const device = await getById(deviceId);
|
|
192
|
+
device.payment = updateBody;
|
|
193
|
+
await device.save();
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const updateById = async (
|
|
197
|
+
userId: string,
|
|
198
|
+
updateBody: any,
|
|
199
|
+
): Promise<any> => {
|
|
200
|
+
const device = await getById(userId);
|
|
201
|
+
|
|
202
|
+
Object.assign(device, updateBody);
|
|
203
|
+
|
|
204
|
+
await device.save();
|
|
205
|
+
|
|
206
|
+
const shadowBody = {
|
|
207
|
+
state: {
|
|
208
|
+
reported: {},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (device.kind === "anabox-smart") {
|
|
213
|
+
shadowBody.state.reported.take_offset_time = updateBody.takeOffsetTime;
|
|
214
|
+
shadowBody.state.reported.alarm_enable = updateBody.alarmEnable;
|
|
215
|
+
}
|
|
216
|
+
if (deviceKindHasFeature("epaper", device.kind)) {
|
|
217
|
+
const sleepTime = parseInt(device.meta?.sleepTime);
|
|
218
|
+
shadowBody.state.reported.sleepTime = isNaN(sleepTime) ? 3600 : sleepTime;
|
|
219
|
+
shadowBody.state.reported.clearScreen = true;
|
|
220
|
+
shadowBody.state.reported.showOverlay =
|
|
221
|
+
device.meta?.showOverlay === undefined ? true : device.meta?.showOverlay;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// console.log('shadowBody', updateBody, shadowBody, device.kind);
|
|
225
|
+
|
|
226
|
+
const shadowNew = await iotDevicesService.shadowAlarmUpdate(
|
|
227
|
+
device.deviceId,
|
|
228
|
+
shadowBody,
|
|
229
|
+
"settings",
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const iotDevice = await populateIotDevice(device);
|
|
233
|
+
const shadow = await populateIotShadow(device);
|
|
234
|
+
const deviceJSON = device.toJSON();
|
|
235
|
+
deviceJSON.iotDevice = iotDevice;
|
|
236
|
+
deviceJSON.shadow = shadow;
|
|
237
|
+
return deviceJSON;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const updateSingleImageMeta = async (
|
|
241
|
+
deviceId: string,
|
|
242
|
+
shadowNew: any,
|
|
243
|
+
): Promise<any> => {
|
|
244
|
+
const shadowBody = {
|
|
245
|
+
state: {
|
|
246
|
+
reported: shadowNew,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const shadowResult = await iotDevicesService.shadowAlarmUpdate(
|
|
251
|
+
deviceId,
|
|
252
|
+
shadowBody,
|
|
253
|
+
"settings",
|
|
254
|
+
);
|
|
255
|
+
return shadowResult;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const deleteById = async (
|
|
259
|
+
userId: string,
|
|
260
|
+
deleteFromIotApi = true,
|
|
261
|
+
): Promise<DeviceDocument> => {
|
|
262
|
+
const device = await getById(userId);
|
|
263
|
+
if (!device) {
|
|
264
|
+
throw new ApiError(httpStatus.NOT_FOUND, "Device not found");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (device.kind === "anabox-smart" && deleteFromIotApi) {
|
|
268
|
+
const iotDevice = await iotDevicesService.activateDevice(
|
|
269
|
+
device.deviceId,
|
|
270
|
+
device.organization,
|
|
271
|
+
false,
|
|
272
|
+
true,
|
|
273
|
+
);
|
|
274
|
+
if (!iotDevice) {
|
|
275
|
+
throw new ApiError(
|
|
276
|
+
httpStatus.NOT_FOUND,
|
|
277
|
+
"Anabox-Smart Device could not be unlinked.",
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (deviceKindHasFeature("epaper", device.kind) && deleteFromIotApi) {
|
|
283
|
+
const iotDevice = await iotDevicesService.activateDevice(
|
|
284
|
+
device.deviceId,
|
|
285
|
+
device.organization,
|
|
286
|
+
false,
|
|
287
|
+
true,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (!iotDevice) {
|
|
291
|
+
throw new ApiError(
|
|
292
|
+
httpStatus.NOT_FOUND,
|
|
293
|
+
"Epaper device could not be unlinked.",
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
await device.deleteOne();
|
|
299
|
+
return device;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export default {
|
|
303
|
+
getById,
|
|
304
|
+
getByIdWithIoT,
|
|
305
|
+
getDeviceByUserId,
|
|
306
|
+
getDeviceByDeviceId,
|
|
307
|
+
getImageById,
|
|
308
|
+
updateById,
|
|
309
|
+
updatePaymentById,
|
|
310
|
+
deleteById,
|
|
311
|
+
populateIotDevices,
|
|
312
|
+
populateDeviceImage,
|
|
313
|
+
populateDeviceStatus,
|
|
314
|
+
queryDevicesByUser,
|
|
315
|
+
registerDevice,
|
|
316
|
+
getAllDevices,
|
|
317
|
+
createDevice,
|
|
318
|
+
populateIotDevice,
|
|
319
|
+
populateIotShadow,
|
|
320
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { objectId } from "../validations/custom.validation.js";
|
|
4
|
+
import {
|
|
5
|
+
zGet,
|
|
6
|
+
zObjectId,
|
|
7
|
+
zPatchBody,
|
|
8
|
+
zUpdate,
|
|
9
|
+
zDelete,
|
|
10
|
+
zPagination,
|
|
11
|
+
} from "../utils/zValidations.js";
|
|
12
|
+
import { enable } from "agenda/dist/job/enable";
|
|
13
|
+
|
|
14
|
+
extendZodWithOpenApi(z);
|
|
15
|
+
|
|
16
|
+
export const createEntrySchema = {
|
|
17
|
+
body: z
|
|
18
|
+
.object({
|
|
19
|
+
name: z.string().optional(),
|
|
20
|
+
deviceId: z.string().optional(),
|
|
21
|
+
kind: z.string().optional(),
|
|
22
|
+
meta: z.record(z.any()).optional(),
|
|
23
|
+
organization: zObjectId,
|
|
24
|
+
patient: zObjectId.optional(),
|
|
25
|
+
paper: zObjectId.optional().nullable(),
|
|
26
|
+
date: z.string().datetime().optional(),
|
|
27
|
+
})
|
|
28
|
+
.openapi({ description: "Create a new device entry" }),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getUsersSchema = {
|
|
32
|
+
query: z
|
|
33
|
+
.object({
|
|
34
|
+
name: z.string().optional(),
|
|
35
|
+
role: z.string().optional(),
|
|
36
|
+
sortBy: z.string().optional(),
|
|
37
|
+
limit: z.number().int().optional(),
|
|
38
|
+
page: z.number().int().optional(),
|
|
39
|
+
})
|
|
40
|
+
.openapi({ description: "Query users" }),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getEntrySchema = zGet("deviceId");
|
|
44
|
+
|
|
45
|
+
export const updateEntrySchema = {
|
|
46
|
+
...zUpdate("deviceId"),
|
|
47
|
+
body: zPatchBody({
|
|
48
|
+
name: z.string().optional(),
|
|
49
|
+
kind: z.string().optional(),
|
|
50
|
+
deviceId: z.string().optional(),
|
|
51
|
+
organization: zObjectId.optional(),
|
|
52
|
+
patient: zObjectId.optional().nullable(),
|
|
53
|
+
paper: zObjectId.optional().nullable(),
|
|
54
|
+
meta: z.record(z.any()).optional(),
|
|
55
|
+
iotDevice: z.record(z.any()).optional(),
|
|
56
|
+
shadow: z.union([z.string(), z.number()]).optional(),
|
|
57
|
+
alarmEnable: z.number().int().optional(),
|
|
58
|
+
takeOffsetTime: z.number().int().optional(),
|
|
59
|
+
updatedAt: z.string().datetime().optional(),
|
|
60
|
+
createdAt: z.string().datetime().optional(),
|
|
61
|
+
payment: z.record(z.any()).optional(),
|
|
62
|
+
lut: z.string().optional(),
|
|
63
|
+
sleepTime: z.string().optional(),
|
|
64
|
+
clearScreen: z.boolean().optional(),
|
|
65
|
+
}).openapi({ description: "Fields to update on a device entry" }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const getEventsSchema = {
|
|
69
|
+
query: z
|
|
70
|
+
.object({
|
|
71
|
+
DateStart: z.string(),
|
|
72
|
+
DateEnd: z.string(),
|
|
73
|
+
TypeFilter: z.string().optional(),
|
|
74
|
+
})
|
|
75
|
+
.openapi({ description: "Fetch device events in a time range" }),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const deleteEntrySchema = zDelete("deviceId");
|
|
79
|
+
|
|
80
|
+
export const createCheckoutSessionSchema = {};
|
|
81
|
+
export const createCustomerPortalSessionSchema = {
|
|
82
|
+
params: z.object({
|
|
83
|
+
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
84
|
+
}),
|
|
85
|
+
body: z.object({
|
|
86
|
+
domain: z.string().url().openapi({
|
|
87
|
+
description: "Domain for return URL",
|
|
88
|
+
example: "https://web.wirewire.de",
|
|
89
|
+
}),
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
export const createDeviceSchema = {
|
|
93
|
+
body: z.object({
|
|
94
|
+
kind: z.string().optional(),
|
|
95
|
+
patient: zObjectId.optional().nullable(),
|
|
96
|
+
paper: zObjectId.optional().nullable(),
|
|
97
|
+
organization: zObjectId.openapi({ description: "Organization ObjectId" }),
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
export const deleteDeviceSchema = zDelete("deviceId");
|
|
101
|
+
export const getDeviceSchema = zGet("deviceId");
|
|
102
|
+
export const getImageSchema = zGet("deviceId");
|
|
103
|
+
export const ledLightSchema = {
|
|
104
|
+
params: z.object({
|
|
105
|
+
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
106
|
+
}),
|
|
107
|
+
body: z
|
|
108
|
+
.object({
|
|
109
|
+
led: z.array(z.tuple([z.number().int(), z.number().int()])),
|
|
110
|
+
timeout: z.number().int(),
|
|
111
|
+
})
|
|
112
|
+
.openapi({
|
|
113
|
+
description: "LED light configuration for device",
|
|
114
|
+
example: {
|
|
115
|
+
led: [
|
|
116
|
+
[0, 0],
|
|
117
|
+
[1, 0],
|
|
118
|
+
[2, 0],
|
|
119
|
+
[3, 0],
|
|
120
|
+
[4, 0],
|
|
121
|
+
[5, 0],
|
|
122
|
+
[6, 0],
|
|
123
|
+
[7, 0],
|
|
124
|
+
[8, 0],
|
|
125
|
+
[9, 0],
|
|
126
|
+
[10, 0],
|
|
127
|
+
[11, 0],
|
|
128
|
+
[12, 0],
|
|
129
|
+
[13, 0],
|
|
130
|
+
[14, 0],
|
|
131
|
+
[15, 100],
|
|
132
|
+
[16, 100],
|
|
133
|
+
[17, 100],
|
|
134
|
+
[18, 100],
|
|
135
|
+
[19, 100],
|
|
136
|
+
[20, 100],
|
|
137
|
+
[21, 100],
|
|
138
|
+
[22, 0],
|
|
139
|
+
[23, 0],
|
|
140
|
+
[24, 0],
|
|
141
|
+
[25, 0],
|
|
142
|
+
[26, 0],
|
|
143
|
+
[27, 0],
|
|
144
|
+
[28, 0],
|
|
145
|
+
],
|
|
146
|
+
timeout: 40,
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
};
|
|
150
|
+
export const pingDeviceSchema = {
|
|
151
|
+
params: z.object({
|
|
152
|
+
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
153
|
+
}),
|
|
154
|
+
query: z.object({
|
|
155
|
+
dataResponse: z
|
|
156
|
+
.string()
|
|
157
|
+
.openapi({ description: "Data response", example: "false" }),
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const resetDeviceSchema = {
|
|
162
|
+
params: z.object({
|
|
163
|
+
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const rebootDeviceSchema = zGet("deviceId");
|
|
168
|
+
|
|
169
|
+
export const registerDeviceSchema = {
|
|
170
|
+
...zPagination,
|
|
171
|
+
body: z.object({
|
|
172
|
+
enable: z.boolean(),
|
|
173
|
+
organization: zObjectId,
|
|
174
|
+
patient: zObjectId.optional().nullable(),
|
|
175
|
+
paper: zObjectId.optional().nullable(),
|
|
176
|
+
}),
|
|
177
|
+
params: z.object({
|
|
178
|
+
deviceId: z.string(),
|
|
179
|
+
}),
|
|
180
|
+
query: zPagination.query.extend({
|
|
181
|
+
patient: zObjectId.optional(),
|
|
182
|
+
organization: zObjectId.optional(),
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const queryDevicesSchema = {
|
|
187
|
+
...zPagination,
|
|
188
|
+
query: zPagination.query.extend({
|
|
189
|
+
patient: zObjectId.optional(),
|
|
190
|
+
organization: zObjectId.optional(),
|
|
191
|
+
}),
|
|
192
|
+
};
|
|
193
|
+
export const subscriptionSchema = {
|
|
194
|
+
params: z.object({
|
|
195
|
+
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
196
|
+
}),
|
|
197
|
+
};
|
|
198
|
+
export const uploadSingleImageFromWebsiteSchema = {};
|
|
199
|
+
export const updateDeviceSchema = {
|
|
200
|
+
...zUpdate("deviceId"),
|
|
201
|
+
body: zPatchBody({
|
|
202
|
+
intake: z.record(z.any()).optional(),
|
|
203
|
+
meta: z.record(z.any()).optional(),
|
|
204
|
+
organization: zObjectId.optional(),
|
|
205
|
+
patient: zObjectId.optional().nullable(),
|
|
206
|
+
paper: zObjectId.optional().nullable(),
|
|
207
|
+
kind: z.string().optional(),
|
|
208
|
+
deviceId: z.string().optional(),
|
|
209
|
+
alarmEnable: z.number().int().optional(),
|
|
210
|
+
takeOffsetTime: z.number().int().optional(),
|
|
211
|
+
iotDevice: z.record(z.any()).optional(),
|
|
212
|
+
payment: z.record(z.any()).optional(),
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const updateSingleImageMetaSchema = {};
|
|
217
|
+
export const uploadSingleImageSchema = {
|
|
218
|
+
body: z.object({
|
|
219
|
+
file: z.instanceof(File).openapi({ description: "File to upload" }),
|
|
220
|
+
}),
|
|
221
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import httpStatus from 'http-status';
|
|
2
|
+
|
|
3
|
+
import pick from '../../src/utils/pick';
|
|
4
|
+
import ApiError from '../../src/utils/ApiError';
|
|
5
|
+
import catchAsync from '../../src/utils/catchAsync';
|
|
6
|
+
import devicesNotificationsService from './devicesNotifications.service';
|
|
7
|
+
|
|
8
|
+
import type { Request, Response } from 'express';
|
|
9
|
+
|
|
10
|
+
export const createEntry = catchAsync(async (req: Request, res: Response) => {
|
|
11
|
+
const user = await devicesNotificationsService.createNotification({ user: res.req.auth.sub, token: req.body.token });
|
|
12
|
+
res.status(httpStatus.CREATED).send(user);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const setDeviceToken = catchAsync(async (req: Request, res: Response) => {
|
|
16
|
+
const user = await devicesNotificationsService.setDeviceToken({ user: res.req.auth.sub, body: req.body });
|
|
17
|
+
res.status(httpStatus.CREATED).send(user);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const removeDeviceToken = catchAsync(async (req: Request, res: Response) => {
|
|
21
|
+
const user = await devicesNotificationsService.removeDeviceToken({ user: res.req.auth.sub, body: req.body });
|
|
22
|
+
res.status(httpStatus.CREATED).send(user);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const getEntries = catchAsync(async (req: Request, res: Response) => {
|
|
26
|
+
const filter = pick(req.query, ['name', 'role']);
|
|
27
|
+
const options = pick(req.query, ['sortBy', 'limit', 'page']);
|
|
28
|
+
const result = await devicesNotificationsService.queryNotifications(filter, options);
|
|
29
|
+
res.send(result);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const queryNotificationsByUser = catchAsync(async (req: Request, res: Response) => {
|
|
33
|
+
const filter = pick({ user: res.req.auth.sub }, ['user']);
|
|
34
|
+
const options = pick(req.query, ['sortBy', 'limit', 'page']);
|
|
35
|
+
const result = await devicesNotificationsService.queryNotificationsByUser(filter, options);
|
|
36
|
+
res.send(result);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const getEntry = catchAsync(async (req: Request, res: Response) => {
|
|
40
|
+
const notification = await devicesNotificationsService.getById(req.params.notificationId);
|
|
41
|
+
if (!notification) {
|
|
42
|
+
throw new ApiError(httpStatus.NOT_FOUND, 'Notification not found');
|
|
43
|
+
}
|
|
44
|
+
res.send(notification);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const updateEntry = catchAsync(async (req: Request, res: Response) => {
|
|
48
|
+
const user = await devicesNotificationsService.updateById(req.params.notificationId, req.body);
|
|
49
|
+
res.send(user);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const deleteEntry = catchAsync(async (req: Request, res: Response) => {
|
|
53
|
+
const entry = await devicesNotificationsService.deleteById(req.params.notificationId);
|
|
54
|
+
res.send(entry);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const cleanup = catchAsync(async (req: Request, res: Response) => {
|
|
58
|
+
const entry = await devicesNotificationsService.cleanup();
|
|
59
|
+
res.send(entry);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
createEntry,
|
|
64
|
+
getEntries,
|
|
65
|
+
setDeviceToken,
|
|
66
|
+
removeDeviceToken,
|
|
67
|
+
cleanup,
|
|
68
|
+
queryNotificationsByUser,
|
|
69
|
+
getEntry,
|
|
70
|
+
updateEntry,
|
|
71
|
+
deleteEntry,
|
|
72
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import mongoose, { Schema, Document, Model } from 'mongoose';
|
|
2
|
+
import type { PaginateModel } from 'mongoose';
|
|
3
|
+
import { toJSON, paginate } from '../../src/models/plugins/index';
|
|
4
|
+
|
|
5
|
+
interface Token {
|
|
6
|
+
token: string;
|
|
7
|
+
deviceId: string;
|
|
8
|
+
plattform: string;
|
|
9
|
+
date: Date;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Settings {
|
|
13
|
+
intake: { email: boolean; push: boolean };
|
|
14
|
+
'intake-reminder': { email: boolean; push: boolean };
|
|
15
|
+
battery: { email: boolean; push: boolean };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DeviceNotificationDocument extends Document {
|
|
19
|
+
user: string;
|
|
20
|
+
tokens: Token[];
|
|
21
|
+
bounceEmail: string;
|
|
22
|
+
settings: Settings;
|
|
23
|
+
createdAt: Date;
|
|
24
|
+
updatedAt: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type DeviceNotificationModel = Model<DeviceNotificationDocument> & PaginateModel<DeviceNotificationDocument>;
|
|
28
|
+
|
|
29
|
+
const deviceNotificationSchema = new Schema<DeviceNotificationDocument>(
|
|
30
|
+
{
|
|
31
|
+
user: { type: String, required: true },
|
|
32
|
+
tokens: [
|
|
33
|
+
{
|
|
34
|
+
token: { type: String, required: true },
|
|
35
|
+
deviceId: { type: String, required: true },
|
|
36
|
+
plattform: { type: String, required: true },
|
|
37
|
+
date: {
|
|
38
|
+
type: Date,
|
|
39
|
+
default: Date.now,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
bounceEmail: { type: String },
|
|
44
|
+
settings: {
|
|
45
|
+
intake: { email: { type: Boolean, default: true }, push: { type: Boolean, default: true } },
|
|
46
|
+
'intake-reminder': {
|
|
47
|
+
email: { type: Boolean, default: true },
|
|
48
|
+
push: { type: Boolean, default: true },
|
|
49
|
+
},
|
|
50
|
+
battery: { email: { type: Boolean, default: true }, push: { type: Boolean, default: true } },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
timestamps: true,
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Add plugins
|
|
59
|
+
deviceNotificationSchema.plugin(toJSON);
|
|
60
|
+
deviceNotificationSchema.plugin(paginate);
|
|
61
|
+
|
|
62
|
+
const DeviceNotifications = mongoose.model<DeviceNotificationDocument, DeviceNotificationModel>(
|
|
63
|
+
'DeviceNotification',
|
|
64
|
+
deviceNotificationSchema,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export default DeviceNotifications;
|