@internetderdinge/api 1.229.0 → 1.229.1

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 (167) hide show
  1. package/dist/src/accounts/accounts.controller.js +89 -0
  2. package/dist/src/accounts/accounts.route.js +101 -0
  3. package/dist/src/accounts/accounts.schemas.js +12 -0
  4. package/dist/src/accounts/accounts.service.js +65 -0
  5. package/dist/src/accounts/accounts.validation.js +99 -0
  6. package/dist/src/accounts/auth0.service.js +188 -0
  7. package/dist/src/config/config.js +48 -0
  8. package/dist/src/config/logger.js +27 -0
  9. package/dist/src/config/morgan.js +16 -0
  10. package/dist/src/config/passport.cjs +28 -0
  11. package/dist/src/config/roles.js +11 -0
  12. package/dist/src/config/tokens.cjs +10 -0
  13. package/dist/src/devices/devices.controller.js +172 -0
  14. package/dist/src/devices/devices.model.js +94 -0
  15. package/dist/src/devices/devices.route.js +153 -0
  16. package/dist/src/devices/devices.schemas.js +84 -0
  17. package/dist/src/devices/devices.service.js +198 -0
  18. package/dist/src/devices/devices.types.js +1 -0
  19. package/dist/src/devices/devices.validation.js +257 -0
  20. package/dist/src/devicesNotifications/devicesNotifications.controller.js +69 -0
  21. package/dist/src/devicesNotifications/devicesNotifications.model.js +39 -0
  22. package/dist/src/devicesNotifications/devicesNotifications.route.js +124 -0
  23. package/dist/src/devicesNotifications/devicesNotifications.schemas.js +10 -0
  24. package/dist/src/devicesNotifications/devicesNotifications.service.js +181 -0
  25. package/dist/src/devicesNotifications/devicesNotifications.validation.js +46 -0
  26. package/dist/src/email/email.service.js +580 -0
  27. package/dist/src/files/upload.service.js +124 -0
  28. package/dist/src/i18n/i18n.js +38 -0
  29. package/dist/src/i18n/saveMissingLocalJsonBackend.js +53 -0
  30. package/dist/src/i18n/types.js +1 -0
  31. package/dist/src/index.js +48 -0
  32. package/dist/src/iotdevice/iotdevice.controller.js +96 -0
  33. package/dist/src/iotdevice/iotdevice.model.js +17 -0
  34. package/dist/src/iotdevice/iotdevice.route.js +143 -0
  35. package/dist/src/iotdevice/iotdevice.schemas.js +60 -0
  36. package/dist/src/iotdevice/iotdevice.service.js +579 -0
  37. package/dist/src/iotdevice/iotdevice.types.js +1 -0
  38. package/dist/src/iotdevice/iotdevice.validation.js +54 -0
  39. package/dist/src/middlewares/auth.js +75 -0
  40. package/dist/src/middlewares/checkJwt.cjs +17 -0
  41. package/dist/src/middlewares/error.js +36 -0
  42. package/dist/src/middlewares/mongooseValidations/ensureSameOrganization.js +13 -0
  43. package/dist/src/middlewares/rateLimiter.js +7 -0
  44. package/dist/src/middlewares/validate.js +18 -0
  45. package/dist/src/middlewares/validateAction.js +35 -0
  46. package/dist/src/middlewares/validateAdmin.js +18 -0
  47. package/dist/src/middlewares/validateAi.js +16 -0
  48. package/dist/src/middlewares/validateCurrentAuthUser.js +17 -0
  49. package/dist/src/middlewares/validateCurrentUser.js +20 -0
  50. package/dist/src/middlewares/validateDevice.js +98 -0
  51. package/dist/src/middlewares/validateDeviceUserOrganization.js +26 -0
  52. package/dist/src/middlewares/validateOrganization.js +63 -0
  53. package/dist/src/middlewares/validateQuerySearchUserAndOrganization.js +44 -0
  54. package/dist/src/middlewares/validateTokens.js +23 -0
  55. package/dist/src/middlewares/validateUser.js +38 -0
  56. package/dist/src/middlewares/validateZod.js +33 -0
  57. package/dist/src/models/plugins/index.js +4 -0
  58. package/dist/src/models/plugins/paginate.plugin.js +117 -0
  59. package/dist/src/models/plugins/paginateNew.plugin.js +185 -0
  60. package/dist/src/models/plugins/simplePopulate.js +16 -0
  61. package/dist/src/models/plugins/toJSON.plugin.js +35 -0
  62. package/dist/src/organizations/organizations.controller.js +64 -0
  63. package/dist/src/organizations/organizations.model.js +41 -0
  64. package/dist/src/organizations/organizations.route.js +98 -0
  65. package/dist/src/organizations/organizations.schemas.js +7 -0
  66. package/dist/src/organizations/organizations.service.js +59 -0
  67. package/dist/src/organizations/organizations.validation.js +62 -0
  68. package/dist/src/pdf/pdf.controller.js +24 -0
  69. package/dist/src/pdf/pdf.route.js +22 -0
  70. package/dist/src/pdf/pdf.schemas.js +6 -0
  71. package/dist/src/pdf/pdf.service.js +65 -0
  72. package/dist/src/pdf/pdf.validation.js +27 -0
  73. package/dist/src/tokens/tokens.controller.js +60 -0
  74. package/dist/src/tokens/tokens.model.js +18 -0
  75. package/dist/src/tokens/tokens.route.js +52 -0
  76. package/dist/src/tokens/tokens.schemas.js +14 -0
  77. package/dist/src/tokens/tokens.service.js +30 -0
  78. package/dist/src/tokens/tokens.validation.js +9 -0
  79. package/dist/src/types/routeSpec.js +1 -0
  80. package/dist/src/users/users.controller.js +147 -0
  81. package/dist/src/users/users.model.js +50 -0
  82. package/dist/src/users/users.route.js +137 -0
  83. package/dist/src/users/users.schemas.js +69 -0
  84. package/dist/src/users/users.service.js +295 -0
  85. package/dist/src/users/users.types.js +1 -0
  86. package/dist/src/users/users.validation.js +144 -0
  87. package/dist/src/utils/ApiError.js +16 -0
  88. package/dist/src/utils/buildRouterAndDocs.js +72 -0
  89. package/dist/src/utils/catchAsync.js +4 -0
  90. package/dist/src/utils/comparePapers.service.js +32 -0
  91. package/dist/src/utils/deviceUtils.js +63 -0
  92. package/dist/src/utils/filterOptions.js +24 -0
  93. package/dist/src/utils/medicationName.js +10 -0
  94. package/dist/src/utils/pick.js +16 -0
  95. package/dist/src/utils/registerOpenApi.js +28 -0
  96. package/dist/src/utils/urlUtils.js +15 -0
  97. package/dist/src/utils/userName.js +22 -0
  98. package/dist/src/utils/zValidations.js +124 -0
  99. package/dist/src/validations/auth.validation.cjs +53 -0
  100. package/dist/src/validations/custom.validation.js +19 -0
  101. package/dist/src/validations/index.cjs +3 -0
  102. package/package.json +13 -3
  103. package/scripts/release-and-sync-paperless.mjs +135 -0
  104. package/src/accounts/accounts.controller.ts +1 -0
  105. package/src/accounts/accounts.service.ts +1 -0
  106. package/src/accounts/accounts.validation.ts +6 -3
  107. package/src/accounts/auth0.service.ts +55 -28
  108. package/src/config/config.ts +6 -0
  109. package/src/config/logger.ts +15 -9
  110. package/src/devices/devices.controller.ts +7 -1
  111. package/src/devices/devices.model.ts +4 -1
  112. package/src/devices/devices.schemas.ts +10 -8
  113. package/src/devices/devices.service.ts +1 -0
  114. package/src/devices/devices.types.ts +1 -0
  115. package/src/devices/devices.validation.ts +85 -23
  116. package/src/devicesNotifications/devicesNotifications.controller.ts +57 -28
  117. package/src/devicesNotifications/devicesNotifications.model.ts +20 -12
  118. package/src/devicesNotifications/devicesNotifications.service.ts +35 -17
  119. package/src/files/upload.service.ts +52 -28
  120. package/src/i18n/i18n.ts +1 -1
  121. package/src/i18n/types.ts +1 -0
  122. package/src/index.ts +47 -0
  123. package/src/iotdevice/iotdevice.controller.ts +1 -0
  124. package/src/iotdevice/iotdevice.model.ts +6 -3
  125. package/src/iotdevice/iotdevice.route.ts +85 -76
  126. package/src/iotdevice/iotdevice.service.ts +4 -3
  127. package/src/iotdevice/iotdevice.types.ts +6 -0
  128. package/src/middlewares/auth.ts +2 -8
  129. package/src/middlewares/error.ts +26 -12
  130. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +4 -3
  131. package/src/middlewares/validateAi.ts +17 -9
  132. package/src/middlewares/validateDevice.ts +1 -0
  133. package/src/middlewares/validateDeviceUserOrganization.ts +1 -0
  134. package/src/middlewares/validateOrganization.ts +1 -1
  135. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +1 -0
  136. package/src/middlewares/validateTokens.ts +2 -1
  137. package/src/middlewares/validateUser.ts +1 -0
  138. package/src/models/plugins/index.ts +5 -4
  139. package/src/models/plugins/paginate.plugin.ts +26 -16
  140. package/src/models/plugins/paginateNew.plugin.ts +33 -21
  141. package/src/models/plugins/simplePopulate.ts +8 -3
  142. package/src/models/plugins/toJSON.plugin.ts +12 -5
  143. package/src/organizations/organizations.controller.ts +1 -2
  144. package/src/organizations/organizations.model.ts +4 -4
  145. package/src/organizations/organizations.route.ts +1 -1
  146. package/src/organizations/organizations.service.ts +15 -6
  147. package/src/pdf/pdf.controller.ts +18 -1
  148. package/src/pdf/pdf.service.ts +25 -16
  149. package/src/tokens/tokens.controller.ts +6 -8
  150. package/src/tokens/tokens.model.ts +3 -1
  151. package/src/tokens/tokens.service.ts +3 -2
  152. package/src/types/express.d.ts +17 -0
  153. package/src/types/mongoose.d.ts +22 -0
  154. package/src/users/users.controller.ts +8 -9
  155. package/src/users/users.model.ts +6 -5
  156. package/src/users/users.route.ts +0 -1
  157. package/src/users/users.service.ts +16 -0
  158. package/src/users/users.types.ts +1 -0
  159. package/src/users/users.validation.ts +6 -2
  160. package/src/utils/ApiError.ts +8 -1
  161. package/src/utils/buildRouterAndDocs.ts +56 -21
  162. package/src/utils/catchAsync.ts +27 -3
  163. package/src/utils/medicationName.ts +5 -4
  164. package/src/utils/pick.ts +5 -1
  165. package/src/utils/userName.ts +1 -0
  166. package/src/utils/zValidations.ts +78 -26
  167. package/tsconfig.json +13 -4
@@ -0,0 +1,579 @@
1
+ // @ts-nocheck
2
+ import httpStatus from "http-status";
3
+ import axios from "axios";
4
+ import AWS from "aws-sdk";
5
+ import { deviceKindHasFeature } from "../utils/deviceUtils";
6
+ import ApiError from "../utils/ApiError";
7
+ import { getAuth0Token } from "../accounts/auth0.service";
8
+ import { uploadImage, getSignedFileUrl, } from "../files/upload.service";
9
+ import { compareImages } from "../utils/comparePapers.service";
10
+ import IotDevice from "./iotdevice.model";
11
+ import { fileTypeFromBuffer } from "file-type";
12
+ import iotsdk from "aws-iot-device-sdk-v2";
13
+ const iot = iotsdk.iot;
14
+ const mqtt = iotsdk.mqtt;
15
+ export const SIMILARITY_THRESHOLD = Number(process.env.EPAPER_SIMILARITY_THRESHOLD ?? 99.995);
16
+ const buildUploadResponse = (data, similarityPercentage, skippedUpload) => {
17
+ return { /*...data,*/ key: data?.Key, similarityPercentage, skippedUpload };
18
+ };
19
+ const downloadPreviousOriginalImage = async (id) => {
20
+ if (!id) {
21
+ return null;
22
+ }
23
+ try {
24
+ const signedUrl = await getSignedFileUrl({
25
+ fileName: `ePaperImages/${id}original.png`,
26
+ });
27
+ const response = await axios.get(signedUrl, {
28
+ responseType: "arraybuffer",
29
+ });
30
+ return Buffer.from(response.data);
31
+ }
32
+ catch (error) {
33
+ console.warn(`Unable to download previous image for ${id}:`, error?.message || error);
34
+ return null;
35
+ }
36
+ };
37
+ const evaluateSimilarityBeforeUpload = async (id, bufferOriginal) => {
38
+ if (!bufferOriginal) {
39
+ return { similarityPercentage: null, skipUpload: false };
40
+ }
41
+ const previousBuffer = await downloadPreviousOriginalImage(id);
42
+ if (!previousBuffer) {
43
+ return { similarityPercentage: null, skipUpload: false };
44
+ }
45
+ try {
46
+ const similarityPercentage = await compareImages(previousBuffer, bufferOriginal);
47
+ return {
48
+ similarityPercentage,
49
+ skipUpload: similarityPercentage >= SIMILARITY_THRESHOLD,
50
+ };
51
+ }
52
+ catch (error) {
53
+ console.warn(`Similarity comparison failed for ${id}:`, error?.message || error);
54
+ return { similarityPercentage: null, skipUpload: false };
55
+ }
56
+ };
57
+ /**
58
+ * Get events for a device
59
+ * @param {Object} params
60
+ * @returns {Promise<any>}
61
+ */
62
+ export const getEvents = async (params) => {
63
+ const accessToken = await getAuth0Token();
64
+ try {
65
+ const response = await axios.get(`${process.env.IOT_API_URL}getevent`, {
66
+ headers: { Authorization: `Bearer ${accessToken}` },
67
+ params: {
68
+ DeviceId: params.DeviceId,
69
+ DateStart: !params.createdAt
70
+ ? params.DateStart
71
+ : params.DateStart < params.createdAt
72
+ ? params.DateStart
73
+ : params.createdAt,
74
+ DateEnd: params.DateEnd,
75
+ TypeFilter: params.TypeFilter,
76
+ },
77
+ });
78
+ return response.data;
79
+ }
80
+ catch (error) {
81
+ return null;
82
+ }
83
+ };
84
+ /**
85
+ * Activate a device
86
+ * @param {string} deviceId
87
+ * @param {string} organization
88
+ * @param {boolean} enable
89
+ * @param {boolean} resetDevice
90
+ * @returns {Promise<any>}
91
+ */
92
+ export const activateDevice = async (deviceId, organization, enable = true, resetDevice = false) => {
93
+ const accessToken = await getAuth0Token();
94
+ try {
95
+ const data = {
96
+ deviceName: deviceId,
97
+ organizationName: organization,
98
+ reset: resetDevice,
99
+ enable,
100
+ };
101
+ const config = {
102
+ method: "post",
103
+ url: `${deviceId.slice(0, 3) === "epd" ? process.env.IOT_API_URL_EPAPER : process.env.IOT_API_URL}activatedevice`,
104
+ headers: {
105
+ Authorization: `Bearer ${accessToken}`,
106
+ },
107
+ data,
108
+ };
109
+ const response = await axios(config);
110
+ return response.data;
111
+ }
112
+ catch (error) {
113
+ console.error(error);
114
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t get activated");
115
+ }
116
+ };
117
+ /**
118
+ * Ping device
119
+ * @param {string} deviceId
120
+ * @returns {Promise<Device>}
121
+ */
122
+ export const pingDevice = async (deviceId, body) => {
123
+ const accessToken = await getAuth0Token();
124
+ try {
125
+ const config = {
126
+ method: "get",
127
+ url: `${process.env.IOT_API_URL}ping`,
128
+ params: {
129
+ DeviceId: deviceId,
130
+ ...body,
131
+ },
132
+ headers: {
133
+ Authorization: `Bearer ${accessToken}`,
134
+ },
135
+ };
136
+ const response = await axios(config);
137
+ return response.data;
138
+ }
139
+ catch (error) {
140
+ //console.log(error);
141
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t get pinged");
142
+ }
143
+ };
144
+ /**
145
+ * Reboot device
146
+ * @param {String} deviveId
147
+ * @returns {Promise<Device>}
148
+ */
149
+ export const rebootDevice = async (deviceId) => {
150
+ const accessToken = await getAuth0Token();
151
+ try {
152
+ const config = {
153
+ method: "get",
154
+ url: `${process.env.IOT_API_URL}rebootdevice?DeviceId=${deviceId}`,
155
+ headers: {
156
+ Authorization: `Bearer ${accessToken}`,
157
+ },
158
+ };
159
+ const response = await axios(config);
160
+ return response.data;
161
+ }
162
+ catch (error) {
163
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t reboot");
164
+ }
165
+ };
166
+ /**
167
+ * Update device meta
168
+ * @param {String} deviveId
169
+ * @returns {Promise<Device>}
170
+ */
171
+ export const updateDevice = async (deviceId, meta) => {
172
+ const accessToken = await getAuth0Token();
173
+ try {
174
+ const config = {
175
+ method: "post",
176
+ url: `${process.env.IOT_API_URL}updatedevice`,
177
+ headers: {
178
+ Authorization: `Bearer ${accessToken}`,
179
+ },
180
+ data: {
181
+ deviceName: deviceId,
182
+ ...meta,
183
+ },
184
+ };
185
+ const response = await axios(config);
186
+ return response.data;
187
+ }
188
+ catch (error) {
189
+ console.log(error);
190
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t update meta");
191
+ }
192
+ };
193
+ /**
194
+ * Case status of device
195
+ * @param {String} deviveId
196
+ * @returns {Promise<Device>}
197
+ */
198
+ const caseStatus = async (deviceId) => {
199
+ const accessToken = await getAuth0Token();
200
+ try {
201
+ const config = {
202
+ method: "get",
203
+ url: `${process.env.IOT_API_URL}getcase?DeviceId=${deviceId}`,
204
+ headers: {
205
+ Authorization: `Bearer ${accessToken}`,
206
+ },
207
+ };
208
+ const response = await axios(config);
209
+ return response.data;
210
+ }
211
+ catch (error) {
212
+ //console.log(error);
213
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t get status");
214
+ }
215
+ };
216
+ /**
217
+ * Case status of device
218
+ * @param {String} deviveId
219
+ * @returns {Promise<Device>}
220
+ */
221
+ export const alarmsDevice = async (deviceId) => {
222
+ const accessToken = await getAuth0Token();
223
+ try {
224
+ const config = {
225
+ method: "get",
226
+ url: `${process.env.IOT_API_URL}getcase?DeviceId=${deviceId}`,
227
+ headers: {
228
+ Authorization: `Bearer ${accessToken}`,
229
+ },
230
+ };
231
+ const response = await axios(config);
232
+ return response.data;
233
+ }
234
+ catch (error) {
235
+ // console.log(error);
236
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t get status");
237
+ }
238
+ };
239
+ /**
240
+ * Get device information
241
+ * @param {Object} userBody
242
+ * @returns {Promise<Device>}
243
+ */
244
+ export const getDevice = async (deviceName) => {
245
+ const accessToken = await getAuth0Token();
246
+ try {
247
+ const response = await axios.post(`${process.env.IOT_API_URL}getdevicelist`, { deviceName }, {
248
+ headers: { Authorization: `Bearer ${accessToken}` },
249
+ });
250
+ return response.data;
251
+ }
252
+ catch (error) {
253
+ console.error(error.message);
254
+ return null;
255
+ }
256
+ };
257
+ /**
258
+ * Get device information
259
+ * shows battery level, signal strength, and other information
260
+ * @param {Object} userBody
261
+ * @returns {Promise<Device>}
262
+ */
263
+ export const getDeviceStatusList = async (deviceName) => {
264
+ const accessToken = await getAuth0Token();
265
+ try {
266
+ const response = await axios.post(`${process.env.IOT_API_URL}getdevicestatuslist`, {
267
+ returnAll: true,
268
+ DeviceId: [],
269
+ }, {
270
+ headers: { Authorization: `Bearer ${accessToken}` },
271
+ });
272
+ // console.log('response', response.data);
273
+ return response.data.message;
274
+ }
275
+ catch (error) {
276
+ console.error(error);
277
+ console.error(`getDeviceStatusList device error: ${deviceName}`);
278
+ return null;
279
+ }
280
+ };
281
+ /**
282
+ * Get device information
283
+ * @param {Object} userBody
284
+ * @returns {Promise<Device>}
285
+ */
286
+ export const getDeviceStatus = async (deviceName, kind) => {
287
+ const accessToken = await getAuth0Token();
288
+ try {
289
+ const response = await axios.get(`${deviceKindHasFeature("epaper", kind) ? process.env.IOT_API_URL_EPAPER : process.env.IOT_API_URL}getdevicestatus`, {
290
+ params: { DeviceId: deviceName },
291
+ headers: { Authorization: `Bearer ${accessToken}` },
292
+ });
293
+ // console.log('getDeviceStatus response', response.data);
294
+ // TODO: Align return values
295
+ return deviceKindHasFeature("epaper", kind)
296
+ ? response.data
297
+ : response.data.message;
298
+ }
299
+ catch (error) {
300
+ console.error(`getDeviceStatus device error: ${deviceName}`, error.message, error.statusMessage);
301
+ return null;
302
+ }
303
+ };
304
+ /**
305
+ * Shadow alarm update
306
+ * @param {Object} userBody
307
+ * @returns {Promise<Device>}
308
+ */
309
+ export const shadowAlarmGet = async (deviceName, shadowName) => {
310
+ const iotdata = new AWS.IotData({
311
+ endpoint: "a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com",
312
+ });
313
+ if (!deviceName)
314
+ return { error: "deviceName is required" };
315
+ const params = {
316
+ thingName: deviceName,
317
+ shadowName,
318
+ };
319
+ try {
320
+ const response = await iotdata.getThingShadow(params).promise();
321
+ return JSON.parse(response.payload);
322
+ }
323
+ catch (e) {
324
+ // console.log(deviceName, e);
325
+ return "error";
326
+ }
327
+ };
328
+ /**
329
+ * LED update with Timeout
330
+ * @param {Object} userBody
331
+ * @returns {Promise<Device>}
332
+ */
333
+ export const ledLightHint = async (deviceName, body) => {
334
+ const accessToken = await getAuth0Token();
335
+ try {
336
+ const response = await axios.post(`${process.env.IOT_API_URL}setled?DeviceId=${deviceName}`, body, {
337
+ headers: { Authorization: `Bearer ${accessToken}` },
338
+ });
339
+ return response.data;
340
+ }
341
+ catch (error) {
342
+ console.error(error);
343
+ return null;
344
+ }
345
+ };
346
+ /**
347
+ * Shadow alarm update
348
+ * @param {Object} userBody
349
+ * @returns {Promise<Device>}
350
+ */
351
+ const shadowAlarmUpdate = async (deviceName, alarms, shadowName) => {
352
+ const data = alarms;
353
+ const iotdata = new AWS.IotData({
354
+ endpoint: "a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com",
355
+ });
356
+ if (!deviceName) {
357
+ return { error: "no deviceId" };
358
+ }
359
+ const params = {
360
+ payload: JSON.stringify(data),
361
+ thingName: deviceName,
362
+ shadowName,
363
+ };
364
+ try {
365
+ const response = await iotdata.updateThingShadow(params).promise();
366
+ return JSON.parse(response.payload);
367
+ }
368
+ catch (e) {
369
+ return "error";
370
+ }
371
+ };
372
+ export const uploadSingleImage = async ({ deviceName, buffer, bufferOriginal, bufferEditable, id, paperId, }) => {
373
+ try {
374
+ const { skipUpload, similarityPercentage } = await evaluateSimilarityBeforeUpload(id, bufferOriginal);
375
+ var response = {};
376
+ if (skipUpload) {
377
+ return buildUploadResponse({ message: "Image skipped due to similarity threshold" }, similarityPercentage, true);
378
+ }
379
+ if (!bufferOriginal) {
380
+ throw new Error("bufferOriginal is required to upload an image");
381
+ }
382
+ // console.log(`Uploading image for ${id} on ${deviceName}; similarity ${similarityPercentage?.toFixed(5)}%`);
383
+ if (deviceName) {
384
+ const accessToken = await getAuth0Token();
385
+ response = await axios.post(`${process.env.IOT_API_URL_EPAPER}uploads`, { deviceName }, {
386
+ headers: { Authorization: `Bearer ${accessToken}` },
387
+ });
388
+ if (!response.data.uploadURL) {
389
+ console.log("No upload URL received", response.data);
390
+ }
391
+ else {
392
+ await axios.put(response.data.uploadURL, buffer, {
393
+ // onUploadProgress: (progressEvent) => console.log('file progress', progressEvent.loaded),
394
+ headers: { "Content-Type": "text/octet-stream" },
395
+ });
396
+ }
397
+ }
398
+ const type = await fileTypeFromBuffer(buffer);
399
+ const fileName = `ePaperImages/${id}`;
400
+ await uploadImage({ blob: buffer, key: fileName + ".png", type });
401
+ await uploadImage({
402
+ blob: bufferOriginal,
403
+ key: fileName + "original.png",
404
+ type,
405
+ });
406
+ if (bufferEditable) {
407
+ await uploadImage({
408
+ blob: bufferEditable,
409
+ key: fileName + "editable.json",
410
+ type,
411
+ });
412
+ }
413
+ return buildUploadResponse(response.data, similarityPercentage, false);
414
+ }
415
+ catch (error) {
416
+ console.error(error);
417
+ return null;
418
+ }
419
+ };
420
+ /**
421
+ * Get device by ID
422
+ * @param {string} id
423
+ * @returns {Promise<Device | null>}
424
+ */
425
+ export const getById = async (id) => {
426
+ return IotDevice.findById(id);
427
+ };
428
+ /**
429
+ * Update device by ID
430
+ * @param {string} userId
431
+ * @param {Partial<Device>} updateBody
432
+ * @returns {Promise<Device>}
433
+ */
434
+ export const updateById = async (userId, updateBody) => {
435
+ const iotDevice = await getById(userId);
436
+ if (!iotDevice) {
437
+ throw new ApiError(httpStatus.NOT_FOUND, "IoT Device not found");
438
+ }
439
+ Object.assign(iotDevice, updateBody);
440
+ await iotDevice.save();
441
+ return iotDevice;
442
+ };
443
+ /**
444
+ * Delete device by ID
445
+ * @param {string} userId
446
+ * @returns {Promise<Device>}
447
+ */
448
+ export const deleteById = async (userId) => {
449
+ const iotDevice = await getById(userId);
450
+ if (!iotDevice) {
451
+ throw new ApiError(httpStatus.NOT_FOUND, "IoT Device not found");
452
+ }
453
+ await iotDevice.deleteOne();
454
+ return iotDevice;
455
+ };
456
+ export const getApiStatus = async (kind) => {
457
+ const accessToken = await getAuth0Token();
458
+ try {
459
+ const response = await axios.get(`${process.env.IOT_API_URL}status/${kind}`, {
460
+ headers: { Authorization: `Bearer ${accessToken}` },
461
+ });
462
+ return response.data;
463
+ }
464
+ catch (error) {
465
+ return null;
466
+ }
467
+ };
468
+ export const resetDevice = async (deviceId) => {
469
+ const accessToken = await getAuth0Token();
470
+ try {
471
+ const response = await axios.get(`${process.env.IOT_API_URL}resetdevice`, {
472
+ params: {
473
+ DeviceId: deviceId,
474
+ },
475
+ headers: { Authorization: `Bearer ${accessToken}` },
476
+ });
477
+ return response.data;
478
+ }
479
+ catch (error) {
480
+ //console.error(error);
481
+ throw new ApiError(httpStatus.NOT_FOUND, "Device couldn`t get reset");
482
+ }
483
+ };
484
+ export async function subscribeToLiveEvents(deviceName) {
485
+ const clientId = `web-client-${deviceName}-${Date.now()}`;
486
+ const client = new mqtt.MqttClient();
487
+ const config = iot.AwsIotMqttConnectionConfigBuilder.new_builder_for_websocket()
488
+ .with_clean_session(true)
489
+ .with_client_id(clientId)
490
+ .with_endpoint("a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com")
491
+ .with_credentials(process.env.AWS_REGION, process.env.AWS_ACCESS_KEY_ID, process.env.AWS_SECRET_ACCESS_KEY)
492
+ .with_keep_alive_seconds(60)
493
+ .build();
494
+ const mqttClient = client.new_connection(config);
495
+ console.log(`Subscribed to live events for device: ${deviceName}`);
496
+ try {
497
+ // 1) actually start the connection
498
+ await mqttClient.connect();
499
+ console.log(`✓ MQTT connected for device: ${deviceName}`);
500
+ // 2) subscribe to the correct topic
501
+ const topic = `${deviceName}/send`;
502
+ // subscribe to topic; rely on global 'message' handler to forward payloads
503
+ await mqttClient.subscribe(topic, mqtt.QoS.AtLeastOnce);
504
+ console.log(`✓ Subscribed to live events for device: ${deviceName} on topic: ${topic}`);
505
+ }
506
+ catch (err) {
507
+ console.error("⚠️ MQTT Unexpected error:", err.message || err);
508
+ throw err;
509
+ }
510
+ mqttClient.on("connect", () => {
511
+ mqttClient.subscribe(`nrf-351358815278525/send`);
512
+ });
513
+ mqttClient.on("message", (topic, payload) => {
514
+ const msg = payload.toString();
515
+ console.log(`🛰 [${topic}]`, msg);
516
+ // emit to your socket server, save to db, whatever…
517
+ });
518
+ mqttClient.on("error", (err) => {
519
+ console.error("MQTT Error", err);
520
+ });
521
+ return mqttClient;
522
+ }
523
+ // NEW:
524
+ export const liveEventsWs = async (ws, req) => {
525
+ console.log("WebSocket connection established for live events");
526
+ // lookup the real Thing name
527
+ /* const device = await devicesService.getById(req.params.deviceId);
528
+ if (!device) {
529
+ ws.send(JSON.stringify({ error: 'device not found' }));
530
+ return ws.close();
531
+ }*/
532
+ // subscribe via MQTT
533
+ const client = await subscribeToLiveEvents(req.params.deviceId);
534
+ client.on("message", (_topic, payload) => {
535
+ // normalize various binary payload types to string
536
+ let msg;
537
+ if (payload instanceof ArrayBuffer) {
538
+ msg = Buffer.from(payload).toString("utf8");
539
+ }
540
+ else if (ArrayBuffer.isView(payload)) {
541
+ // Uint8Array, DataView, etc.
542
+ msg = Buffer.from(payload).toString("utf8");
543
+ }
544
+ else {
545
+ msg = payload.toString();
546
+ }
547
+ console.log("🛰 Forwarding MQTT → WS:", msg);
548
+ ws.send(msg);
549
+ });
550
+ // wait 4 1 second before sending a message to the client
551
+ console.log("Waiting 1 second before sending initial message to client...");
552
+ await new Promise((resolve) => setTimeout(resolve, 3000));
553
+ // send initial connection established message and then every 10 seconds
554
+ ws.send(JSON.stringify({ message: "WebSocket connection established" }));
555
+ const intervalId = setInterval(() => {
556
+ ws.send(JSON.stringify({ message: "send message" }));
557
+ }, 10000);
558
+ ws.on("close", () => {
559
+ clearInterval(intervalId);
560
+ client.disconnect();
561
+ });
562
+ };
563
+ export default {
564
+ activateDevice,
565
+ getEvents,
566
+ getById,
567
+ updateById,
568
+ deleteById,
569
+ getApiStatus,
570
+ getDevice,
571
+ uploadSingleImage,
572
+ liveEventsWs,
573
+ shadowAlarmGet,
574
+ shadowAlarmUpdate,
575
+ ledLightHint,
576
+ getDeviceStatus,
577
+ getDeviceStatusList,
578
+ subscribeToLiveEvents,
579
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
2
+ import { z } from 'zod';
3
+ import { zObjectId, zPagination } from '../utils/zValidations';
4
+ extendZodWithOpenApi(z);
5
+ export const getDevice = {
6
+ params: z.object({
7
+ deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
8
+ }),
9
+ body: z.object({
10
+ deviceId: z.array(zObjectId).openapi({ description: 'Array of device IDs' }),
11
+ }),
12
+ };
13
+ export const iotDevicesSchema = {
14
+ ...zPagination,
15
+ query: zPagination.query.extend({
16
+ patient: zObjectId.optional(),
17
+ }),
18
+ };
19
+ export const getDeviceSchema = {};
20
+ export const getEntrySchema = {
21
+ params: z.object({
22
+ deviceId: z.string(),
23
+ }),
24
+ };
25
+ export const getEventsSchema = {
26
+ params: z.object({
27
+ deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
28
+ }),
29
+ query: z.object({
30
+ DateStart: z.string().openapi({ description: 'Start date (ISO‐string)', example: '2025-05-01T00:00:00Z' }),
31
+ DateEnd: z.string().openapi({ description: 'End date (ISO‐string)', example: '2025-05-31T23:59:59Z' }),
32
+ TypeFilter: z.string().openapi({ description: 'Optional type filter', example: '' }).optional().default(''),
33
+ }),
34
+ };
35
+ export const updateEntrySchema = {};
36
+ export const pingDeviceSchema = {
37
+ params: z.object({
38
+ deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
39
+ }),
40
+ query: z.object({
41
+ dataResponse: z.string().openapi({ description: 'Data response', example: 'false' }),
42
+ }),
43
+ };
44
+ export const shadowAlarmValidationSchema = {
45
+ params: z.object({
46
+ nrfId: z.string().openapi({ description: 'Device ID', example: 'nrf-22343' }),
47
+ shadowName: z.string().openapi({ description: 'Shadow name', example: 'alarm' }),
48
+ }),
49
+ };
50
+ export const apiStatusRequestSchema = {
51
+ params: z.object({
52
+ kind: z.string().openapi({ description: 'Kind of API status', example: 'iot' }),
53
+ }),
54
+ };
@@ -0,0 +1,75 @@
1
+ import httpStatus from "http-status";
2
+ import { expressjwt as jwt } from "express-jwt";
3
+ import crypto from "crypto";
4
+ import jwksRsa from "jwks-rsa";
5
+ import ApiError from "../utils/ApiError";
6
+ import Token from "../tokens/tokens.model";
7
+ import { roleRights } from "../config/roles";
8
+ const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => {
9
+ if (err || info || !user) {
10
+ return reject(new ApiError(httpStatus.UNAUTHORIZED, "Please authenticate"));
11
+ }
12
+ req.user = user;
13
+ if (requiredRights.length) {
14
+ const userRights = roleRights.get(user.role) || [];
15
+ const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
16
+ if (!hasRequiredRights && req.params.userId !== user.id) {
17
+ return reject(new ApiError(httpStatus.FORBIDDEN, "Forbidden"));
18
+ }
19
+ }
20
+ resolve();
21
+ };
22
+ const auth = function authFactory(...requiredRights) {
23
+ return async function authMiddleware(req, res, next) {
24
+ try {
25
+ // Check for custom token in X-API-Key header
26
+ const apiKey = req.headers["x-api-key"];
27
+ if (apiKey && apiKey.length === 64) {
28
+ // 32 bytes hex encoded
29
+ const hashedToken = crypto
30
+ .createHash("sha256")
31
+ .update(apiKey)
32
+ .digest("hex");
33
+ const tokenDoc = await Token.findOne({
34
+ value: hashedToken,
35
+ }).select("+owner");
36
+ if (tokenDoc) {
37
+ const ownerId = tokenDoc.owner;
38
+ const roles = ["api"];
39
+ req.auth = {
40
+ id: ownerId,
41
+ tokenId: tokenDoc._id,
42
+ type: "api",
43
+ // For API-key auth, we can treat the token owner as the subject.
44
+ // Avoid fetching user profile from Auth0 Management API on every request.
45
+ sub: ownerId,
46
+ "https://memo.wirewire.de/roles": roles,
47
+ };
48
+ return next();
49
+ }
50
+ }
51
+ // Fallback to Auth0 JWT validation
52
+ jwt({
53
+ secret: jwksRsa.expressJwtSecret({
54
+ cache: true,
55
+ rateLimit: true,
56
+ jwksRequestsPerMinute: 5,
57
+ jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
58
+ }),
59
+ issuer: `https://${process.env.AUTH0_DOMAIN}/`,
60
+ algorithms: ["RS256"],
61
+ })(req, res, (err) => {
62
+ if (err) {
63
+ const status = err.status || 500;
64
+ const message = err.message || "Sorry we were unable to process your request.";
65
+ return res.status(status).send({ message });
66
+ }
67
+ next();
68
+ });
69
+ }
70
+ catch (error) {
71
+ next(error);
72
+ }
73
+ };
74
+ };
75
+ export default auth;