@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
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
zUpdate,
|
|
10
10
|
zDelete,
|
|
11
11
|
zPagination,
|
|
12
|
+
zTypeFilter,
|
|
12
13
|
} from "../utils/zValidations.js";
|
|
13
14
|
|
|
14
15
|
extendZodWithOpenApi(z);
|
|
@@ -73,9 +74,9 @@ export const getEventsSchema = {
|
|
|
73
74
|
}),
|
|
74
75
|
query: z
|
|
75
76
|
.object({
|
|
76
|
-
DateStart: z.string(),
|
|
77
|
-
DateEnd: z.string(),
|
|
78
|
-
TypeFilter:
|
|
77
|
+
DateStart: z.string().datetime().optional(),
|
|
78
|
+
DateEnd: z.string().datetime().optional(),
|
|
79
|
+
TypeFilter: zTypeFilter,
|
|
79
80
|
})
|
|
80
81
|
.openapi({ description: "Fetch device events in a time range" }),
|
|
81
82
|
};
|
|
@@ -112,53 +113,6 @@ export const createDeviceSchema = {
|
|
|
112
113
|
};
|
|
113
114
|
export const deleteDeviceSchema = zDelete("deviceId");
|
|
114
115
|
export const getDeviceSchema = zGet("deviceId");
|
|
115
|
-
export const ledLightSchema = {
|
|
116
|
-
params: z.object({
|
|
117
|
-
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
118
|
-
}),
|
|
119
|
-
body: z
|
|
120
|
-
.object({
|
|
121
|
-
led: z.array(z.tuple([z.number().int(), z.number().int()])),
|
|
122
|
-
timeout: z.number().int(),
|
|
123
|
-
})
|
|
124
|
-
.openapi({
|
|
125
|
-
description: "LED light configuration for device",
|
|
126
|
-
example: {
|
|
127
|
-
led: [
|
|
128
|
-
[0, 0],
|
|
129
|
-
[1, 0],
|
|
130
|
-
[2, 0],
|
|
131
|
-
[3, 0],
|
|
132
|
-
[4, 0],
|
|
133
|
-
[5, 0],
|
|
134
|
-
[6, 0],
|
|
135
|
-
[7, 0],
|
|
136
|
-
[8, 0],
|
|
137
|
-
[9, 0],
|
|
138
|
-
[10, 0],
|
|
139
|
-
[11, 0],
|
|
140
|
-
[12, 0],
|
|
141
|
-
[13, 0],
|
|
142
|
-
[14, 0],
|
|
143
|
-
[15, 100],
|
|
144
|
-
[16, 100],
|
|
145
|
-
[17, 100],
|
|
146
|
-
[18, 100],
|
|
147
|
-
[19, 100],
|
|
148
|
-
[20, 100],
|
|
149
|
-
[21, 100],
|
|
150
|
-
[22, 0],
|
|
151
|
-
[23, 0],
|
|
152
|
-
[24, 0],
|
|
153
|
-
[25, 0],
|
|
154
|
-
[26, 0],
|
|
155
|
-
[27, 0],
|
|
156
|
-
[28, 0],
|
|
157
|
-
],
|
|
158
|
-
timeout: 40,
|
|
159
|
-
},
|
|
160
|
-
}),
|
|
161
|
-
};
|
|
162
116
|
export const pingDeviceSchema = {
|
|
163
117
|
params: z.object({
|
|
164
118
|
deviceId: zObjectIdFor("deviceId").openapi({
|
|
@@ -215,16 +169,28 @@ export const registerDeviceSchema = {
|
|
|
215
169
|
|
|
216
170
|
export const queryDevicesSchema = {
|
|
217
171
|
...zPagination,
|
|
218
|
-
query: zPagination.query
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
172
|
+
query: zPagination.query
|
|
173
|
+
.extend({
|
|
174
|
+
patient: zObjectIdFor("patient").optional().openapi({
|
|
175
|
+
description:
|
|
176
|
+
"Patient ObjectId. Required if organization is not provided.",
|
|
177
|
+
}),
|
|
178
|
+
organization: zObjectIdFor("organization").optional().openapi({
|
|
179
|
+
description:
|
|
180
|
+
"Organization ObjectId. Required if patient is not provided.",
|
|
181
|
+
}),
|
|
182
|
+
})
|
|
183
|
+
.refine((data) => data.patient || data.organization, {
|
|
184
|
+
message: "Either patient or organization is required",
|
|
185
|
+
}),
|
|
222
186
|
};
|
|
187
|
+
|
|
223
188
|
export const subscriptionSchema = {
|
|
224
189
|
params: z.object({
|
|
225
190
|
deviceId: zObjectId.openapi({ description: "Device ObjectId" }),
|
|
226
191
|
}),
|
|
227
192
|
};
|
|
193
|
+
|
|
228
194
|
export const uploadSingleImageFromWebsiteSchema = {};
|
|
229
195
|
export const updateDeviceSchema = {
|
|
230
196
|
...zUpdate("deviceId"),
|
|
@@ -54,11 +54,15 @@ const actionButton = ({
|
|
|
54
54
|
</table>`;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
interface SendEmailParams {
|
|
57
|
+
export interface SendEmailParams {
|
|
58
58
|
title?: string;
|
|
59
59
|
body?: string;
|
|
60
60
|
url?: string;
|
|
61
61
|
domain?: string;
|
|
62
|
+
appBaseUrl?: string;
|
|
63
|
+
productName?: string;
|
|
64
|
+
companyName?: string;
|
|
65
|
+
accountUrl?: string;
|
|
62
66
|
image?: string;
|
|
63
67
|
email: string;
|
|
64
68
|
actionButtonText?: string;
|
|
@@ -69,7 +73,11 @@ export const sendEmail = async ({
|
|
|
69
73
|
title = "Kein Titel",
|
|
70
74
|
body = "Kein Inhalt",
|
|
71
75
|
url = "",
|
|
72
|
-
domain = "
|
|
76
|
+
domain = "web",
|
|
77
|
+
appBaseUrl = `https://${domain}.wirewire.de`,
|
|
78
|
+
productName = domain,
|
|
79
|
+
companyName = "The Wire UG",
|
|
80
|
+
accountUrl = `${appBaseUrl}/account`,
|
|
73
81
|
image,
|
|
74
82
|
email,
|
|
75
83
|
actionButtonText,
|
|
@@ -560,8 +568,8 @@ export const sendEmail = async ({
|
|
|
560
568
|
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
|
561
569
|
<tr>
|
|
562
570
|
<td class="email-masthead">
|
|
563
|
-
<a href="
|
|
564
|
-
${
|
|
571
|
+
<a href="${appBaseUrl}" class="f-fallback email-masthead_name">
|
|
572
|
+
${productName}
|
|
565
573
|
</a>
|
|
566
574
|
</td>
|
|
567
575
|
</tr>
|
|
@@ -580,7 +588,7 @@ export const sendEmail = async ({
|
|
|
580
588
|
${actionButton({
|
|
581
589
|
link: urlStartsWithHttp(url)
|
|
582
590
|
? url
|
|
583
|
-
:
|
|
591
|
+
: `${appBaseUrl}${url}`,
|
|
584
592
|
text: actionButtonTextWithLanguage,
|
|
585
593
|
})}
|
|
586
594
|
</div>
|
|
@@ -595,8 +603,8 @@ export const sendEmail = async ({
|
|
|
595
603
|
<tr>
|
|
596
604
|
<td class="content-cell" align="center">
|
|
597
605
|
<p class="f-fallback sub align-center">
|
|
598
|
-
${
|
|
599
|
-
<a href="
|
|
606
|
+
${companyName}
|
|
607
|
+
<a href="${accountUrl}">Account</a>
|
|
600
608
|
|
|
601
609
|
${config.env !== "production" ? `<br/><br/>Environment: ${config.env}` : ""}
|
|
602
610
|
</p>
|
package/src/index.ts
CHANGED
|
@@ -13,12 +13,16 @@ export {
|
|
|
13
13
|
export { initI18n } from "../src/i18n/i18n";
|
|
14
14
|
export { default as i18n } from "../src/i18n/i18n";
|
|
15
15
|
export { default as usersRoute } from "../src/users/users.route";
|
|
16
|
+
export * from "../src/users/users.route";
|
|
16
17
|
export { default as usersService } from "../src/users/users.service";
|
|
18
|
+
export * from "../src/users/users.service";
|
|
19
|
+
export * from "../src/users/users.validation";
|
|
17
20
|
export { default as accountsRoute } from "../src/accounts/accounts.route";
|
|
18
21
|
export { default as adminSearchRoute } from "../src/admin/adminSearch.route";
|
|
19
22
|
export { default as accountsService } from "../src/accounts/accounts.service";
|
|
20
23
|
export { auth0 } from "../src/accounts/auth0.service";
|
|
21
24
|
export { default as organizationsRoute } from "../src/organizations/organizations.route";
|
|
25
|
+
export * from "../src/organizations/organizations.controller";
|
|
22
26
|
export { default as organizationsService } from "../src/organizations/organizations.service";
|
|
23
27
|
export { default as Organization } from "../src/organizations/organizations.model";
|
|
24
28
|
export { default as devicesRoute } from "../src/devices/devices.route";
|
|
@@ -37,11 +41,11 @@ export {
|
|
|
37
41
|
shadowAlarmGet,
|
|
38
42
|
shadowAlarmUpdate,
|
|
39
43
|
} from "../src/iotdevice/iotdevice.service";
|
|
40
|
-
export { default as pdfRoute } from "../src/pdf/pdf.route";
|
|
41
44
|
export { default as tokensRoute } from "../src/tokens/tokens.route";
|
|
42
45
|
export * from "../src/tokens/tokens.service";
|
|
43
46
|
export { default as Token } from "../src/tokens/tokens.model";
|
|
44
47
|
export { User } from "../src/users/users.model";
|
|
48
|
+
export * from "../src/users/users.model";
|
|
45
49
|
export { isAdmin, validateAdmin } from "../src/middlewares/validateAdmin";
|
|
46
50
|
export { sendEmail } from "../src/email/email.service";
|
|
47
51
|
export { default as catchAsync } from "../src/utils/catchAsync";
|
|
@@ -62,6 +62,7 @@ export const iotdeviceRouteSpecs: RouteSpec[] = [
|
|
|
62
62
|
requestSchema: getEventsSchema,
|
|
63
63
|
responseSchema: eventResponseSchema.array(),
|
|
64
64
|
handler: getEvents,
|
|
65
|
+
privateDocs: true,
|
|
65
66
|
summary: "Get events for a device",
|
|
66
67
|
description: "Fetches event records for the specified device by its ID.",
|
|
67
68
|
},
|
|
@@ -147,9 +148,10 @@ export const iotdeviceRouteSpecs: RouteSpec[] = [
|
|
|
147
148
|
requestSchema: apiStatusRequestSchema,
|
|
148
149
|
responseSchema: apiStatusSchema,
|
|
149
150
|
handler: getApiStatus,
|
|
151
|
+
privateDocs: true,
|
|
150
152
|
summary: "Get API status by kind",
|
|
151
153
|
description:
|
|
152
|
-
"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.",
|
|
154
|
+
"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.",
|
|
153
155
|
},
|
|
154
156
|
{
|
|
155
157
|
method: "get",
|
|
@@ -39,12 +39,17 @@ const iotDataClient = new IoTDataPlaneClient({
|
|
|
39
39
|
*/
|
|
40
40
|
export const getEvents = async (params: {
|
|
41
41
|
DeviceId: string;
|
|
42
|
-
DateStart
|
|
43
|
-
DateEnd
|
|
42
|
+
DateStart?: string;
|
|
43
|
+
DateEnd?: string;
|
|
44
44
|
TypeFilter?: string;
|
|
45
45
|
createdAt?: string;
|
|
46
46
|
}): Promise<any> => {
|
|
47
47
|
const accessToken = await getAuth0Token();
|
|
48
|
+
// Keep the requested start date, but never query events before the device existed.
|
|
49
|
+
const dateStart =
|
|
50
|
+
params.createdAt && new Date(params.DateStart) < new Date(params.createdAt)
|
|
51
|
+
? params.createdAt
|
|
52
|
+
: params.DateStart;
|
|
48
53
|
try {
|
|
49
54
|
const response: AxiosResponse = await axios.get(
|
|
50
55
|
`${process.env.IOT_API_URL}getevent`,
|
|
@@ -52,11 +57,7 @@ export const getEvents = async (params: {
|
|
|
52
57
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
53
58
|
params: {
|
|
54
59
|
DeviceId: params.DeviceId,
|
|
55
|
-
DateStart:
|
|
56
|
-
? params.DateStart
|
|
57
|
-
: params.DateStart < params.createdAt
|
|
58
|
-
? params.DateStart
|
|
59
|
-
: params.createdAt,
|
|
60
|
+
DateStart: dateStart,
|
|
60
61
|
DateEnd: params.DateEnd,
|
|
61
62
|
TypeFilter: params.TypeFilter,
|
|
62
63
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { objectId } from '../validations/custom.validation';
|
|
4
|
-
import { zGet, zObjectId, zPagination } from '../utils/zValidations';
|
|
4
|
+
import { zGet, zObjectId, zPagination, zTypeFilter } from '../utils/zValidations';
|
|
5
5
|
|
|
6
6
|
extendZodWithOpenApi(z);
|
|
7
7
|
|
|
@@ -31,9 +31,17 @@ export const getEventsSchema = {
|
|
|
31
31
|
deviceId: zObjectId.openapi({ description: 'Device ObjectId' }),
|
|
32
32
|
}),
|
|
33
33
|
query: z.object({
|
|
34
|
-
DateStart: z
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
DateStart: z
|
|
35
|
+
.string()
|
|
36
|
+
.datetime()
|
|
37
|
+
.openapi({ description: 'Start date (ISO‐string)', example: '2025-05-01T00:00:00Z' })
|
|
38
|
+
.optional(),
|
|
39
|
+
DateEnd: z
|
|
40
|
+
.string()
|
|
41
|
+
.datetime()
|
|
42
|
+
.openapi({ description: 'End date (ISO‐string)', example: '2025-05-31T23:59:59Z' })
|
|
43
|
+
.optional(),
|
|
44
|
+
TypeFilter: zTypeFilter.default(''),
|
|
37
45
|
}),
|
|
38
46
|
};
|
|
39
47
|
export const updateEntrySchema = {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import httpStatus from "http-status";
|
|
2
|
+
import ApiError from "../utils/ApiError";
|
|
3
|
+
import type { Request, Response, NextFunction } from "express";
|
|
4
|
+
|
|
5
|
+
const ROLES_CLAIM = "https://memo.wirewire.de/roles";
|
|
6
|
+
|
|
7
|
+
const isAdminOrSupport = (user: Record<string, any> | undefined): boolean => {
|
|
8
|
+
if (!user) return false;
|
|
9
|
+
|
|
10
|
+
const roles = user[ROLES_CLAIM];
|
|
11
|
+
|
|
12
|
+
return Array.isArray(roles)
|
|
13
|
+
? roles.includes("admin") || roles.includes("support")
|
|
14
|
+
: false;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const validateAdminOrSupport = async (
|
|
18
|
+
req: Request,
|
|
19
|
+
res: Response,
|
|
20
|
+
next: NextFunction,
|
|
21
|
+
): Promise<void> => {
|
|
22
|
+
if (isAdminOrSupport(req.auth)) {
|
|
23
|
+
next();
|
|
24
|
+
} else {
|
|
25
|
+
next(
|
|
26
|
+
new ApiError(
|
|
27
|
+
httpStatus.FORBIDDEN,
|
|
28
|
+
"User is not part of the admin or support group (validateAdminOrSupport)",
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { isAdminOrSupport, validateAdminOrSupport };
|
|
@@ -12,18 +12,49 @@ import { filterOptions } from "../utils/filterOptions.js";
|
|
|
12
12
|
|
|
13
13
|
const ObjectId = mongoose.Types.ObjectId;
|
|
14
14
|
|
|
15
|
+
export type CreateOrganizationOwnerUserParams = {
|
|
16
|
+
organization: any;
|
|
17
|
+
owner: string;
|
|
18
|
+
request: Request;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type CreateOrganizationOwnerUserHook = (
|
|
22
|
+
params: CreateOrganizationOwnerUserParams,
|
|
23
|
+
) => Record<string, any>;
|
|
24
|
+
|
|
25
|
+
let createOrganizationOwnerUserHook: CreateOrganizationOwnerUserHook | null =
|
|
26
|
+
null;
|
|
27
|
+
|
|
28
|
+
export const setCreateOrganizationOwnerUserHook = (
|
|
29
|
+
hook?: CreateOrganizationOwnerUserHook,
|
|
30
|
+
): void => {
|
|
31
|
+
createOrganizationOwnerUserHook = hook ?? null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const createOrganizationOwnerUserBody = (
|
|
35
|
+
params: CreateOrganizationOwnerUserParams,
|
|
36
|
+
) => ({
|
|
37
|
+
organization: params.organization._id,
|
|
38
|
+
owner: params.owner,
|
|
39
|
+
role: "admin",
|
|
40
|
+
status: "accept",
|
|
41
|
+
...(createOrganizationOwnerUserHook
|
|
42
|
+
? createOrganizationOwnerUserHook(params)
|
|
43
|
+
: {}),
|
|
44
|
+
});
|
|
45
|
+
|
|
15
46
|
export const createOrganization = catchAsync(
|
|
16
47
|
async (req: Request, res: Response): Promise<void> => {
|
|
17
48
|
const organization = await organizationsService.createOrganization(
|
|
18
49
|
req.body,
|
|
19
50
|
);
|
|
20
|
-
const user = await usersService.createUser(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
const user = await usersService.createUser(
|
|
52
|
+
createOrganizationOwnerUserBody({
|
|
53
|
+
organization,
|
|
54
|
+
owner: res.req.auth.sub,
|
|
55
|
+
request: req,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
27
58
|
res.status(httpStatus.CREATED).send(organization);
|
|
28
59
|
},
|
|
29
60
|
);
|
|
@@ -92,7 +92,9 @@ export const organizationsRouteSpecs: RouteSpec[] = [
|
|
|
92
92
|
responseSchema: organizationResponseSchema,
|
|
93
93
|
handler: updateOrganization,
|
|
94
94
|
summary: "Update an organization by ID",
|
|
95
|
-
|
|
95
|
+
privateDocs: true,
|
|
96
|
+
description:
|
|
97
|
+
"LEGACY: Updates the details of a specific organization by its ID.",
|
|
96
98
|
},
|
|
97
99
|
{
|
|
98
100
|
method: "delete",
|
package/src/users/users.model.ts
CHANGED
|
@@ -14,7 +14,6 @@ export interface IUser {
|
|
|
14
14
|
inviteCode?: string;
|
|
15
15
|
email?: string;
|
|
16
16
|
role: (typeof roles)[number];
|
|
17
|
-
category: string;
|
|
18
17
|
status?: string;
|
|
19
18
|
meta?: Record<string, any>;
|
|
20
19
|
}
|
|
@@ -27,7 +26,7 @@ export interface IUserModel extends Model<IUserDocument> {
|
|
|
27
26
|
isEmailTaken(email: string, excludeUserId?: Types.ObjectId): Promise<boolean>;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
const userSchema = new Schema<IUserDocument, IUserModel>(
|
|
29
|
+
export const userSchema = new Schema<IUserDocument, IUserModel>(
|
|
31
30
|
{
|
|
32
31
|
name: { type: String, trim: true },
|
|
33
32
|
avatar: { type: String },
|
|
@@ -37,7 +36,6 @@ const userSchema = new Schema<IUserDocument, IUserModel>(
|
|
|
37
36
|
inviteCode: { type: String },
|
|
38
37
|
email: { type: String },
|
|
39
38
|
role: { type: String, default: "patient" /* , enum: Object.keys(roles) */ },
|
|
40
|
-
category: { type: String, default: "patient" },
|
|
41
39
|
status: { type: String },
|
|
42
40
|
meta: { type: Schema.Types.Mixed },
|
|
43
41
|
},
|
|
@@ -59,6 +57,12 @@ userSchema.virtual("organizationData", {
|
|
|
59
57
|
userSchema.plugin(toJSON);
|
|
60
58
|
userSchema.plugin(paginate);
|
|
61
59
|
|
|
60
|
+
export const extendUserSchema = (
|
|
61
|
+
definition: Parameters<typeof userSchema.add>[0],
|
|
62
|
+
): void => {
|
|
63
|
+
userSchema.add(definition);
|
|
64
|
+
};
|
|
65
|
+
|
|
62
66
|
/**
|
|
63
67
|
* Check if email is taken
|
|
64
68
|
* @param {string} email - The user's email
|
package/src/users/users.route.ts
CHANGED
|
@@ -45,7 +45,6 @@ export const userRouteSpecs: RouteSpec[] = [
|
|
|
45
45
|
handler: userController.createUser,
|
|
46
46
|
summary: "Create a new user",
|
|
47
47
|
description: "Creates a new user within the current organization.",
|
|
48
|
-
memoOnly: true,
|
|
49
48
|
},
|
|
50
49
|
{
|
|
51
50
|
method: "get",
|
|
@@ -137,7 +136,9 @@ export const userRouteSpecs: RouteSpec[] = [
|
|
|
137
136
|
responseSchema: updateUserResponseSchema,
|
|
138
137
|
handler: userController.updateUser,
|
|
139
138
|
summary: "Update a user by ID",
|
|
140
|
-
|
|
139
|
+
privateDocs: true,
|
|
140
|
+
description:
|
|
141
|
+
"LEGACY: Replaces a user’s full record with the provided data. (Replaced by PATCH /:userId)",
|
|
141
142
|
},
|
|
142
143
|
{
|
|
143
144
|
method: "patch",
|
|
@@ -7,6 +7,7 @@ import ApiError from "../utils/ApiError.js";
|
|
|
7
7
|
import auth0Service from "../accounts/auth0.service";
|
|
8
8
|
import organizationsService from "../organizations/organizations.service";
|
|
9
9
|
import { sendEmail } from "../email/email.service";
|
|
10
|
+
import type { SendEmailParams } from "../email/email.service";
|
|
10
11
|
import i18n from "../i18n/i18n";
|
|
11
12
|
|
|
12
13
|
export type UpdateTimesByIdHook = (
|
|
@@ -21,6 +22,27 @@ export const setUpdateTimesByIdHook = (hook?: UpdateTimesByIdHook): void => {
|
|
|
21
22
|
updateTimesByIdHook = hook ?? null;
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
export type BuildInviteEmailParams = {
|
|
26
|
+
auth: { sub: string };
|
|
27
|
+
user: IUserDocument;
|
|
28
|
+
inviteCode: string;
|
|
29
|
+
email: string;
|
|
30
|
+
organization: any;
|
|
31
|
+
lng: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type BuildInviteEmailHook = (
|
|
35
|
+
params: BuildInviteEmailParams,
|
|
36
|
+
) => Partial<SendEmailParams>;
|
|
37
|
+
|
|
38
|
+
let buildInviteEmailHook: BuildInviteEmailHook | null = null;
|
|
39
|
+
|
|
40
|
+
export const setBuildInviteEmailHook = (
|
|
41
|
+
hook?: BuildInviteEmailHook,
|
|
42
|
+
): void => {
|
|
43
|
+
buildInviteEmailHook = hook ?? null;
|
|
44
|
+
};
|
|
45
|
+
|
|
24
46
|
/**
|
|
25
47
|
* Create a new user
|
|
26
48
|
*/
|
|
@@ -100,14 +122,13 @@ export const getByIdWithAuth0 = async (id: string): Promise<any | null> => {
|
|
|
100
122
|
return json;
|
|
101
123
|
};
|
|
102
124
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
category: string,
|
|
125
|
+
export const getUsersByAppField = async (
|
|
126
|
+
appName: string,
|
|
127
|
+
fieldName: string,
|
|
128
|
+
value: unknown,
|
|
108
129
|
organization?: string,
|
|
109
130
|
): Promise<IUserDocument[]> => {
|
|
110
|
-
const filter: any = {
|
|
131
|
+
const filter: any = { [`apps.${appName}.${fieldName}`]: value };
|
|
111
132
|
if (organization) filter.organization = organization;
|
|
112
133
|
return User.find(filter);
|
|
113
134
|
};
|
|
@@ -182,21 +203,35 @@ export const sendInviteEmail = async (params: {
|
|
|
182
203
|
const auth0User = await auth0Service.getUserById(auth.sub);
|
|
183
204
|
const lng = auth0User.data?.app_metadata?.language as string | "en";
|
|
184
205
|
|
|
185
|
-
const title = `${i18n.t("Invite to ", { lng })}${
|
|
186
|
-
organization.kind === "private-wirewire" ? "paperlesspaper" : "ANABOX smart"
|
|
187
|
-
}`;
|
|
188
206
|
const body = i18n.t(
|
|
189
207
|
"You have been invited to join the group. Click on the link to accept the invitation.",
|
|
190
208
|
{ lng },
|
|
191
209
|
);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
210
|
+
const baseEmail: SendEmailParams = {
|
|
211
|
+
title: `${i18n.t("Invite to ", { lng })}${
|
|
212
|
+
organization?.name || "Application"
|
|
213
|
+
}`,
|
|
195
214
|
body,
|
|
196
215
|
url: `/${user.organization}/invite/${inviteCode}`,
|
|
197
216
|
actionButtonText: "Accept invite",
|
|
198
|
-
domain:
|
|
217
|
+
domain: "web",
|
|
218
|
+
productName: organization?.name || "Application",
|
|
199
219
|
email,
|
|
220
|
+
lng,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
await sendEmail({
|
|
224
|
+
...baseEmail,
|
|
225
|
+
...(buildInviteEmailHook
|
|
226
|
+
? buildInviteEmailHook({
|
|
227
|
+
auth,
|
|
228
|
+
user,
|
|
229
|
+
inviteCode,
|
|
230
|
+
email,
|
|
231
|
+
organization,
|
|
232
|
+
lng,
|
|
233
|
+
})
|
|
234
|
+
: {}),
|
|
200
235
|
});
|
|
201
236
|
};
|
|
202
237
|
|
|
@@ -384,7 +419,7 @@ export default {
|
|
|
384
419
|
createCurrentUser,
|
|
385
420
|
getById,
|
|
386
421
|
getByIdWithAuth0,
|
|
387
|
-
|
|
422
|
+
getUsersByAppField,
|
|
388
423
|
getUsersByOrganization,
|
|
389
424
|
getUsersByOrganizationAndId,
|
|
390
425
|
getUsersByOwner,
|
|
@@ -407,6 +442,7 @@ export default {
|
|
|
407
442
|
populateAuth0User,
|
|
408
443
|
populateAuth0Users,
|
|
409
444
|
setUpdateTimesByIdHook,
|
|
445
|
+
setBuildInviteEmailHook,
|
|
410
446
|
};
|
|
411
447
|
|
|
412
448
|
export type UserService = typeof import("./users.service");
|