@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.
Files changed (54) hide show
  1. package/dist/src/accounts/accounts.route.js +18 -5
  2. package/dist/src/admin/adminSearch.controller.js +0 -19
  3. package/dist/src/admin/adminSearch.route.js +5 -5
  4. package/dist/src/devices/devices.controller.js +4 -6
  5. package/dist/src/devices/devices.route.js +14 -14
  6. package/dist/src/devices/devices.validation.js +15 -54
  7. package/dist/src/email/email.service.js +6 -6
  8. package/dist/src/index.js +5 -1
  9. package/dist/src/iotdevice/iotdevice.route.js +3 -1
  10. package/dist/src/iotdevice/iotdevice.service.js +5 -5
  11. package/dist/src/iotdevice/iotdevice.validation.js +12 -4
  12. package/dist/src/middlewares/validateAdminOrSupport.js +20 -0
  13. package/dist/src/organizations/organizations.controller.js +17 -6
  14. package/dist/src/organizations/organizations.route.js +2 -1
  15. package/dist/src/users/users.model.js +4 -2
  16. package/dist/src/users/users.route.js +2 -2
  17. package/dist/src/users/users.service.js +26 -10
  18. package/dist/src/users/users.validation.js +60 -61
  19. package/dist/src/utils/buildRouterAndDocs.js +7 -4
  20. package/dist/src/utils/zValidations.js +7 -0
  21. package/package.json +5 -2
  22. package/scripts/release-and-sync-paperless.mjs +21 -2
  23. package/scripts/release-version.mjs +21 -2
  24. package/src/accounts/accounts.route.ts +21 -5
  25. package/src/admin/adminSearch.controller.ts +0 -35
  26. package/src/admin/adminSearch.route.ts +8 -6
  27. package/src/admin/adminSearch.service.ts +6 -10
  28. package/src/devices/devices.controller.ts +4 -12
  29. package/src/devices/devices.route.ts +14 -15
  30. package/src/devices/devices.validation.ts +20 -54
  31. package/src/email/email.service.ts +15 -7
  32. package/src/index.ts +5 -1
  33. package/src/iotdevice/iotdevice.route.ts +3 -1
  34. package/src/iotdevice/iotdevice.service.ts +8 -7
  35. package/src/iotdevice/iotdevice.validation.ts +12 -4
  36. package/src/middlewares/validateAdminOrSupport.ts +34 -0
  37. package/src/organizations/organizations.controller.ts +38 -7
  38. package/src/organizations/organizations.route.ts +3 -1
  39. package/src/users/users.model.ts +7 -3
  40. package/src/users/users.route.ts +3 -2
  41. package/src/users/users.service.ts +50 -14
  42. package/src/users/users.validation.ts +62 -60
  43. package/src/utils/buildRouterAndDocs.ts +14 -5
  44. package/src/utils/zValidations.ts +8 -0
  45. package/dist/src/pdf/pdf.controller.js +0 -24
  46. package/dist/src/pdf/pdf.route.js +0 -22
  47. package/dist/src/pdf/pdf.schemas.js +0 -6
  48. package/dist/src/pdf/pdf.service.js +0 -64
  49. package/dist/src/pdf/pdf.validation.js +0 -27
  50. package/src/pdf/pdf.controller.ts +0 -35
  51. package/src/pdf/pdf.route.ts +0 -28
  52. package/src/pdf/pdf.schemas.ts +0 -7
  53. package/src/pdf/pdf.service.ts +0 -103
  54. 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: "Create or replace an account by ID",
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 { validateAdmin } from "../middlewares/validateAdmin.js";
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"), validateAdmin],
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"), validateAdmin],
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"), validateAdmin],
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"), validateAdmin],
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, ledLight, rebootDevice, getEntry, updateEntry, deleteEntry, };
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, ledLightSchema, rebootDeviceSchema, resetDeviceSchema, } from "./devices.validation.js";
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: z.string().optional(),
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.extend({
195
- patient: zObjectIdFor("patient").optional(),
196
- organization: zObjectIdFor("organization").optional(),
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 = "memo", image, email, actionButtonText, lng, }) => {
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="https://${domain}.wirewire.de" class="f-fallback email-masthead_name">
515
- ${domain === "memo" ? "ANABOX smart" : "paperlesspaper"}
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
- : `http://${domain}.wirewire.de${url}`,
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
- ${domain === "web" ? "The Wire UG" : "wirewire GmbH"}
550
- <a href="http://${domain}.wirewire.de/account">Account</a>
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: !params.createdAt
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.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(''),
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: organization._id,
25
+ const user = await usersService.createUser(createOrganizationOwnerUserBody({
26
+ organization,
14
27
  owner: res.req.auth.sub,
15
- role: "admin",
16
- category: "relative",
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
- description: "Updates the details of a specific organization by its ID.",
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
- description: "Replaces a user’s full record with the provided data.",
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",