@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
|
@@ -5,6 +5,52 @@ import jwksRsa from "jwks-rsa";
|
|
|
5
5
|
import ApiError from "../utils/ApiError";
|
|
6
6
|
import Token from "../tokens/tokens.model";
|
|
7
7
|
import { roleRights } from "../config/roles";
|
|
8
|
+
import auth0Service from "../accounts/auth0.service";
|
|
9
|
+
const ROLES_CLAIM = "https://memo.wirewire.de/roles";
|
|
10
|
+
const AUTH0_ROLE_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
11
|
+
const auth0RolesCache = new Map();
|
|
12
|
+
const dedupeRoles = (roles) => Array.from(new Set(roles.map((role) => role.trim()).filter(Boolean)));
|
|
13
|
+
const extractRoleNamesFromManagementResponse = (payload) => {
|
|
14
|
+
const iterablePayload = payload && typeof payload[Symbol.iterator] === "function"
|
|
15
|
+
? Array.from(payload)
|
|
16
|
+
: [];
|
|
17
|
+
const list = Array.isArray(payload)
|
|
18
|
+
? payload
|
|
19
|
+
: Array.isArray(payload?.data)
|
|
20
|
+
? payload.data
|
|
21
|
+
: Array.isArray(payload?.items)
|
|
22
|
+
? payload.items
|
|
23
|
+
: iterablePayload;
|
|
24
|
+
const roleNames = list
|
|
25
|
+
.map((entry) => typeof entry?.name === "string" ? entry.name : "")
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
return dedupeRoles(roleNames);
|
|
28
|
+
};
|
|
29
|
+
const getAuth0RolesByOwner = async (ownerId) => {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const cached = auth0RolesCache.get(ownerId);
|
|
32
|
+
if (cached && cached.expiresAt > now) {
|
|
33
|
+
// return cached.roles;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
console.log(`Fetching Auth0 roles for owner ${ownerId} from Management API...`);
|
|
37
|
+
const rolesPayload = await auth0Service.auth0.users.roles.list(ownerId);
|
|
38
|
+
console.log(`Fetched Auth0 roles for owner ${ownerId}:`, rolesPayload);
|
|
39
|
+
const roles = extractRoleNamesFromManagementResponse(rolesPayload);
|
|
40
|
+
auth0RolesCache.set(ownerId, {
|
|
41
|
+
roles,
|
|
42
|
+
expiresAt: now + AUTH0_ROLE_CACHE_TTL_MS,
|
|
43
|
+
});
|
|
44
|
+
return roles;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.warn("auth middleware: could not fetch Auth0 roles for owner", {
|
|
48
|
+
ownerId,
|
|
49
|
+
error,
|
|
50
|
+
});
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
8
54
|
const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => {
|
|
9
55
|
if (err || info || !user) {
|
|
10
56
|
return reject(new ApiError(httpStatus.UNAUTHORIZED, "Please authenticate"));
|
|
@@ -35,7 +81,8 @@ const auth = function authFactory(...requiredRights) {
|
|
|
35
81
|
}).select("+owner");
|
|
36
82
|
if (tokenDoc) {
|
|
37
83
|
const ownerId = tokenDoc.owner;
|
|
38
|
-
const
|
|
84
|
+
const auth0Roles = await getAuth0RolesByOwner(ownerId);
|
|
85
|
+
console.log(`Authenticated API token request for owner ${ownerId}`, auth0Roles);
|
|
39
86
|
req.auth = {
|
|
40
87
|
id: ownerId,
|
|
41
88
|
tokenId: tokenDoc._id,
|
|
@@ -43,7 +90,7 @@ const auth = function authFactory(...requiredRights) {
|
|
|
43
90
|
// For API-key auth, we can treat the token owner as the subject.
|
|
44
91
|
// Avoid fetching user profile from Auth0 Management API on every request.
|
|
45
92
|
sub: ownerId,
|
|
46
|
-
|
|
93
|
+
[ROLES_CLAIM]: auth0Roles,
|
|
47
94
|
};
|
|
48
95
|
return next();
|
|
49
96
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import httpStatus from "http-status";
|
|
2
2
|
import ApiError from "../utils/ApiError";
|
|
3
3
|
import userService from "../users/users.service";
|
|
4
|
-
const
|
|
4
|
+
export const validateCurrentUser = async (req, res, next) => {
|
|
5
5
|
try {
|
|
6
6
|
// TODO: Check if the user is logged in
|
|
7
7
|
const currentUser = await userService.getUserByOwner(res.req.auth.sub, req.body.organization);
|
|
@@ -17,4 +17,4 @@ const getCurrentUser = async (req, res, next) => {
|
|
|
17
17
|
next(new ApiError(httpStatus.INTERNAL_SERVER_ERROR, "Failed to validate user service"));
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
export default
|
|
20
|
+
export default validateCurrentUser;
|
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
import puppeteer from "puppeteer";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import
|
|
3
|
+
import { GetObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
4
|
+
import { getSignedUrl as getS3SignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4
5
|
import path from "path";
|
|
5
|
-
|
|
6
|
-
const s3 = new AWS.S3({
|
|
7
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
8
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
6
|
+
const s3 = new S3Client({
|
|
9
7
|
region: process.env.AWS_REGION,
|
|
8
|
+
credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
|
|
9
|
+
? {
|
|
10
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
11
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
12
|
+
}
|
|
13
|
+
: undefined,
|
|
10
14
|
});
|
|
11
15
|
// Function to upload a file to S3
|
|
12
16
|
const uploadBuffer = async (buffer, fileName) => {
|
|
13
|
-
|
|
17
|
+
await s3.send(new PutObjectCommand({
|
|
14
18
|
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
15
19
|
Key: fileName,
|
|
16
20
|
Body: buffer,
|
|
17
21
|
ContentType: "application/pdf",
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
const data = await s3.upload(params).promise();
|
|
21
|
-
return data.Location;
|
|
22
|
+
}));
|
|
22
23
|
};
|
|
23
24
|
// Generate a signed URL
|
|
24
|
-
const generateSignedUrl = (fileName) => {
|
|
25
|
-
|
|
25
|
+
const generateSignedUrl = async (fileName) => {
|
|
26
|
+
return getS3SignedUrl(s3, new GetObjectCommand({
|
|
26
27
|
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
27
28
|
Key: fileName,
|
|
28
|
-
|
|
29
|
-
};
|
|
30
|
-
return s3.getSignedUrl("getObject", params);
|
|
29
|
+
}), { expiresIn: 60 * 60 });
|
|
31
30
|
};
|
|
32
31
|
const generatePdfFromUrl = async ({ urlPath, token, }) => {
|
|
33
32
|
const domain = process.env.FRONTEND_URL;
|
|
@@ -53,10 +52,10 @@ const generatePdfFromUrl = async ({ urlPath, token, }) => {
|
|
|
53
52
|
localStorage.setItem("print-token", "");
|
|
54
53
|
});
|
|
55
54
|
await browser.close();
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const signedUrl = generateSignedUrl(fileName);
|
|
55
|
+
const fileName = `download-${uuidv4()}.pdf`;
|
|
56
|
+
await uploadBuffer(Buffer.from(pdf), fileName);
|
|
57
|
+
console.log(`File uploaded successfully. File Name: ${fileName}`);
|
|
58
|
+
const signedUrl = await generateSignedUrl(path.basename(fileName));
|
|
60
59
|
console.log(`Signed URL: ${signedUrl}`);
|
|
61
60
|
return signedUrl;
|
|
62
61
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { extendZodWithOpenApi } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
3
3
|
extendZodWithOpenApi(z);
|
|
4
4
|
export const createUserResponseSchema = z.object({
|
|
5
5
|
id: z.string(),
|
|
@@ -23,47 +23,3 @@ export const updateUserResponseSchema = z.object({
|
|
|
23
23
|
export const deleteUserResponseSchema = z.object({
|
|
24
24
|
success: z.boolean(),
|
|
25
25
|
});
|
|
26
|
-
export const updateTimesByIdResponseSchema = z
|
|
27
|
-
.array(z.object({
|
|
28
|
-
rrule: z
|
|
29
|
-
.object({
|
|
30
|
-
freq: z.string().optional().openapi({ example: 'DAILY', description: 'Recurrence frequency' }),
|
|
31
|
-
byweekday: z
|
|
32
|
-
.array(z.number())
|
|
33
|
-
.openapi({ example: [0, 1, 2, 6, 3], description: 'Days of week to repeat (0=Sunday)' }),
|
|
34
|
-
exclude: z.array(z.string()).openapi({ example: ['2024-03-28T10:45:00.000Z'], description: 'Dates to skip' }),
|
|
35
|
-
})
|
|
36
|
-
.openapi({ description: 'Recurrence rule object' }),
|
|
37
|
-
medication: z.string().optional().openapi({ example: '6152c5f3902e7f91374d9f75', description: 'Medication ObjectId' }),
|
|
38
|
-
patient: z.string().openapi({ example: '614fb1d709dd9f6de85d6374', description: 'Patient ObjectId' }),
|
|
39
|
-
date: z.string().openapi({ example: '2024-03-25T00:30:00.000Z', description: 'Scheduled date/time (ISO)' }),
|
|
40
|
-
timeCategory: z.string().openapi({ example: 'noon', description: 'Time category (e.g. morning, noon)' }),
|
|
41
|
-
amount: z.number().openapi({ example: 1, description: 'Dosage amount' }),
|
|
42
|
-
emptyStomach: z.boolean().openapi({ example: false, description: 'Whether to take on empty stomach' }),
|
|
43
|
-
instruction: z.string().optional().openapi({ example: '', description: 'Additional instructions' }),
|
|
44
|
-
unit: z.string().optional().openapi({ example: 'St', description: 'Dosage unit' }),
|
|
45
|
-
bake: z.boolean().openapi({ example: false, description: 'Baking flag (if applicable)' }),
|
|
46
|
-
id: z.string().openapi({ example: '660079fd11fdc2dd935e43af', description: 'Entry identifier' }),
|
|
47
|
-
}))
|
|
48
|
-
.openapi({
|
|
49
|
-
example: [
|
|
50
|
-
{
|
|
51
|
-
rrule: {
|
|
52
|
-
freq: 'DAILY',
|
|
53
|
-
byweekday: [0, 1, 2, 6, 3],
|
|
54
|
-
exclude: ['2024-03-28T10:45:00.000Z'],
|
|
55
|
-
},
|
|
56
|
-
patient: '614fb1d709dd9f6de85d6374',
|
|
57
|
-
date: '2024-03-25T00:30:00.000Z',
|
|
58
|
-
timeCategory: 'noon',
|
|
59
|
-
amount: 1,
|
|
60
|
-
emptyStomach: false,
|
|
61
|
-
instruction: '',
|
|
62
|
-
unit: 'St',
|
|
63
|
-
bake: false,
|
|
64
|
-
id: '660079fd11fdc2dd935e43af',
|
|
65
|
-
},
|
|
66
|
-
// ...other items
|
|
67
|
-
],
|
|
68
|
-
description: 'Array of updated time entries by ID',
|
|
69
|
-
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@internetderdinge/api",
|
|
3
|
-
"version": "1.229.
|
|
3
|
+
"version": "1.229.17",
|
|
4
4
|
"description": "Shared OpenIoT API modules",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -15,11 +15,13 @@
|
|
|
15
15
|
"deps:versions": "node --input-type=module -e \"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const out = (name) => { const pkgPath = require.resolve(name + '/package.json'); const version = require(pkgPath).version; console.log(name + '@' + version + ' -> ' + pkgPath); }; out('zod'); out('@asteasolutions/zod-to-openapi');\"",
|
|
16
16
|
"deploy:version": "node ./scripts/release-version.mjs",
|
|
17
17
|
"release:paperless": "node ./scripts/release-and-sync-paperless.mjs",
|
|
18
|
+
"release:paperless:publish": "node ./scripts/release-and-sync-paperless.mjs --publish",
|
|
18
19
|
"lint": "eslint .",
|
|
19
20
|
"lint:fix": "eslint . --fix"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@aws-sdk/client-cloudwatch-events": "^3.990.0",
|
|
24
|
+
"@aws-sdk/client-iot-data-plane": "^3.990.0",
|
|
23
25
|
"@aws-sdk/client-s3": "^3.990.0",
|
|
24
26
|
"@aws-sdk/client-sesv2": "^3.990.0",
|
|
25
27
|
"@aws-sdk/credential-provider-node": "^3.972.9",
|
|
@@ -33,7 +35,6 @@
|
|
|
33
35
|
"auth0": "^5.3.1",
|
|
34
36
|
"aws-crt": "^1.29.0",
|
|
35
37
|
"aws-iot-device-sdk-v2": "^1.25.0",
|
|
36
|
-
"aws-sdk": "^2.1693.0",
|
|
37
38
|
"aws4": "^1.13.2",
|
|
38
39
|
"axios": "^1.13.5",
|
|
39
40
|
"body-parser": "~2.2.2",
|
|
@@ -138,7 +138,6 @@ const deleteCurrent = catchAsync(
|
|
|
138
138
|
const current = catchAsync(
|
|
139
139
|
async (req: AuthenticatedRequest, res: Response): Promise<void> => {
|
|
140
140
|
const user = await accountsService.getAccountById(req.auth.sub);
|
|
141
|
-
console.log("user", user);
|
|
142
141
|
res.send(user);
|
|
143
142
|
},
|
|
144
143
|
);
|
package/src/config/config.ts
CHANGED
|
@@ -3,12 +3,6 @@ import dotenv from "dotenv";
|
|
|
3
3
|
// Load env from the current working directory
|
|
4
4
|
dotenv.config();
|
|
5
5
|
|
|
6
|
-
console.log("Current working directory:", process.cwd());
|
|
7
|
-
console.log("Loaded environment variables:", {
|
|
8
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
9
|
-
PORT: process.env.PORT,
|
|
10
|
-
MONGODB_URL: process.env.MONGODB_URL,
|
|
11
|
-
});
|
|
12
6
|
import Joi from "joi";
|
|
13
7
|
|
|
14
8
|
const envVarsSchema = Joi.object()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import httpStatus from "http-status";
|
|
3
3
|
import mongoose from "mongoose";
|
|
4
|
-
import multer from "multer";
|
|
5
4
|
import type { Request, Response } from "express";
|
|
6
5
|
import pick from "../utils/pick.js";
|
|
7
6
|
import ApiError from "../utils/ApiError.js";
|
|
@@ -129,19 +128,6 @@ const getEntry = catchAsync(
|
|
|
129
128
|
},
|
|
130
129
|
);
|
|
131
130
|
|
|
132
|
-
const getImageById = catchAsync(
|
|
133
|
-
async (req: Request, res: Response): Promise<void> => {
|
|
134
|
-
const device = await devicesService.getImageById(
|
|
135
|
-
req.params.deviceId,
|
|
136
|
-
req.params.uuid,
|
|
137
|
-
);
|
|
138
|
-
if (!device) {
|
|
139
|
-
throw new ApiError(httpStatus.NOT_FOUND, "Device not found");
|
|
140
|
-
}
|
|
141
|
-
res.send(device);
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
|
|
145
131
|
const updateEntry = catchAsync(
|
|
146
132
|
async (req: Request, res: Response): Promise<void> => {
|
|
147
133
|
// TODO: Remove in newer versions
|
|
@@ -228,54 +214,15 @@ const rebootDevice = catchAsync(
|
|
|
228
214
|
},
|
|
229
215
|
);
|
|
230
216
|
|
|
231
|
-
const updateSingleImageMeta = catchAsync(
|
|
232
|
-
async (req: Request, res: Response): Promise<void> => {
|
|
233
|
-
const device = await devicesService.getById(req.params.deviceId);
|
|
234
|
-
|
|
235
|
-
const { uuid, ...body } = req.body;
|
|
236
|
-
const deviceUpdate = await devicesService.updateById(req.params.deviceId, {
|
|
237
|
-
meta: { ...device.meta, file: uuid },
|
|
238
|
-
});
|
|
239
|
-
const deviceMeta = await devicesService.updateSingleImageMeta(
|
|
240
|
-
device.deviceId,
|
|
241
|
-
body,
|
|
242
|
-
);
|
|
243
|
-
res.send({ deviceMeta, deviceUpdate });
|
|
244
|
-
},
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
const uploadSingleImage = catchAsync(
|
|
248
|
-
async (req: Request, res: Response): Promise<void> => {
|
|
249
|
-
const device = await devicesService.getById(req.params.deviceId);
|
|
250
|
-
|
|
251
|
-
const files = req.files as Array<{ buffer: Buffer }> | undefined;
|
|
252
|
-
if (!files || files.length === 0) {
|
|
253
|
-
throw new ApiError(httpStatus.BAD_REQUEST, "No image file uploaded");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const iotUpload = await iotDevicesService.uploadSingleImage({
|
|
257
|
-
deviceName: device.deviceId,
|
|
258
|
-
buffer: files[0].buffer,
|
|
259
|
-
deviceId: req.params.deviceId,
|
|
260
|
-
uuid: req.body.uuid,
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
res.send(iotUpload);
|
|
264
|
-
},
|
|
265
|
-
);
|
|
266
|
-
|
|
267
217
|
export {
|
|
268
218
|
createEntry,
|
|
269
219
|
getEntries,
|
|
270
220
|
queryDevicesByUser,
|
|
271
221
|
getEvents,
|
|
272
|
-
getImageById,
|
|
273
222
|
registerDevice,
|
|
274
223
|
pingDevice,
|
|
275
224
|
ledLight,
|
|
276
225
|
rebootDevice,
|
|
277
|
-
uploadSingleImage,
|
|
278
|
-
updateSingleImageMeta,
|
|
279
226
|
getEntry,
|
|
280
227
|
updateEntry,
|
|
281
228
|
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 type { RouteSpec } from "../types/routeSpec";
|
|
5
4
|
import auth from "../middlewares/auth.js";
|
|
@@ -23,9 +22,6 @@ import {
|
|
|
23
22
|
registerDeviceSchema,
|
|
24
23
|
ledLightSchema,
|
|
25
24
|
rebootDeviceSchema,
|
|
26
|
-
getImageSchema,
|
|
27
|
-
updateSingleImageMetaSchema,
|
|
28
|
-
uploadSingleImageSchema,
|
|
29
25
|
resetDeviceSchema,
|
|
30
26
|
} from "./devices.validation.js";
|
|
31
27
|
import {
|
|
@@ -33,8 +29,6 @@ import {
|
|
|
33
29
|
devicesResponseSchema,
|
|
34
30
|
eventResponseSchema,
|
|
35
31
|
genericResponseSchema,
|
|
36
|
-
imageResponseSchema,
|
|
37
|
-
uploadResponseSchema,
|
|
38
32
|
resetResponseSchema,
|
|
39
33
|
} from "./devices.schemas.js";
|
|
40
34
|
import {
|
|
@@ -105,6 +99,9 @@ export const devicesRouteSpecs: RouteSpec[] = [
|
|
|
105
99
|
{
|
|
106
100
|
method: "delete",
|
|
107
101
|
path: "/:deviceId",
|
|
102
|
+
validate: [auth("manageUsers"), validateDevice, validateOrganizationDelete],
|
|
103
|
+
requestSchema: deleteDeviceSchema,
|
|
104
|
+
responseSchema: genericResponseSchema,
|
|
108
105
|
handler: devicesController.deleteEntry,
|
|
109
106
|
summary: "Delete a device",
|
|
110
107
|
description: "Remove the specified device from the system.",
|
|
@@ -168,39 +165,6 @@ export const devicesRouteSpecs: RouteSpec[] = [
|
|
|
168
165
|
summary: "Reboot a device",
|
|
169
166
|
description: "Initiate a remote reboot of the specified device.",
|
|
170
167
|
},
|
|
171
|
-
{
|
|
172
|
-
method: "get",
|
|
173
|
-
path: "/image/:deviceId/:uuid",
|
|
174
|
-
validate: [auth("getUsers"), validateDevice],
|
|
175
|
-
requestSchema: getImageSchema,
|
|
176
|
-
responseSchema: imageResponseSchema,
|
|
177
|
-
handler: devicesController.getImageById,
|
|
178
|
-
summary: "Fetch an image by UUID",
|
|
179
|
-
description:
|
|
180
|
-
"Download a previously uploaded image for the device by its UUID.",
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
method: "post",
|
|
184
|
-
path: "/updateSingleImageMeta/:deviceId",
|
|
185
|
-
validate: [auth("getUsers"), validateDevice],
|
|
186
|
-
requestSchema: updateSingleImageMetaSchema,
|
|
187
|
-
responseSchema: uploadResponseSchema,
|
|
188
|
-
handler: devicesController.updateSingleImageMeta,
|
|
189
|
-
summary: "Update image metadata",
|
|
190
|
-
description:
|
|
191
|
-
"Modify metadata (e.g., title, tags) for an existing device image.",
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
method: "post",
|
|
195
|
-
path: "/uploadSingleImage/:deviceId",
|
|
196
|
-
validate: [auth("getUsers"), multer().array("picture", 2), validateDevice],
|
|
197
|
-
requestSchema: uploadSingleImageSchema,
|
|
198
|
-
responseSchema: uploadResponseSchema,
|
|
199
|
-
handler: devicesController.uploadSingleImage,
|
|
200
|
-
summary: "Upload one or two images",
|
|
201
|
-
description:
|
|
202
|
-
"Upload up to two image files to the device for processing or storage.",
|
|
203
|
-
},
|
|
204
168
|
];
|
|
205
169
|
|
|
206
170
|
const router: Router = Router();
|
|
@@ -5,7 +5,6 @@ import ApiError from "../utils/ApiError.js";
|
|
|
5
5
|
import iotDevicesService from "../iotdevice/iotdevice.service.js";
|
|
6
6
|
|
|
7
7
|
import { promisify } from "util";
|
|
8
|
-
import { getSignedFileUrl } from "../files/upload.service";
|
|
9
8
|
import { deviceByDeviceName, deviceKindHasFeature } from "../utils/deviceUtils";
|
|
10
9
|
|
|
11
10
|
import type { DeviceDocument, DeviceInput } from "./devices.model.js";
|
|
@@ -107,15 +106,6 @@ export const populateDeviceStatus = async (e: {
|
|
|
107
106
|
return deviceStatus;
|
|
108
107
|
};
|
|
109
108
|
|
|
110
|
-
export const populateDeviceImage = async (
|
|
111
|
-
deviceId: string,
|
|
112
|
-
uuid: string,
|
|
113
|
-
): Promise<string> => {
|
|
114
|
-
const fileName = `ePaperImages/${deviceId}+${uuid}.png`;
|
|
115
|
-
const url = await getSignedFileUrl({ fileName });
|
|
116
|
-
return url;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
109
|
export const populateIotDevices = async (
|
|
120
110
|
data: DeviceDocument[],
|
|
121
111
|
): Promise<any[]> => {
|
|
@@ -154,14 +144,6 @@ export const getById = async (id: string): Promise<DeviceDocument> => {
|
|
|
154
144
|
return device;
|
|
155
145
|
};
|
|
156
146
|
|
|
157
|
-
export const getImageById = async (
|
|
158
|
-
id: string,
|
|
159
|
-
uuid: string,
|
|
160
|
-
): Promise<{ url: string }> => {
|
|
161
|
-
const url = await populateDeviceImage(id, uuid);
|
|
162
|
-
return { url };
|
|
163
|
-
};
|
|
164
|
-
|
|
165
147
|
export const getByIdWithIoT = async (id: string): Promise<any> => {
|
|
166
148
|
const device = await getById(id);
|
|
167
149
|
if (!device) {
|
|
@@ -238,24 +220,6 @@ export const updateById = async (
|
|
|
238
220
|
return deviceJSON;
|
|
239
221
|
};
|
|
240
222
|
|
|
241
|
-
export const updateSingleImageMeta = async (
|
|
242
|
-
deviceId: string,
|
|
243
|
-
shadowNew: any,
|
|
244
|
-
): Promise<any> => {
|
|
245
|
-
const shadowBody = {
|
|
246
|
-
state: {
|
|
247
|
-
reported: shadowNew,
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const shadowResult = await iotDevicesService.shadowAlarmUpdate(
|
|
252
|
-
deviceId,
|
|
253
|
-
shadowBody,
|
|
254
|
-
"settings",
|
|
255
|
-
);
|
|
256
|
-
return shadowResult;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
223
|
export const deleteById = async (
|
|
260
224
|
userId: string,
|
|
261
225
|
deleteFromIotApi = true,
|
|
@@ -305,12 +269,10 @@ export default {
|
|
|
305
269
|
getByIdWithIoT,
|
|
306
270
|
getDeviceByUserId,
|
|
307
271
|
getDeviceByDeviceId,
|
|
308
|
-
getImageById,
|
|
309
272
|
updateById,
|
|
310
273
|
updatePaymentById,
|
|
311
274
|
deleteById,
|
|
312
275
|
populateIotDevices,
|
|
313
|
-
populateDeviceImage,
|
|
314
276
|
populateDeviceStatus,
|
|
315
277
|
queryDevicesByUser,
|
|
316
278
|
registerDevice,
|
|
@@ -112,14 +112,6 @@ export const createDeviceSchema = {
|
|
|
112
112
|
};
|
|
113
113
|
export const deleteDeviceSchema = zDelete("deviceId");
|
|
114
114
|
export const getDeviceSchema = zGet("deviceId");
|
|
115
|
-
export const getImageSchema = {
|
|
116
|
-
params: z.object({
|
|
117
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
118
|
-
description: "Device ObjectId",
|
|
119
|
-
}),
|
|
120
|
-
uuid: z.string().openapi({ description: "Image UUID" }),
|
|
121
|
-
}),
|
|
122
|
-
};
|
|
123
115
|
export const ledLightSchema = {
|
|
124
116
|
params: z.object({
|
|
125
117
|
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
@@ -202,17 +194,18 @@ export const registerDeviceSchema = {
|
|
|
202
194
|
example: {
|
|
203
195
|
enable: true,
|
|
204
196
|
organization:
|
|
205
|
-
process.env.SCHEMA_EXAMPLE_ORGANIZATION_ID ||
|
|
206
|
-
|
|
197
|
+
process.env.SCHEMA_EXAMPLE_ORGANIZATION_ID ||
|
|
198
|
+
"682fd0d7d4a6325d9d45b86d",
|
|
199
|
+
patient:
|
|
200
|
+
process.env.SCHEMA_EXAMPLE_USER_ID || "682fd0d7d4a6325d9d45b86d",
|
|
207
201
|
paper: process.env.SCHEMA_EXAMPLE_PAPER_ID || null,
|
|
208
202
|
},
|
|
209
203
|
}),
|
|
210
204
|
params: z.object({
|
|
211
|
-
deviceId: (
|
|
212
|
-
|
|
213
|
-
process.env.SCHEMA_EXAMPLE_DEVICE_SERIAL
|
|
214
|
-
|
|
215
|
-
: z.string()
|
|
205
|
+
deviceId: (process.env.SCHEMA_STRICT_EXAMPLES === "true" &&
|
|
206
|
+
process.env.SCHEMA_EXAMPLE_DEVICE_SERIAL
|
|
207
|
+
? z.literal(process.env.SCHEMA_EXAMPLE_DEVICE_SERIAL)
|
|
208
|
+
: z.string()
|
|
216
209
|
).openapi({
|
|
217
210
|
description: "Device serial",
|
|
218
211
|
example: process.env.SCHEMA_EXAMPLE_DEVICE_SERIAL || "DEVICE-EXAMPLE",
|
|
@@ -224,7 +217,7 @@ export const queryDevicesSchema = {
|
|
|
224
217
|
...zPagination,
|
|
225
218
|
query: zPagination.query.extend({
|
|
226
219
|
patient: zObjectIdFor("patient").optional(),
|
|
227
|
-
organization: zObjectIdFor("organization"),
|
|
220
|
+
organization: zObjectIdFor("organization").optional(),
|
|
228
221
|
}),
|
|
229
222
|
};
|
|
230
223
|
export const subscriptionSchema = {
|
|
@@ -249,34 +242,3 @@ export const updateDeviceSchema = {
|
|
|
249
242
|
payment: z.record(z.string(), z.any()).optional(),
|
|
250
243
|
}),
|
|
251
244
|
};
|
|
252
|
-
|
|
253
|
-
export const updateSingleImageMetaSchema = {
|
|
254
|
-
params: z.object({
|
|
255
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
256
|
-
description: "Device ObjectId",
|
|
257
|
-
}),
|
|
258
|
-
}),
|
|
259
|
-
body: z
|
|
260
|
-
.object({
|
|
261
|
-
meta: z.record(z.string(), z.any()).optional(),
|
|
262
|
-
})
|
|
263
|
-
.openapi({ description: "Image metadata updates" }),
|
|
264
|
-
};
|
|
265
|
-
export const uploadSingleImageSchema = {
|
|
266
|
-
params: z.object({
|
|
267
|
-
deviceId: zObjectIdFor("deviceId").openapi({
|
|
268
|
-
description: "Device ObjectId",
|
|
269
|
-
}),
|
|
270
|
-
}),
|
|
271
|
-
body: z
|
|
272
|
-
.object({
|
|
273
|
-
uuid: z
|
|
274
|
-
.string()
|
|
275
|
-
.optional()
|
|
276
|
-
.openapi({ description: "Optional image UUID", example: "mock-uuid" }),
|
|
277
|
-
})
|
|
278
|
-
.openapi({
|
|
279
|
-
description: "Multipart body is mocked during tests.",
|
|
280
|
-
example: { uuid: "mock-uuid" },
|
|
281
|
-
}),
|
|
282
|
-
};
|