@internetderdinge/api 1.229.32 → 1.229.39
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.route.js +18 -5
- package/dist/src/admin/adminSearch.controller.js +0 -19
- package/dist/src/admin/adminSearch.route.js +5 -5
- package/dist/src/devices/devices.controller.js +4 -6
- package/dist/src/devices/devices.route.js +14 -14
- package/dist/src/devices/devices.validation.js +15 -54
- package/dist/src/email/email.service.js +6 -6
- package/dist/src/index.js +5 -1
- package/dist/src/iotdevice/iotdevice.route.js +3 -1
- package/dist/src/iotdevice/iotdevice.service.js +5 -5
- package/dist/src/iotdevice/iotdevice.validation.js +12 -4
- package/dist/src/middlewares/validateAdminOrSupport.js +20 -0
- package/dist/src/organizations/organizations.controller.js +17 -6
- package/dist/src/organizations/organizations.route.js +2 -1
- package/dist/src/users/users.model.js +4 -2
- package/dist/src/users/users.route.js +2 -2
- package/dist/src/users/users.service.js +26 -10
- package/dist/src/users/users.validation.js +60 -61
- package/dist/src/utils/buildRouterAndDocs.js +7 -4
- package/dist/src/utils/zValidations.js +7 -0
- package/package.json +5 -2
- package/scripts/release-and-sync-paperless.mjs +21 -2
- package/scripts/release-version.mjs +21 -2
- package/src/accounts/accounts.route.ts +21 -5
- package/src/admin/adminSearch.controller.ts +0 -35
- package/src/admin/adminSearch.route.ts +8 -6
- package/src/admin/adminSearch.service.ts +6 -10
- package/src/devices/devices.controller.ts +4 -12
- package/src/devices/devices.route.ts +14 -15
- package/src/devices/devices.validation.ts +20 -54
- package/src/email/email.service.ts +15 -7
- package/src/index.ts +5 -1
- package/src/iotdevice/iotdevice.route.ts +3 -1
- package/src/iotdevice/iotdevice.service.ts +8 -7
- package/src/iotdevice/iotdevice.validation.ts +12 -4
- package/src/middlewares/validateAdminOrSupport.ts +34 -0
- package/src/organizations/organizations.controller.ts +38 -7
- package/src/organizations/organizations.route.ts +3 -1
- package/src/users/users.model.ts +7 -3
- package/src/users/users.route.ts +3 -2
- package/src/users/users.service.ts +50 -14
- package/src/users/users.validation.ts +62 -60
- package/src/utils/buildRouterAndDocs.ts +14 -5
- package/src/utils/zValidations.ts +8 -0
- package/dist/src/pdf/pdf.controller.js +0 -24
- package/dist/src/pdf/pdf.route.js +0 -22
- package/dist/src/pdf/pdf.schemas.js +0 -6
- package/dist/src/pdf/pdf.service.js +0 -64
- package/dist/src/pdf/pdf.validation.js +0 -27
- package/src/pdf/pdf.controller.ts +0 -35
- package/src/pdf/pdf.route.ts +0 -28
- package/src/pdf/pdf.schemas.ts +0 -7
- package/src/pdf/pdf.service.ts +0 -103
- package/src/pdf/pdf.validation.ts +0 -30
|
@@ -12,9 +12,9 @@ export const accountsRouteSpecs = [
|
|
|
12
12
|
validate: [auth("manageUsers")],
|
|
13
13
|
requestSchema: accountsValidation.currentAccountSchema,
|
|
14
14
|
responseSchema: accountResponseSchema,
|
|
15
|
-
privateDocs: true,
|
|
16
15
|
handler: accountsController.current,
|
|
17
16
|
summary: "Get the current account",
|
|
17
|
+
description: "Fetches the details of the currently authenticated account.",
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
method: "post",
|
|
@@ -56,15 +56,26 @@ export const accountsRouteSpecs = [
|
|
|
56
56
|
handler: accountsController.avatar,
|
|
57
57
|
summary: "Fetch account avatar",
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
method: "delete",
|
|
61
|
+
path: "/current",
|
|
62
|
+
validate: [auth("manageUsers")],
|
|
63
|
+
requestSchema: accountsValidation.deleteCurrentSchema,
|
|
64
|
+
responseSchema: accountResponseSchema,
|
|
65
|
+
handler: accountsController.deleteCurrent,
|
|
66
|
+
summary: "Delete the current user's account",
|
|
67
|
+
description: "Permanently deletes the current user's account. Will not delete associated data. This action is irreversible.",
|
|
68
|
+
},
|
|
59
69
|
{
|
|
60
70
|
method: "delete",
|
|
61
71
|
path: "/deleteCurrent",
|
|
62
72
|
validate: [auth("manageUsers")],
|
|
63
73
|
requestSchema: accountsValidation.deleteCurrentSchema,
|
|
64
74
|
responseSchema: accountResponseSchema,
|
|
65
|
-
privateDocs: true,
|
|
66
75
|
handler: accountsController.deleteCurrent,
|
|
67
|
-
summary: "Delete the current account",
|
|
76
|
+
summary: "Delete the current user's account",
|
|
77
|
+
privateDocs: true,
|
|
78
|
+
description: "LEGACY: Permanently deletes the current user's account. Will not delete associated data. This action is irreversible. (Replaced by DELETE /current)",
|
|
68
79
|
},
|
|
69
80
|
{
|
|
70
81
|
method: "get",
|
|
@@ -74,6 +85,7 @@ export const accountsRouteSpecs = [
|
|
|
74
85
|
responseSchema: accountResponseSchema,
|
|
75
86
|
handler: accountsController.getAccountById,
|
|
76
87
|
summary: "Get an account by ID",
|
|
88
|
+
description: "Fetches the details of a single account by its ID.",
|
|
77
89
|
},
|
|
78
90
|
{
|
|
79
91
|
method: "post",
|
|
@@ -83,7 +95,8 @@ export const accountsRouteSpecs = [
|
|
|
83
95
|
responseSchema: accountResponseSchema,
|
|
84
96
|
privateDocs: true,
|
|
85
97
|
handler: accountsController.updateEntry,
|
|
86
|
-
summary: "
|
|
98
|
+
summary: "Update an account by ID",
|
|
99
|
+
description: "LEGACY: Updates an existing account with a specified ID.",
|
|
87
100
|
},
|
|
88
101
|
{
|
|
89
102
|
method: "patch",
|
|
@@ -91,9 +104,9 @@ export const accountsRouteSpecs = [
|
|
|
91
104
|
validate: [auth("manageUsers"), validateParamsAccount],
|
|
92
105
|
requestSchema: accountsValidation.updateAccountSchema,
|
|
93
106
|
responseSchema: accountResponseSchema,
|
|
94
|
-
privateDocs: true,
|
|
95
107
|
handler: accountsController.updateEntry,
|
|
96
108
|
summary: "Update fields on an account by ID",
|
|
109
|
+
description: "Updates specific fields of an existing account identified by its ID.",
|
|
97
110
|
},
|
|
98
111
|
];
|
|
99
112
|
const router = Router();
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import httpStatus from "http-status";
|
|
2
|
-
import { ApiError } from "../utils/ApiError.js";
|
|
3
1
|
import catchAsync from "../utils/catchAsync.js";
|
|
4
2
|
import { getAdminDevices, getAdminIotDevices, getAdminStats, searchAdminCollections, } from "./adminSearch.service.js";
|
|
5
|
-
const ADMIN_ROLE_CLAIM = "https://memo.wirewire.de/roles";
|
|
6
|
-
const hasAdminRole = (auth) => {
|
|
7
|
-
const roles = auth?.[ADMIN_ROLE_CLAIM];
|
|
8
|
-
return Array.isArray(roles) && roles.includes("admin");
|
|
9
|
-
};
|
|
10
3
|
const readListQuery = (req) => {
|
|
11
4
|
const rawPage = Number(req.query.page);
|
|
12
5
|
const rawPerPage = Number(req.query.perPage);
|
|
@@ -20,9 +13,6 @@ const readListQuery = (req) => {
|
|
|
20
13
|
};
|
|
21
14
|
};
|
|
22
15
|
export const searchAdmin = catchAsync(async (req, res) => {
|
|
23
|
-
if (!hasAdminRole(req.auth)) {
|
|
24
|
-
throw new ApiError(httpStatus.FORBIDDEN, "User is not part of the admin group");
|
|
25
|
-
}
|
|
26
16
|
const search = String(req.query.search || "").trim();
|
|
27
17
|
const rawLimit = Number(req.query.limit);
|
|
28
18
|
const limit = Number.isFinite(rawLimit)
|
|
@@ -32,23 +22,14 @@ export const searchAdmin = catchAsync(async (req, res) => {
|
|
|
32
22
|
res.send(result);
|
|
33
23
|
});
|
|
34
24
|
export const getStats = catchAsync(async (req, res) => {
|
|
35
|
-
if (!hasAdminRole(req.auth)) {
|
|
36
|
-
throw new ApiError(httpStatus.FORBIDDEN, "User is not part of the admin group");
|
|
37
|
-
}
|
|
38
25
|
const result = await getAdminStats();
|
|
39
26
|
res.send(result);
|
|
40
27
|
});
|
|
41
28
|
export const getIotDevices = catchAsync(async (req, res) => {
|
|
42
|
-
if (!hasAdminRole(req.auth)) {
|
|
43
|
-
throw new ApiError(httpStatus.FORBIDDEN, "User is not part of the admin group");
|
|
44
|
-
}
|
|
45
29
|
const result = await getAdminIotDevices(readListQuery(req));
|
|
46
30
|
res.send(result);
|
|
47
31
|
});
|
|
48
32
|
export const getDevices = catchAsync(async (req, res) => {
|
|
49
|
-
if (!hasAdminRole(req.auth)) {
|
|
50
|
-
throw new ApiError(httpStatus.FORBIDDEN, "User is not part of the admin group");
|
|
51
|
-
}
|
|
52
33
|
const result = await getAdminDevices(readListQuery(req));
|
|
53
34
|
res.send(result);
|
|
54
35
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Router } from "express";
|
|
2
2
|
import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
|
|
3
3
|
import auth from "../middlewares/auth.js";
|
|
4
|
-
import {
|
|
4
|
+
import { validateAdminOrSupport } from "../middlewares/validateAdminOrSupport.js";
|
|
5
5
|
import { getDevices, getIotDevices, getStats, searchAdmin, } from "./adminSearch.controller.js";
|
|
6
6
|
import { adminDevicesSchema, adminIotDevicesSchema, adminSearchSchema, adminStatsSchema, } from "./adminSearch.validation.js";
|
|
7
7
|
import { adminDevicesResponseSchema, adminIotDevicesResponseSchema, adminSearchResponseSchema, adminStatsResponseSchema, } from "./adminSearch.schemas.js";
|
|
@@ -9,7 +9,7 @@ export const adminSearchRouteSpecs = [
|
|
|
9
9
|
{
|
|
10
10
|
method: "get",
|
|
11
11
|
path: "/stats",
|
|
12
|
-
validate: [auth("getUsers"),
|
|
12
|
+
validate: [auth("getUsers"), validateAdminOrSupport],
|
|
13
13
|
requestSchema: adminStatsSchema,
|
|
14
14
|
responseSchema: adminStatsResponseSchema,
|
|
15
15
|
handler: getStats,
|
|
@@ -19,7 +19,7 @@ export const adminSearchRouteSpecs = [
|
|
|
19
19
|
{
|
|
20
20
|
method: "get",
|
|
21
21
|
path: "/search",
|
|
22
|
-
validate: [auth("getUsers"),
|
|
22
|
+
validate: [auth("getUsers"), validateAdminOrSupport],
|
|
23
23
|
requestSchema: adminSearchSchema,
|
|
24
24
|
responseSchema: adminSearchResponseSchema,
|
|
25
25
|
handler: searchAdmin,
|
|
@@ -29,7 +29,7 @@ export const adminSearchRouteSpecs = [
|
|
|
29
29
|
{
|
|
30
30
|
method: "get",
|
|
31
31
|
path: "/iotDevices",
|
|
32
|
-
validate: [auth("getUsers"),
|
|
32
|
+
validate: [auth("getUsers"), validateAdminOrSupport],
|
|
33
33
|
requestSchema: adminIotDevicesSchema,
|
|
34
34
|
responseSchema: adminIotDevicesResponseSchema,
|
|
35
35
|
handler: getIotDevices,
|
|
@@ -39,7 +39,7 @@ export const adminSearchRouteSpecs = [
|
|
|
39
39
|
{
|
|
40
40
|
method: "get",
|
|
41
41
|
path: "/devices",
|
|
42
|
-
validate: [auth("getUsers"),
|
|
42
|
+
validate: [auth("getUsers"), validateAdminOrSupport],
|
|
43
43
|
requestSchema: adminDevicesSchema,
|
|
44
44
|
responseSchema: adminDevicesResponseSchema,
|
|
45
45
|
handler: getDevices,
|
|
@@ -107,6 +107,9 @@ const updateEntry = catchAsync(async (req, res) => {
|
|
|
107
107
|
});
|
|
108
108
|
const getEvents = catchAsync(async (req, res) => {
|
|
109
109
|
const device = await devicesService.getById(req.params.deviceId);
|
|
110
|
+
if (!device) {
|
|
111
|
+
throw new ApiError(httpStatus.NOT_FOUND, "Device not found");
|
|
112
|
+
}
|
|
110
113
|
const events = await iotDevicesService.getEvents({
|
|
111
114
|
...req.query,
|
|
112
115
|
createdAt: device.createdAt,
|
|
@@ -129,14 +132,9 @@ export const resetDevice = catchAsync(async (req, res) => {
|
|
|
129
132
|
const reset = await iotDevicesService.resetDevice(device.deviceId, req.body);
|
|
130
133
|
res.send({ device, reset });
|
|
131
134
|
});
|
|
132
|
-
const ledLight = catchAsync(async (req, res) => {
|
|
133
|
-
const device = await devicesService.getByIdWithIoT(req.params.deviceId);
|
|
134
|
-
const ping = await iotDevicesService.ledLightHint(device.deviceId, req.body);
|
|
135
|
-
res.send({ device, ping });
|
|
136
|
-
});
|
|
137
135
|
const rebootDevice = catchAsync(async (req, res) => {
|
|
138
136
|
const device = await devicesService.getByIdWithIoT(req.params.deviceId);
|
|
139
137
|
const reboot = await iotDevicesService.rebootDevice(device.deviceId);
|
|
140
138
|
res.send({ device, reboot });
|
|
141
139
|
});
|
|
142
|
-
export { createEntry, getEntries, queryDevicesByUser, getEvents, registerDevice, pingDevice,
|
|
140
|
+
export { createEntry, getEntries, queryDevicesByUser, getEvents, registerDevice, pingDevice, rebootDevice, getEntry, updateEntry, deleteEntry, };
|
|
@@ -6,7 +6,7 @@ import { validateQuerySearchUserAndOrganization } from "../middlewares/validateQ
|
|
|
6
6
|
import { validateDevice } from "../middlewares/validateDevice.js";
|
|
7
7
|
import * as devicesController from "./devices.controller.js";
|
|
8
8
|
import { resetDevice } from "./devices.controller.js";
|
|
9
|
-
import { createDeviceSchema, queryDevicesSchema, getDeviceSchema, updateDeviceSchema, deleteDeviceSchema, getEventsSchema, pingDeviceSchema, registerDeviceSchema,
|
|
9
|
+
import { createDeviceSchema, queryDevicesSchema, getDeviceSchema, updateDeviceSchema, deleteDeviceSchema, getEventsSchema, pingDeviceSchema, registerDeviceSchema, rebootDeviceSchema, resetDeviceSchema, } from "./devices.validation.js";
|
|
10
10
|
import { deviceResponseSchema, devicesResponseSchema, eventResponseSchema, genericResponseSchema, resetResponseSchema, } from "./devices.schemas.js";
|
|
11
11
|
import { validateOrganizationDelete, validateOrganizationUpdate, } from "../middlewares/validateAction.js";
|
|
12
12
|
export const devicesRouteSpecs = [
|
|
@@ -32,7 +32,7 @@ export const devicesRouteSpecs = [
|
|
|
32
32
|
responseSchema: devicesResponseSchema,
|
|
33
33
|
handler: devicesController.queryDevicesByUser,
|
|
34
34
|
summary: "Query devices by user",
|
|
35
|
-
description: "Retrieve a paginated list of devices visible to the authenticated user.",
|
|
35
|
+
description: "Retrieve a paginated list of devices visible to the authenticated user. Either patient or organization is required.",
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
method: "get",
|
|
@@ -61,8 +61,19 @@ export const devicesRouteSpecs = [
|
|
|
61
61
|
requestSchema: updateDeviceSchema,
|
|
62
62
|
responseSchema: deviceResponseSchema,
|
|
63
63
|
handler: devicesController.updateEntry,
|
|
64
|
+
privateDocs: true,
|
|
64
65
|
summary: "Update a device",
|
|
65
|
-
description: "Modify the properties of an existing device identified by its ID.",
|
|
66
|
+
description: "LEGACY: Modify the properties of an existing device identified by its ID.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
method: "patch",
|
|
70
|
+
path: "/:deviceId",
|
|
71
|
+
validate: [auth("manageUsers"), validateDevice, validateOrganizationUpdate],
|
|
72
|
+
requestSchema: updateDeviceSchema,
|
|
73
|
+
responseSchema: deviceResponseSchema,
|
|
74
|
+
handler: devicesController.updateEntry,
|
|
75
|
+
summary: "Update a device",
|
|
76
|
+
description: "Updates specific fields of an existing device identified by its ID.",
|
|
66
77
|
},
|
|
67
78
|
{
|
|
68
79
|
method: "delete",
|
|
@@ -88,17 +99,6 @@ export const devicesRouteSpecs = [
|
|
|
88
99
|
summary: "Register a device",
|
|
89
100
|
description: "Associate an existing device with the authenticated organization.",
|
|
90
101
|
},
|
|
91
|
-
{
|
|
92
|
-
method: "post",
|
|
93
|
-
path: "/ledlight/:deviceId",
|
|
94
|
-
validate: [auth("getUsers"), validateDevice],
|
|
95
|
-
requestSchema: ledLightSchema,
|
|
96
|
-
responseSchema: genericResponseSchema,
|
|
97
|
-
handler: devicesController.ledLight,
|
|
98
|
-
summary: "Set LED light on device",
|
|
99
|
-
description: "Turn the device’s LED on or off, or set its color/brightness.",
|
|
100
|
-
memoOnly: true,
|
|
101
|
-
},
|
|
102
102
|
{
|
|
103
103
|
method: "get",
|
|
104
104
|
path: "/ping/:deviceId",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { zGet, zObjectId, zObjectIdFor, zPatchBody, zUpdate, zDelete, zPagination, } from "../utils/zValidations.js";
|
|
3
|
+
import { zGet, zObjectId, zObjectIdFor, zPatchBody, zUpdate, zDelete, zPagination, zTypeFilter, } from "../utils/zValidations.js";
|
|
4
4
|
extendZodWithOpenApi(z);
|
|
5
5
|
export const createEntrySchema = {
|
|
6
6
|
body: z
|
|
@@ -58,9 +58,9 @@ export const getEventsSchema = {
|
|
|
58
58
|
}),
|
|
59
59
|
query: z
|
|
60
60
|
.object({
|
|
61
|
-
DateStart: z.string(),
|
|
62
|
-
DateEnd: z.string(),
|
|
63
|
-
TypeFilter:
|
|
61
|
+
DateStart: z.string().datetime().optional(),
|
|
62
|
+
DateEnd: z.string().datetime().optional(),
|
|
63
|
+
TypeFilter: zTypeFilter,
|
|
64
64
|
})
|
|
65
65
|
.openapi({ description: "Fetch device events in a time range" }),
|
|
66
66
|
};
|
|
@@ -95,53 +95,6 @@ export const createDeviceSchema = {
|
|
|
95
95
|
};
|
|
96
96
|
export const deleteDeviceSchema = zDelete("deviceId");
|
|
97
97
|
export const getDeviceSchema = zGet("deviceId");
|
|
98
|
-
export const ledLightSchema = {
|
|
99
|
-
params: z.object({
|
|
100
|
-
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
101
|
-
}),
|
|
102
|
-
body: z
|
|
103
|
-
.object({
|
|
104
|
-
led: z.array(z.tuple([z.number().int(), z.number().int()])),
|
|
105
|
-
timeout: z.number().int(),
|
|
106
|
-
})
|
|
107
|
-
.openapi({
|
|
108
|
-
description: "LED light configuration for device",
|
|
109
|
-
example: {
|
|
110
|
-
led: [
|
|
111
|
-
[0, 0],
|
|
112
|
-
[1, 0],
|
|
113
|
-
[2, 0],
|
|
114
|
-
[3, 0],
|
|
115
|
-
[4, 0],
|
|
116
|
-
[5, 0],
|
|
117
|
-
[6, 0],
|
|
118
|
-
[7, 0],
|
|
119
|
-
[8, 0],
|
|
120
|
-
[9, 0],
|
|
121
|
-
[10, 0],
|
|
122
|
-
[11, 0],
|
|
123
|
-
[12, 0],
|
|
124
|
-
[13, 0],
|
|
125
|
-
[14, 0],
|
|
126
|
-
[15, 100],
|
|
127
|
-
[16, 100],
|
|
128
|
-
[17, 100],
|
|
129
|
-
[18, 100],
|
|
130
|
-
[19, 100],
|
|
131
|
-
[20, 100],
|
|
132
|
-
[21, 100],
|
|
133
|
-
[22, 0],
|
|
134
|
-
[23, 0],
|
|
135
|
-
[24, 0],
|
|
136
|
-
[25, 0],
|
|
137
|
-
[26, 0],
|
|
138
|
-
[27, 0],
|
|
139
|
-
[28, 0],
|
|
140
|
-
],
|
|
141
|
-
timeout: 40,
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
};
|
|
145
98
|
export const pingDeviceSchema = {
|
|
146
99
|
params: z.object({
|
|
147
100
|
deviceId: zObjectIdFor("deviceId").openapi({
|
|
@@ -191,9 +144,17 @@ export const registerDeviceSchema = {
|
|
|
191
144
|
};
|
|
192
145
|
export const queryDevicesSchema = {
|
|
193
146
|
...zPagination,
|
|
194
|
-
query: zPagination.query
|
|
195
|
-
|
|
196
|
-
|
|
147
|
+
query: zPagination.query
|
|
148
|
+
.extend({
|
|
149
|
+
patient: zObjectIdFor("patient").optional().openapi({
|
|
150
|
+
description: "Patient ObjectId. Required if organization is not provided.",
|
|
151
|
+
}),
|
|
152
|
+
organization: zObjectIdFor("organization").optional().openapi({
|
|
153
|
+
description: "Organization ObjectId. Required if patient is not provided.",
|
|
154
|
+
}),
|
|
155
|
+
})
|
|
156
|
+
.refine((data) => data.patient || data.organization, {
|
|
157
|
+
message: "Either patient or organization is required",
|
|
197
158
|
}),
|
|
198
159
|
};
|
|
199
160
|
export const subscriptionSchema = {
|
|
@@ -34,7 +34,7 @@ const actionButton = ({ link, text, color = "#0076ff", }) => {
|
|
|
34
34
|
</tr>
|
|
35
35
|
</table>`;
|
|
36
36
|
};
|
|
37
|
-
export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", url = "", domain = "
|
|
37
|
+
export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", url = "", domain = "web", appBaseUrl = `https://${domain}.wirewire.de`, productName = domain, companyName = "The Wire UG", accountUrl = `${appBaseUrl}/account`, image, email, actionButtonText, lng, }) => {
|
|
38
38
|
const interactive = "#0076ff";
|
|
39
39
|
const sesClient = new SESv2Client({
|
|
40
40
|
region: "eu-central-1",
|
|
@@ -511,8 +511,8 @@ export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", ur
|
|
|
511
511
|
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
512
512
|
<tr>
|
|
513
513
|
<td class="email-masthead">
|
|
514
|
-
<a href="
|
|
515
|
-
${
|
|
514
|
+
<a href="${appBaseUrl}" class="f-fallback email-masthead_name">
|
|
515
|
+
${productName}
|
|
516
516
|
</a>
|
|
517
517
|
</td>
|
|
518
518
|
</tr>
|
|
@@ -531,7 +531,7 @@ export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", ur
|
|
|
531
531
|
${actionButton({
|
|
532
532
|
link: urlStartsWithHttp(url)
|
|
533
533
|
? url
|
|
534
|
-
:
|
|
534
|
+
: `${appBaseUrl}${url}`,
|
|
535
535
|
text: actionButtonTextWithLanguage,
|
|
536
536
|
})}
|
|
537
537
|
</div>
|
|
@@ -546,8 +546,8 @@ export const sendEmail = async ({ title = "Kein Titel", body = "Kein Inhalt", ur
|
|
|
546
546
|
<tr>
|
|
547
547
|
<td class="content-cell" align="center">
|
|
548
548
|
<p class="f-fallback sub align-center">
|
|
549
|
-
${
|
|
550
|
-
<a href="
|
|
549
|
+
${companyName}
|
|
550
|
+
<a href="${accountUrl}">Account</a>
|
|
551
551
|
|
|
552
552
|
${config.env !== "production" ? `<br/><br/>Environment: ${config.env}` : ""}
|
|
553
553
|
</p>
|
package/dist/src/index.js
CHANGED
|
@@ -8,12 +8,16 @@ 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 * from "../src/users/users.route";
|
|
11
12
|
export { default as usersService } from "../src/users/users.service";
|
|
13
|
+
export * from "../src/users/users.service";
|
|
14
|
+
export * from "../src/users/users.validation";
|
|
12
15
|
export { default as accountsRoute } from "../src/accounts/accounts.route";
|
|
13
16
|
export { default as adminSearchRoute } from "../src/admin/adminSearch.route";
|
|
14
17
|
export { default as accountsService } from "../src/accounts/accounts.service";
|
|
15
18
|
export { auth0 } from "../src/accounts/auth0.service";
|
|
16
19
|
export { default as organizationsRoute } from "../src/organizations/organizations.route";
|
|
20
|
+
export * from "../src/organizations/organizations.controller";
|
|
17
21
|
export { default as organizationsService } from "../src/organizations/organizations.service";
|
|
18
22
|
export { default as Organization } from "../src/organizations/organizations.model";
|
|
19
23
|
export { default as devicesRoute } from "../src/devices/devices.route";
|
|
@@ -25,11 +29,11 @@ export { default as devicesNotificationsService } from "../src/devicesNotificati
|
|
|
25
29
|
export { default as iotDevicesService } from "../src/iotdevice/iotdevice.service";
|
|
26
30
|
export { default as iotdeviceRoute } from "../src/iotdevice/iotdevice.route";
|
|
27
31
|
export { SIMILARITY_THRESHOLD, alarmsDevice, getDeviceStatus, getDeviceStatusList, shadowAlarmGet, shadowAlarmUpdate, } from "../src/iotdevice/iotdevice.service";
|
|
28
|
-
export { default as pdfRoute } from "../src/pdf/pdf.route";
|
|
29
32
|
export { default as tokensRoute } from "../src/tokens/tokens.route";
|
|
30
33
|
export * from "../src/tokens/tokens.service";
|
|
31
34
|
export { default as Token } from "../src/tokens/tokens.model";
|
|
32
35
|
export { User } from "../src/users/users.model";
|
|
36
|
+
export * from "../src/users/users.model";
|
|
33
37
|
export { isAdmin, validateAdmin } from "../src/middlewares/validateAdmin";
|
|
34
38
|
export { sendEmail } from "../src/email/email.service";
|
|
35
39
|
export { default as catchAsync } from "../src/utils/catchAsync";
|
|
@@ -26,6 +26,7 @@ export const iotdeviceRouteSpecs = [
|
|
|
26
26
|
requestSchema: getEventsSchema,
|
|
27
27
|
responseSchema: eventResponseSchema.array(),
|
|
28
28
|
handler: getEvents,
|
|
29
|
+
privateDocs: true,
|
|
29
30
|
summary: "Get events for a device",
|
|
30
31
|
description: "Fetches event records for the specified device by its ID.",
|
|
31
32
|
},
|
|
@@ -104,8 +105,9 @@ export const iotdeviceRouteSpecs = [
|
|
|
104
105
|
requestSchema: apiStatusRequestSchema,
|
|
105
106
|
responseSchema: apiStatusSchema,
|
|
106
107
|
handler: getApiStatus,
|
|
108
|
+
privateDocs: true,
|
|
107
109
|
summary: "Get API status by 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.",
|
|
110
|
+
description: "Retrieves the IoT API status information for a given status kind to monitor system health or performance. Can be accessed without authentication for uptime monitoring purposes.",
|
|
109
111
|
},
|
|
110
112
|
{
|
|
111
113
|
method: "get",
|
|
@@ -22,16 +22,16 @@ const iotDataClient = new IoTDataPlaneClient({
|
|
|
22
22
|
*/
|
|
23
23
|
export const getEvents = async (params) => {
|
|
24
24
|
const accessToken = await getAuth0Token();
|
|
25
|
+
// Keep the requested start date, but never query events before the device existed.
|
|
26
|
+
const dateStart = params.createdAt && new Date(params.DateStart) < new Date(params.createdAt)
|
|
27
|
+
? params.createdAt
|
|
28
|
+
: params.DateStart;
|
|
25
29
|
try {
|
|
26
30
|
const response = await axios.get(`${process.env.IOT_API_URL}getevent`, {
|
|
27
31
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
28
32
|
params: {
|
|
29
33
|
DeviceId: params.DeviceId,
|
|
30
|
-
DateStart:
|
|
31
|
-
? params.DateStart
|
|
32
|
-
: params.DateStart < params.createdAt
|
|
33
|
-
? params.DateStart
|
|
34
|
-
: params.createdAt,
|
|
34
|
+
DateStart: dateStart,
|
|
35
35
|
DateEnd: params.DateEnd,
|
|
36
36
|
TypeFilter: params.TypeFilter,
|
|
37
37
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { zObjectId, zPagination } from '../utils/zValidations';
|
|
3
|
+
import { zObjectId, zPagination, zTypeFilter } from '../utils/zValidations';
|
|
4
4
|
extendZodWithOpenApi(z);
|
|
5
5
|
export const getDevice = {
|
|
6
6
|
params: z.object({
|
|
@@ -27,9 +27,17 @@ export const getEventsSchema = {
|
|
|
27
27
|
deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
|
|
28
28
|
}),
|
|
29
29
|
query: z.object({
|
|
30
|
-
DateStart: z
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
DateStart: z
|
|
31
|
+
.string()
|
|
32
|
+
.datetime()
|
|
33
|
+
.openapi({ description: 'Start date (ISO‐string)', example: '2025-05-01T00:00:00Z' })
|
|
34
|
+
.optional(),
|
|
35
|
+
DateEnd: z
|
|
36
|
+
.string()
|
|
37
|
+
.datetime()
|
|
38
|
+
.openapi({ description: 'End date (ISO‐string)', example: '2025-05-31T23:59:59Z' })
|
|
39
|
+
.optional(),
|
|
40
|
+
TypeFilter: zTypeFilter.default(''),
|
|
33
41
|
}),
|
|
34
42
|
};
|
|
35
43
|
export const updateEntrySchema = {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import ApiError from "../utils/ApiError";
|
|
3
|
+
const ROLES_CLAIM = "https://memo.wirewire.de/roles";
|
|
4
|
+
const isAdminOrSupport = (user) => {
|
|
5
|
+
if (!user)
|
|
6
|
+
return false;
|
|
7
|
+
const roles = user[ROLES_CLAIM];
|
|
8
|
+
return Array.isArray(roles)
|
|
9
|
+
? roles.includes("admin") || roles.includes("support")
|
|
10
|
+
: false;
|
|
11
|
+
};
|
|
12
|
+
const validateAdminOrSupport = async (req, res, next) => {
|
|
13
|
+
if (isAdminOrSupport(req.auth)) {
|
|
14
|
+
next();
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
next(new ApiError(httpStatus.FORBIDDEN, "User is not part of the admin or support group (validateAdminOrSupport)"));
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
export { isAdminOrSupport, validateAdminOrSupport };
|
|
@@ -7,15 +7,26 @@ import organizationsService, { deleteOrganizationById, } from "./organizations.s
|
|
|
7
7
|
import mongoose from "mongoose";
|
|
8
8
|
import { filterOptions } from "../utils/filterOptions.js";
|
|
9
9
|
const ObjectId = mongoose.Types.ObjectId;
|
|
10
|
+
let createOrganizationOwnerUserHook = null;
|
|
11
|
+
export const setCreateOrganizationOwnerUserHook = (hook) => {
|
|
12
|
+
createOrganizationOwnerUserHook = hook ?? null;
|
|
13
|
+
};
|
|
14
|
+
const createOrganizationOwnerUserBody = (params) => ({
|
|
15
|
+
organization: params.organization._id,
|
|
16
|
+
owner: params.owner,
|
|
17
|
+
role: "admin",
|
|
18
|
+
status: "accept",
|
|
19
|
+
...(createOrganizationOwnerUserHook
|
|
20
|
+
? createOrganizationOwnerUserHook(params)
|
|
21
|
+
: {}),
|
|
22
|
+
});
|
|
10
23
|
export const createOrganization = catchAsync(async (req, res) => {
|
|
11
24
|
const organization = await organizationsService.createOrganization(req.body);
|
|
12
|
-
const user = await usersService.createUser({
|
|
13
|
-
organization
|
|
25
|
+
const user = await usersService.createUser(createOrganizationOwnerUserBody({
|
|
26
|
+
organization,
|
|
14
27
|
owner: res.req.auth.sub,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
status: "accept",
|
|
18
|
-
});
|
|
28
|
+
request: req,
|
|
29
|
+
}));
|
|
19
30
|
res.status(httpStatus.CREATED).send(organization);
|
|
20
31
|
});
|
|
21
32
|
export const getOrganizations = catchAsync(async (req, res) => {
|
|
@@ -74,7 +74,8 @@ export const organizationsRouteSpecs = [
|
|
|
74
74
|
responseSchema: organizationResponseSchema,
|
|
75
75
|
handler: updateOrganization,
|
|
76
76
|
summary: "Update an organization by ID",
|
|
77
|
-
|
|
77
|
+
privateDocs: true,
|
|
78
|
+
description: "LEGACY: Updates the details of a specific organization by its ID.",
|
|
78
79
|
},
|
|
79
80
|
{
|
|
80
81
|
method: "delete",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import mongoose, { Schema } from "mongoose";
|
|
3
3
|
import { toJSON, paginate } from "../models/plugins/index.js";
|
|
4
|
-
const userSchema = new Schema({
|
|
4
|
+
export const userSchema = new Schema({
|
|
5
5
|
name: { type: String, trim: true },
|
|
6
6
|
avatar: { type: String },
|
|
7
7
|
timezone: { type: String, default: "Europe/Berlin" },
|
|
@@ -10,7 +10,6 @@ const userSchema = new Schema({
|
|
|
10
10
|
inviteCode: { type: String },
|
|
11
11
|
email: { type: String },
|
|
12
12
|
role: { type: String, default: "patient" /* , enum: Object.keys(roles) */ },
|
|
13
|
-
category: { type: String, default: "patient" },
|
|
14
13
|
status: { type: String },
|
|
15
14
|
meta: { type: Schema.Types.Mixed },
|
|
16
15
|
}, {
|
|
@@ -27,6 +26,9 @@ userSchema.virtual("organizationData", {
|
|
|
27
26
|
// plugins
|
|
28
27
|
userSchema.plugin(toJSON);
|
|
29
28
|
userSchema.plugin(paginate);
|
|
29
|
+
export const extendUserSchema = (definition) => {
|
|
30
|
+
userSchema.add(definition);
|
|
31
|
+
};
|
|
30
32
|
/**
|
|
31
33
|
* Check if email is taken
|
|
32
34
|
* @param {string} email - The user's email
|
|
@@ -17,7 +17,6 @@ export const userRouteSpecs = [
|
|
|
17
17
|
handler: userController.createUser,
|
|
18
18
|
summary: "Create a new user",
|
|
19
19
|
description: "Creates a new user within the current organization.",
|
|
20
|
-
memoOnly: true,
|
|
21
20
|
},
|
|
22
21
|
{
|
|
23
22
|
method: "get",
|
|
@@ -107,7 +106,8 @@ export const userRouteSpecs = [
|
|
|
107
106
|
responseSchema: updateUserResponseSchema,
|
|
108
107
|
handler: userController.updateUser,
|
|
109
108
|
summary: "Update a user by ID",
|
|
110
|
-
|
|
109
|
+
privateDocs: true,
|
|
110
|
+
description: "LEGACY: Replaces a user’s full record with the provided data. (Replaced by PATCH /:userId)",
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
method: "patch",
|