@internetderdinge/api 1.229.9 → 1.229.17
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/dist/src/accounts/accounts.controller.js +0 -1
- package/dist/src/config/config.js +0 -6
- package/dist/src/devices/devices.controller.js +1 -31
- package/dist/src/devices/devices.route.js +5 -33
- package/dist/src/devices/devices.service.js +0 -21
- package/dist/src/devices/devices.validation.js +3 -40
- package/dist/src/email/email.service.js +46 -39
- package/dist/src/index.js +19 -1
- package/dist/src/iotdevice/iotdevice.route.js +1 -1
- package/dist/src/iotdevice/iotdevice.service.js +23 -113
- package/dist/src/middlewares/auth.js +49 -2
- package/dist/src/middlewares/validateAdmin.js +0 -1
- package/dist/src/middlewares/validateCurrentUser.js +2 -2
- package/dist/src/models/plugins/simplePopulate.js +1 -1
- package/dist/src/pdf/pdf.service.js +18 -19
- package/dist/src/users/users.schemas.js +2 -46
- package/dist/src/users/users.service.js +1 -0
- package/package.json +3 -2
- package/src/accounts/accounts.controller.ts +0 -1
- package/src/config/config.ts +0 -6
- package/src/devices/devices.controller.ts +0 -53
- package/src/devices/devices.route.ts +3 -39
- package/src/devices/devices.service.ts +0 -38
- package/src/devices/devices.validation.ts +9 -47
- package/src/email/email.service.ts +70 -43
- package/src/index.ts +19 -1
- package/src/iotdevice/iotdevice.route.ts +1 -1
- package/src/iotdevice/iotdevice.service.ts +34 -167
- package/src/middlewares/auth.ts +75 -2
- package/src/middlewares/validateAdmin.ts +0 -1
- package/src/middlewares/validateCurrentUser.ts +2 -2
- package/src/models/plugins/simplePopulate.ts +1 -1
- package/src/pdf/pdf.service.ts +36 -31
- package/src/users/users.schemas.ts +3 -50
- package/src/users/users.service.ts +1 -0
|
@@ -76,7 +76,6 @@ const deleteCurrent = catchAsync(async (req, res) => {
|
|
|
76
76
|
});
|
|
77
77
|
const current = catchAsync(async (req, res) => {
|
|
78
78
|
const user = await accountsService.getAccountById(req.auth.sub);
|
|
79
|
-
console.log("user", user);
|
|
80
79
|
res.send(user);
|
|
81
80
|
});
|
|
82
81
|
const mfaEnroll = catchAsync(async (req, res) => {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
// Load env from the current working directory
|
|
3
3
|
dotenv.config();
|
|
4
|
-
console.log("Current working directory:", process.cwd());
|
|
5
|
-
console.log("Loaded environment variables:", {
|
|
6
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
7
|
-
PORT: process.env.PORT,
|
|
8
|
-
MONGODB_URL: process.env.MONGODB_URL,
|
|
9
|
-
});
|
|
10
4
|
import Joi from "joi";
|
|
11
5
|
const envVarsSchema = Joi.object()
|
|
12
6
|
.keys({
|
|
@@ -89,13 +89,6 @@ const getEntry = catchAsync(async (req, res) => {
|
|
|
89
89
|
}
|
|
90
90
|
res.send(device);
|
|
91
91
|
});
|
|
92
|
-
const getImageById = catchAsync(async (req, res) => {
|
|
93
|
-
const device = await devicesService.getImageById(req.params.deviceId, req.params.uuid);
|
|
94
|
-
if (!device) {
|
|
95
|
-
throw new ApiError(httpStatus.NOT_FOUND, "Device not found");
|
|
96
|
-
}
|
|
97
|
-
res.send(device);
|
|
98
|
-
});
|
|
99
92
|
const updateEntry = catchAsync(async (req, res) => {
|
|
100
93
|
// TODO: Remove in newer versions
|
|
101
94
|
const { updatedAt, createdAt, payment, iotDevice, ...newBody } = req.body;
|
|
@@ -146,27 +139,4 @@ const rebootDevice = catchAsync(async (req, res) => {
|
|
|
146
139
|
const reboot = await iotDevicesService.rebootDevice(device.deviceId);
|
|
147
140
|
res.send({ device, reboot });
|
|
148
141
|
});
|
|
149
|
-
|
|
150
|
-
const device = await devicesService.getById(req.params.deviceId);
|
|
151
|
-
const { uuid, ...body } = req.body;
|
|
152
|
-
const deviceUpdate = await devicesService.updateById(req.params.deviceId, {
|
|
153
|
-
meta: { ...device.meta, file: uuid },
|
|
154
|
-
});
|
|
155
|
-
const deviceMeta = await devicesService.updateSingleImageMeta(device.deviceId, body);
|
|
156
|
-
res.send({ deviceMeta, deviceUpdate });
|
|
157
|
-
});
|
|
158
|
-
const uploadSingleImage = catchAsync(async (req, res) => {
|
|
159
|
-
const device = await devicesService.getById(req.params.deviceId);
|
|
160
|
-
const files = req.files;
|
|
161
|
-
if (!files || files.length === 0) {
|
|
162
|
-
throw new ApiError(httpStatus.BAD_REQUEST, "No image file uploaded");
|
|
163
|
-
}
|
|
164
|
-
const iotUpload = await iotDevicesService.uploadSingleImage({
|
|
165
|
-
deviceName: device.deviceId,
|
|
166
|
-
buffer: files[0].buffer,
|
|
167
|
-
deviceId: req.params.deviceId,
|
|
168
|
-
uuid: req.body.uuid,
|
|
169
|
-
});
|
|
170
|
-
res.send(iotUpload);
|
|
171
|
-
});
|
|
172
|
-
export { createEntry, getEntries, queryDevicesByUser, getEvents, getImageById, registerDevice, pingDevice, ledLight, rebootDevice, uploadSingleImage, updateSingleImageMeta, getEntry, updateEntry, deleteEntry, };
|
|
142
|
+
export { createEntry, getEntries, queryDevicesByUser, getEvents, registerDevice, pingDevice, ledLight, rebootDevice, getEntry, updateEntry, deleteEntry, };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
|
-
import multer from "multer";
|
|
3
2
|
import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
|
|
4
3
|
import auth from "../middlewares/auth.js";
|
|
5
4
|
import { validateBodyOrganization, } from "../middlewares/validateOrganization.js";
|
|
@@ -7,8 +6,8 @@ import { validateQuerySearchUserAndOrganization } from "../middlewares/validateQ
|
|
|
7
6
|
import { validateDevice } from "../middlewares/validateDevice.js";
|
|
8
7
|
import * as devicesController from "./devices.controller.js";
|
|
9
8
|
import { resetDevice } from "./devices.controller.js";
|
|
10
|
-
import { createDeviceSchema, queryDevicesSchema, getDeviceSchema, updateDeviceSchema, getEventsSchema, pingDeviceSchema, registerDeviceSchema, ledLightSchema, rebootDeviceSchema,
|
|
11
|
-
import { deviceResponseSchema, devicesResponseSchema, eventResponseSchema, genericResponseSchema,
|
|
9
|
+
import { createDeviceSchema, queryDevicesSchema, getDeviceSchema, updateDeviceSchema, deleteDeviceSchema, getEventsSchema, pingDeviceSchema, registerDeviceSchema, ledLightSchema, rebootDeviceSchema, resetDeviceSchema, } from "./devices.validation.js";
|
|
10
|
+
import { deviceResponseSchema, devicesResponseSchema, eventResponseSchema, genericResponseSchema, resetResponseSchema, } from "./devices.schemas.js";
|
|
12
11
|
import { validateOrganizationDelete, validateOrganizationUpdate, } from "../middlewares/validateAction.js";
|
|
13
12
|
export const devicesRouteSpecs = [
|
|
14
13
|
{
|
|
@@ -68,6 +67,9 @@ export const devicesRouteSpecs = [
|
|
|
68
67
|
{
|
|
69
68
|
method: "delete",
|
|
70
69
|
path: "/:deviceId",
|
|
70
|
+
validate: [auth("manageUsers"), validateDevice, validateOrganizationDelete],
|
|
71
|
+
requestSchema: deleteDeviceSchema,
|
|
72
|
+
responseSchema: genericResponseSchema,
|
|
71
73
|
handler: devicesController.deleteEntry,
|
|
72
74
|
summary: "Delete a device",
|
|
73
75
|
description: "Remove the specified device from the system.",
|
|
@@ -127,36 +129,6 @@ export const devicesRouteSpecs = [
|
|
|
127
129
|
summary: "Reboot a device",
|
|
128
130
|
description: "Initiate a remote reboot of the specified device.",
|
|
129
131
|
},
|
|
130
|
-
{
|
|
131
|
-
method: "get",
|
|
132
|
-
path: "/image/:deviceId/:uuid",
|
|
133
|
-
validate: [auth("getUsers"), validateDevice],
|
|
134
|
-
requestSchema: getImageSchema,
|
|
135
|
-
responseSchema: imageResponseSchema,
|
|
136
|
-
handler: devicesController.getImageById,
|
|
137
|
-
summary: "Fetch an image by UUID",
|
|
138
|
-
description: "Download a previously uploaded image for the device by its UUID.",
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
method: "post",
|
|
142
|
-
path: "/updateSingleImageMeta/:deviceId",
|
|
143
|
-
validate: [auth("getUsers"), validateDevice],
|
|
144
|
-
requestSchema: updateSingleImageMetaSchema,
|
|
145
|
-
responseSchema: uploadResponseSchema,
|
|
146
|
-
handler: devicesController.updateSingleImageMeta,
|
|
147
|
-
summary: "Update image metadata",
|
|
148
|
-
description: "Modify metadata (e.g., title, tags) for an existing device image.",
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
method: "post",
|
|
152
|
-
path: "/uploadSingleImage/:deviceId",
|
|
153
|
-
validate: [auth("getUsers"), multer().array("picture", 2), validateDevice],
|
|
154
|
-
requestSchema: uploadSingleImageSchema,
|
|
155
|
-
responseSchema: uploadResponseSchema,
|
|
156
|
-
handler: devicesController.uploadSingleImage,
|
|
157
|
-
summary: "Upload one or two images",
|
|
158
|
-
description: "Upload up to two image files to the device for processing or storage.",
|
|
159
|
-
},
|
|
160
132
|
];
|
|
161
133
|
const router = Router();
|
|
162
134
|
buildRouterAndDocs(router, devicesRouteSpecs, "/devices", ["Devices"]);
|
|
@@ -4,7 +4,6 @@ import Device from "./devices.model.js";
|
|
|
4
4
|
import ApiError from "../utils/ApiError.js";
|
|
5
5
|
import iotDevicesService from "../iotdevice/iotdevice.service.js";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
-
import { getSignedFileUrl } from "../files/upload.service";
|
|
8
7
|
import { deviceByDeviceName, deviceKindHasFeature } from "../utils/deviceUtils";
|
|
9
8
|
import * as usersService from "../users/users.service";
|
|
10
9
|
const setTimeoutAsync = promisify(setTimeout);
|
|
@@ -63,11 +62,6 @@ export const populateDeviceStatus = async (e) => {
|
|
|
63
62
|
const deviceStatus = await iotDevicesService.getDeviceStatus(e.deviceId, e.kind);
|
|
64
63
|
return deviceStatus;
|
|
65
64
|
};
|
|
66
|
-
export const populateDeviceImage = async (deviceId, uuid) => {
|
|
67
|
-
const fileName = `ePaperImages/${deviceId}+${uuid}.png`;
|
|
68
|
-
const url = await getSignedFileUrl({ fileName });
|
|
69
|
-
return url;
|
|
70
|
-
};
|
|
71
65
|
export const populateIotDevices = async (data) => {
|
|
72
66
|
const nrfList = data.map((e) => e.deviceId);
|
|
73
67
|
const iotDevices = await iotDevicesService.getDevice(nrfList);
|
|
@@ -93,10 +87,6 @@ export const getById = async (id) => {
|
|
|
93
87
|
//if (!device) throw new ApiError(httpStatus.NOT_FOUND, `Device not found id: ${id}`);
|
|
94
88
|
return device;
|
|
95
89
|
};
|
|
96
|
-
export const getImageById = async (id, uuid) => {
|
|
97
|
-
const url = await populateDeviceImage(id, uuid);
|
|
98
|
-
return { url };
|
|
99
|
-
};
|
|
100
90
|
export const getByIdWithIoT = async (id) => {
|
|
101
91
|
const device = await getById(id);
|
|
102
92
|
if (!device) {
|
|
@@ -148,15 +138,6 @@ export const updateById = async (userId, updateBody) => {
|
|
|
148
138
|
deviceJSON.shadow = shadow;
|
|
149
139
|
return deviceJSON;
|
|
150
140
|
};
|
|
151
|
-
export const updateSingleImageMeta = async (deviceId, shadowNew) => {
|
|
152
|
-
const shadowBody = {
|
|
153
|
-
state: {
|
|
154
|
-
reported: shadowNew,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
const shadowResult = await iotDevicesService.shadowAlarmUpdate(deviceId, shadowBody, "settings");
|
|
158
|
-
return shadowResult;
|
|
159
|
-
};
|
|
160
141
|
export const deleteById = async (userId, deleteFromIotApi = true) => {
|
|
161
142
|
const device = await getById(userId);
|
|
162
143
|
if (!device) {
|
|
@@ -182,12 +163,10 @@ export default {
|
|
|
182
163
|
getByIdWithIoT,
|
|
183
164
|
getDeviceByUserId,
|
|
184
165
|
getDeviceByDeviceId,
|
|
185
|
-
getImageById,
|
|
186
166
|
updateById,
|
|
187
167
|
updatePaymentById,
|
|
188
168
|
deleteById,
|
|
189
169
|
populateIotDevices,
|
|
190
|
-
populateDeviceImage,
|
|
191
170
|
populateDeviceStatus,
|
|
192
171
|
queryDevicesByUser,
|
|
193
172
|
registerDevice,
|
|
@@ -95,14 +95,6 @@ export const createDeviceSchema = {
|
|
|
95
95
|
};
|
|
96
96
|
export const deleteDeviceSchema = zDelete("deviceId");
|
|
97
97
|
export const getDeviceSchema = zGet("deviceId");
|
|
98
|
-
export const getImageSchema = {
|
|
99
|
-
params: z.object({
|
|
100
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
101
|
-
description: "Device ObjectId",
|
|
102
|
-
}),
|
|
103
|
-
uuid: z.string().openapi({ description: "Image UUID" }),
|
|
104
|
-
}),
|
|
105
|
-
};
|
|
106
98
|
export const ledLightSchema = {
|
|
107
99
|
params: z.object({
|
|
108
100
|
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
@@ -181,7 +173,8 @@ export const registerDeviceSchema = {
|
|
|
181
173
|
.openapi({
|
|
182
174
|
example: {
|
|
183
175
|
enable: true,
|
|
184
|
-
organization: process.env.SCHEMA_EXAMPLE_ORGANIZATION_ID ||
|
|
176
|
+
organization: process.env.SCHEMA_EXAMPLE_ORGANIZATION_ID ||
|
|
177
|
+
"682fd0d7d4a6325d9d45b86d",
|
|
185
178
|
patient: process.env.SCHEMA_EXAMPLE_USER_ID || "682fd0d7d4a6325d9d45b86d",
|
|
186
179
|
paper: process.env.SCHEMA_EXAMPLE_PAPER_ID || null,
|
|
187
180
|
},
|
|
@@ -200,7 +193,7 @@ export const queryDevicesSchema = {
|
|
|
200
193
|
...zPagination,
|
|
201
194
|
query: zPagination.query.extend({
|
|
202
195
|
patient: zObjectIdFor("patient").optional(),
|
|
203
|
-
organization: zObjectIdFor("organization"),
|
|
196
|
+
organization: zObjectIdFor("organization").optional(),
|
|
204
197
|
}),
|
|
205
198
|
};
|
|
206
199
|
export const subscriptionSchema = {
|
|
@@ -225,33 +218,3 @@ export const updateDeviceSchema = {
|
|
|
225
218
|
payment: z.record(z.string(), z.any()).optional(),
|
|
226
219
|
}),
|
|
227
220
|
};
|
|
228
|
-
export const updateSingleImageMetaSchema = {
|
|
229
|
-
params: z.object({
|
|
230
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
231
|
-
description: "Device ObjectId",
|
|
232
|
-
}),
|
|
233
|
-
}),
|
|
234
|
-
body: z
|
|
235
|
-
.object({
|
|
236
|
-
meta: z.record(z.string(), z.any()).optional(),
|
|
237
|
-
})
|
|
238
|
-
.openapi({ description: "Image metadata updates" }),
|
|
239
|
-
};
|
|
240
|
-
export const uploadSingleImageSchema = {
|
|
241
|
-
params: z.object({
|
|
242
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
243
|
-
description: "Device ObjectId",
|
|
244
|
-
}),
|
|
245
|
-
}),
|
|
246
|
-
body: z
|
|
247
|
-
.object({
|
|
248
|
-
uuid: z
|
|
249
|
-
.string()
|
|
250
|
-
.optional()
|
|
251
|
-
.openapi({ description: "Optional image UUID", example: "mock-uuid" }),
|
|
252
|
-
})
|
|
253
|
-
.openapi({
|
|
254
|
-
description: "Multipart body is mocked during tests.",
|
|
255
|
-
example: { uuid: "mock-uuid" },
|
|
256
|
-
}),
|
|
257
|
-
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import config from
|
|
3
|
-
import i18n from
|
|
1
|
+
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
|
|
2
|
+
import config from "../../src/config/config";
|
|
3
|
+
import i18n from "../../src/i18n/i18n";
|
|
4
4
|
function urlStartsWithHttp(url) {
|
|
5
|
-
return url.startsWith(
|
|
5
|
+
return url.startsWith("http");
|
|
6
6
|
}
|
|
7
|
-
const button = ({ link, text, color =
|
|
7
|
+
const button = ({ link, text, color = "#0076ff", }) => {
|
|
8
8
|
return `
|
|
9
9
|
<div><!--[if mso]>
|
|
10
10
|
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="${link}" style="height:40px;v-text-anchor:middle;width:200px;" arcsize="15%" stroke="f" fillcolor="${color}">
|
|
@@ -19,7 +19,7 @@ const button = ({ link, text, color = '#0076ff' }) => {
|
|
|
19
19
|
<![endif]--></div>
|
|
20
20
|
`;
|
|
21
21
|
};
|
|
22
|
-
const actionButton = ({ link, text, color =
|
|
22
|
+
const actionButton = ({ link, text, color = "#0076ff", }) => {
|
|
23
23
|
return `<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
24
24
|
<tr>
|
|
25
25
|
<td align="center">
|
|
@@ -34,28 +34,32 @@ const actionButton = ({ link, text, color = '#0076ff' }) => {
|
|
|
34
34
|
</tr>
|
|
35
35
|
</table>`;
|
|
36
36
|
};
|
|
37
|
-
export const sendEmail = async ({ title =
|
|
38
|
-
const interactive =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", url = "", domain = "memo", image, email, actionButtonText, lng, }) => {
|
|
38
|
+
const interactive = "#0076ff";
|
|
39
|
+
const sesClient = new SESv2Client({
|
|
40
|
+
region: "eu-central-1",
|
|
41
|
+
credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
|
|
42
|
+
? {
|
|
43
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
44
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
43
47
|
});
|
|
44
|
-
const actionButtonTextWithLanguage = i18n.t(actionButtonText ||
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const base64ToName = Buffer.from(`Memo ${i18n.t('Notifications', { lng })}`).toString('base64');
|
|
48
|
+
const actionButtonTextWithLanguage = i18n.t(actionButtonText || "Go to Application", { lng });
|
|
49
|
+
const toEmail = "notifications@wirewire.de";
|
|
50
|
+
const base64ToName = Buffer.from(`Memo ${i18n.t("Notifications", { lng })}`).toString("base64");
|
|
48
51
|
const finalToName = `=?UTF-8?B?${base64ToName}?= <${toEmail}>`;
|
|
49
52
|
const params = {
|
|
50
53
|
Destination: {
|
|
51
54
|
ToAddresses: [email],
|
|
52
55
|
},
|
|
53
|
-
ConfigurationSetName:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
ConfigurationSetName: "memo-transactional",
|
|
57
|
+
Content: {
|
|
58
|
+
Simple: {
|
|
59
|
+
Body: {
|
|
60
|
+
Html: {
|
|
61
|
+
Charset: "UTF-8",
|
|
62
|
+
Data: `
|
|
59
63
|
<!DOCTYPE html>
|
|
60
64
|
<html>
|
|
61
65
|
<head>
|
|
@@ -508,7 +512,7 @@ export const sendEmail = async ({ title = 'Kein Titel', body = 'Kein Inhalt', ur
|
|
|
508
512
|
<tr>
|
|
509
513
|
<td class="email-masthead">
|
|
510
514
|
<a href="https://${domain}.wirewire.de" class="f-fallback email-masthead_name">
|
|
511
|
-
${domain ===
|
|
515
|
+
${domain === "memo" ? "ANABOX smart" : "paperlesspaper"}
|
|
512
516
|
</a>
|
|
513
517
|
</td>
|
|
514
518
|
</tr>
|
|
@@ -520,14 +524,16 @@ export const sendEmail = async ({ title = 'Kein Titel', body = 'Kein Inhalt', ur
|
|
|
520
524
|
<tr>
|
|
521
525
|
<td class="content-cell align-center">
|
|
522
526
|
<div class="f-fallback">
|
|
523
|
-
${image ? `<img class="email-image" src="${image}" alt="memo image" />` :
|
|
527
|
+
${image ? `<img class="email-image" src="${image}" alt="memo image" />` : ""}
|
|
524
528
|
<h1>${title}</h1>
|
|
525
529
|
<p>${body}</p>
|
|
526
530
|
<!-- Action -->
|
|
527
531
|
${actionButton({
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
532
|
+
link: urlStartsWithHttp(url)
|
|
533
|
+
? url
|
|
534
|
+
: `http://${domain}.wirewire.de${url}`,
|
|
535
|
+
text: actionButtonTextWithLanguage,
|
|
536
|
+
})}
|
|
531
537
|
</div>
|
|
532
538
|
</td>
|
|
533
539
|
</tr>
|
|
@@ -540,10 +546,10 @@ export const sendEmail = async ({ title = 'Kein Titel', body = 'Kein Inhalt', ur
|
|
|
540
546
|
<tr>
|
|
541
547
|
<td class="content-cell" align="center">
|
|
542
548
|
<p class="f-fallback sub align-center">
|
|
543
|
-
${domain ===
|
|
549
|
+
${domain === "web" ? "The Wire UG" : "wirewire GmbH"}
|
|
544
550
|
<a href="http://${domain}.wirewire.de/account">Account</a>
|
|
545
551
|
|
|
546
|
-
${config.env !==
|
|
552
|
+
${config.env !== "production" ? `<br/><br/>Environment: ${config.env}` : ""}
|
|
547
553
|
</p>
|
|
548
554
|
</td>
|
|
549
555
|
</tr>
|
|
@@ -557,22 +563,23 @@ export const sendEmail = async ({ title = 'Kein Titel', body = 'Kein Inhalt', ur
|
|
|
557
563
|
</body>
|
|
558
564
|
</html>
|
|
559
565
|
`,
|
|
566
|
+
},
|
|
567
|
+
Text: {
|
|
568
|
+
Charset: "UTF-8",
|
|
569
|
+
Data: `${title} ${body}`,
|
|
570
|
+
},
|
|
560
571
|
},
|
|
561
|
-
|
|
562
|
-
Charset:
|
|
563
|
-
Data: `${title}
|
|
572
|
+
Subject: {
|
|
573
|
+
Charset: "UTF-8",
|
|
574
|
+
Data: `${title} - Memo App`,
|
|
564
575
|
},
|
|
565
576
|
},
|
|
566
|
-
Subject: {
|
|
567
|
-
Charset: 'UTF-8',
|
|
568
|
-
Data: `${title} - Memo App`,
|
|
569
|
-
},
|
|
570
577
|
},
|
|
571
|
-
|
|
578
|
+
FromEmailAddress: finalToName,
|
|
572
579
|
};
|
|
573
580
|
try {
|
|
574
|
-
const data = await
|
|
575
|
-
console.log(
|
|
581
|
+
const data = await sesClient.send(new SendEmailCommand(params));
|
|
582
|
+
console.log("Email submitted to SES", data);
|
|
576
583
|
}
|
|
577
584
|
catch (error) {
|
|
578
585
|
console.error(error);
|
package/dist/src/index.js
CHANGED
|
@@ -8,8 +8,10 @@ export { validateDevice, validateDeviceIsInOrganization, validateDeviceOrOrganiz
|
|
|
8
8
|
export { initI18n } from "../src/i18n/i18n";
|
|
9
9
|
export { default as i18n } from "../src/i18n/i18n";
|
|
10
10
|
export { default as usersRoute } from "../src/users/users.route";
|
|
11
|
+
export { default as usersService } from "../src/users/users.service";
|
|
11
12
|
export { default as accountsRoute } from "../src/accounts/accounts.route";
|
|
12
13
|
export { default as accountsService } from "../src/accounts/accounts.service";
|
|
14
|
+
export { auth0 } from "../src/accounts/auth0.service";
|
|
13
15
|
export { default as organizationsRoute } from "../src/organizations/organizations.route";
|
|
14
16
|
export { default as organizationsService } from "../src/organizations/organizations.service";
|
|
15
17
|
export { default as Organization } from "../src/organizations/organizations.model";
|
|
@@ -20,12 +22,12 @@ export * from "../src/devices/devices.validation";
|
|
|
20
22
|
export { default as devicesNotificationsRoute } from "./devicesNotifications/devicesNotifications.route";
|
|
21
23
|
export { default as devicesNotificationsService } from "../src/devicesNotifications/devicesNotifications.service";
|
|
22
24
|
export { default as iotDevicesService } from "../src/iotdevice/iotdevice.service";
|
|
25
|
+
export { default as iotdeviceRoute } from "../src/iotdevice/iotdevice.route";
|
|
23
26
|
export { SIMILARITY_THRESHOLD } from "../src/iotdevice/iotdevice.service";
|
|
24
27
|
export { default as pdfRoute } from "../src/pdf/pdf.route";
|
|
25
28
|
export { default as tokensRoute } from "../src/tokens/tokens.route";
|
|
26
29
|
export * from "../src/tokens/tokens.service";
|
|
27
30
|
export { default as Token } from "../src/tokens/tokens.model";
|
|
28
|
-
export * as usersService from "../src/users/users.service";
|
|
29
31
|
export { User } from "../src/users/users.model";
|
|
30
32
|
export { isAdmin, validateAdmin } from "../src/middlewares/validateAdmin";
|
|
31
33
|
export { sendEmail } from "../src/email/email.service";
|
|
@@ -36,6 +38,7 @@ export { paginate, toJSON } from "../src/models/plugins/index";
|
|
|
36
38
|
export { compareImages } from "../src/utils/comparePapers.service";
|
|
37
39
|
export { resolvePossiblyRelativeUrl } from "../src/utils/urlUtils";
|
|
38
40
|
export { getSignedFileUrl } from "../src/files/upload.service";
|
|
41
|
+
export * from "../src/middlewares/rateLimiter";
|
|
39
42
|
export * from "../src/utils/ApiError";
|
|
40
43
|
export * from "../src/utils/buildRouterAndDocs";
|
|
41
44
|
export * from "../src/utils/comparePapers.service";
|
|
@@ -47,3 +50,18 @@ export * from "../src/utils/registerOpenApi";
|
|
|
47
50
|
export * from "../src/utils/urlUtils";
|
|
48
51
|
export * from "../src/utils/userName";
|
|
49
52
|
export * from "../src/utils/zValidations";
|
|
53
|
+
export * from "../src/validations/custom.validation";
|
|
54
|
+
export * from "../src/models/plugins/paginate.plugin";
|
|
55
|
+
export * from "../src/models/plugins/simplePopulate";
|
|
56
|
+
export * from "../src/middlewares/validateOrganization";
|
|
57
|
+
export * from "../src/middlewares/validateUser";
|
|
58
|
+
export * from "../src/middlewares/validateCurrentUser";
|
|
59
|
+
export * from "../src/middlewares/validateZod";
|
|
60
|
+
export * from "../src/middlewares/validateAdmin";
|
|
61
|
+
export * from "../src/middlewares/validateAi";
|
|
62
|
+
export * from "../src/middlewares/validateDevice";
|
|
63
|
+
export * from "../src/middlewares/auth";
|
|
64
|
+
export * from "../src/middlewares/error";
|
|
65
|
+
export * from "../src/accounts/auth0.service";
|
|
66
|
+
export * from "../src/middlewares/validateAction";
|
|
67
|
+
export { default as generateUserName } from "../src/utils/userName";
|
|
@@ -105,7 +105,7 @@ export const iotdeviceRouteSpecs = [
|
|
|
105
105
|
responseSchema: apiStatusSchema,
|
|
106
106
|
handler: getApiStatus,
|
|
107
107
|
summary: "Get API status by kind",
|
|
108
|
-
description: "Retrieves the API status information for a given status kind.",
|
|
108
|
+
description: "Retrieves the IoT API status information for a given status kind to monitor system health or performance. Can be accessed without authentication for monitoring purposes.",
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
method: "get",
|
|
@@ -1,59 +1,20 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import httpStatus from "http-status";
|
|
3
3
|
import axios from "axios";
|
|
4
|
-
import
|
|
4
|
+
import { GetThingShadowCommand, IoTDataPlaneClient, UpdateThingShadowCommand, } from "@aws-sdk/client-iot-data-plane";
|
|
5
5
|
import { deviceKindHasFeature } from "../utils/deviceUtils";
|
|
6
6
|
import ApiError from "../utils/ApiError";
|
|
7
7
|
import { getAuth0Token } from "../accounts/auth0.service";
|
|
8
|
-
import { uploadImage, getSignedFileUrl } from "../files/upload.service";
|
|
9
|
-
import { compareImages } from "../utils/comparePapers.service";
|
|
10
8
|
import IotDevice from "./iotdevice.model";
|
|
11
|
-
import { fileTypeFromBuffer } from "file-type";
|
|
12
9
|
import iotsdk from "aws-iot-device-sdk-v2";
|
|
13
10
|
const iot = iotsdk.iot;
|
|
14
11
|
const mqtt = iotsdk.mqtt;
|
|
15
12
|
export const SIMILARITY_THRESHOLD = Number(process.env.EPAPER_SIMILARITY_THRESHOLD ?? 99.995);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
};
|
|
13
|
+
const IOT_DATA_ENDPOINT = "https://a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com";
|
|
14
|
+
const iotDataClient = new IoTDataPlaneClient({
|
|
15
|
+
endpoint: IOT_DATA_ENDPOINT,
|
|
16
|
+
region: process.env.AWS_REGION ?? "eu-central-1",
|
|
17
|
+
});
|
|
57
18
|
/**
|
|
58
19
|
* Get events for a device
|
|
59
20
|
* @param {Object} params
|
|
@@ -309,18 +270,17 @@ export const getDeviceStatus = async (deviceName, kind) => {
|
|
|
309
270
|
* @returns {Promise<Device>}
|
|
310
271
|
*/
|
|
311
272
|
export const shadowAlarmGet = async (deviceName, shadowName) => {
|
|
312
|
-
const iotdata = new AWS.IotData({
|
|
313
|
-
endpoint: "a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com",
|
|
314
|
-
});
|
|
315
273
|
if (!deviceName)
|
|
316
274
|
return { error: "deviceName is required" };
|
|
317
|
-
const params = {
|
|
318
|
-
thingName: deviceName,
|
|
319
|
-
shadowName,
|
|
320
|
-
};
|
|
321
275
|
try {
|
|
322
|
-
const response = await
|
|
323
|
-
|
|
276
|
+
const response = await iotDataClient.send(new GetThingShadowCommand({
|
|
277
|
+
thingName: deviceName,
|
|
278
|
+
shadowName,
|
|
279
|
+
}));
|
|
280
|
+
const payload = response.payload
|
|
281
|
+
? Buffer.from(response.payload).toString("utf8")
|
|
282
|
+
: "{}";
|
|
283
|
+
return JSON.parse(payload);
|
|
324
284
|
}
|
|
325
285
|
catch (e) {
|
|
326
286
|
// console.log(deviceName, e);
|
|
@@ -352,73 +312,24 @@ export const ledLightHint = async (deviceName, body) => {
|
|
|
352
312
|
*/
|
|
353
313
|
const shadowAlarmUpdate = async (deviceName, alarms, shadowName) => {
|
|
354
314
|
const data = alarms;
|
|
355
|
-
const iotdata = new AWS.IotData({
|
|
356
|
-
endpoint: "a2vm6rc8xrtk10-ats.iot.eu-central-1.amazonaws.com",
|
|
357
|
-
});
|
|
358
315
|
if (!deviceName) {
|
|
359
316
|
return { error: "no deviceId" };
|
|
360
317
|
}
|
|
361
|
-
const params = {
|
|
362
|
-
payload: JSON.stringify(data),
|
|
363
|
-
thingName: deviceName,
|
|
364
|
-
shadowName,
|
|
365
|
-
};
|
|
366
318
|
try {
|
|
367
|
-
const response = await
|
|
368
|
-
|
|
319
|
+
const response = await iotDataClient.send(new UpdateThingShadowCommand({
|
|
320
|
+
payload: JSON.stringify(data),
|
|
321
|
+
thingName: deviceName,
|
|
322
|
+
shadowName,
|
|
323
|
+
}));
|
|
324
|
+
const payload = response.payload
|
|
325
|
+
? Buffer.from(response.payload).toString("utf8")
|
|
326
|
+
: "{}";
|
|
327
|
+
return JSON.parse(payload);
|
|
369
328
|
}
|
|
370
329
|
catch (e) {
|
|
371
330
|
return "error";
|
|
372
331
|
}
|
|
373
332
|
};
|
|
374
|
-
export const uploadSingleImage = async ({ deviceName, buffer, bufferOriginal, bufferEditable, id, paperId, }) => {
|
|
375
|
-
try {
|
|
376
|
-
const { skipUpload, similarityPercentage } = await evaluateSimilarityBeforeUpload(id, bufferOriginal);
|
|
377
|
-
var response = {};
|
|
378
|
-
if (skipUpload) {
|
|
379
|
-
return buildUploadResponse({ message: "Image skipped due to similarity threshold" }, similarityPercentage, true);
|
|
380
|
-
}
|
|
381
|
-
if (!bufferOriginal) {
|
|
382
|
-
throw new Error("bufferOriginal is required to upload an image");
|
|
383
|
-
}
|
|
384
|
-
// console.log(`Uploading image for ${id} on ${deviceName}; similarity ${similarityPercentage?.toFixed(5)}%`);
|
|
385
|
-
if (deviceName) {
|
|
386
|
-
const accessToken = await getAuth0Token();
|
|
387
|
-
response = await axios.post(`${process.env.IOT_API_URL_EPAPER}uploads`, { deviceName }, {
|
|
388
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
389
|
-
});
|
|
390
|
-
if (!response.data.uploadURL) {
|
|
391
|
-
console.log("No upload URL received", response.data);
|
|
392
|
-
}
|
|
393
|
-
else {
|
|
394
|
-
await axios.put(response.data.uploadURL, buffer, {
|
|
395
|
-
// onUploadProgress: (progressEvent) => console.log('file progress', progressEvent.loaded),
|
|
396
|
-
headers: { "Content-Type": "text/octet-stream" },
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
const type = await fileTypeFromBuffer(buffer);
|
|
401
|
-
const fileName = `ePaperImages/${id}`;
|
|
402
|
-
await uploadImage({ blob: buffer, key: fileName + ".png", type });
|
|
403
|
-
await uploadImage({
|
|
404
|
-
blob: bufferOriginal,
|
|
405
|
-
key: fileName + "original.png",
|
|
406
|
-
type,
|
|
407
|
-
});
|
|
408
|
-
if (bufferEditable) {
|
|
409
|
-
await uploadImage({
|
|
410
|
-
blob: bufferEditable,
|
|
411
|
-
key: fileName + "editable.json",
|
|
412
|
-
type,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
return buildUploadResponse(response.data, similarityPercentage, false);
|
|
416
|
-
}
|
|
417
|
-
catch (error) {
|
|
418
|
-
console.error(error);
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
333
|
/**
|
|
423
334
|
* Get device by ID
|
|
424
335
|
* @param {string} id
|
|
@@ -570,7 +481,6 @@ export default {
|
|
|
570
481
|
deleteById,
|
|
571
482
|
getApiStatus,
|
|
572
483
|
getDevice,
|
|
573
|
-
uploadSingleImage,
|
|
574
484
|
liveEventsWs,
|
|
575
485
|
shadowAlarmGet,
|
|
576
486
|
shadowAlarmUpdate,
|