@internetderdinge/api 1.229.31 → 1.229.37
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 +1 -6
- package/dist/src/devices/devices.route.js +13 -13
- package/dist/src/devices/devices.validation.js +0 -47
- 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/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/package.json +5 -2
- package/scripts/release-and-sync-paperless.mjs +60 -28
- 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 +0 -12
- package/src/devices/devices.route.ts +13 -14
- package/src/devices/devices.validation.ts +0 -47
- package/src/email/email.service.ts +15 -7
- package/src/index.ts +5 -1
- package/src/iotdevice/iotdevice.route.ts +3 -1
- 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/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
|
@@ -112,53 +112,6 @@ export const createDeviceSchema = {
|
|
|
112
112
|
};
|
|
113
113
|
export const deleteDeviceSchema = zDelete("deviceId");
|
|
114
114
|
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
115
|
export const pingDeviceSchema = {
|
|
163
116
|
params: z.object({
|
|
164
117
|
deviceId: zObjectIdFor("deviceId").openapi({
|
|
@@ -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",
|
|
@@ -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");
|
|
@@ -14,37 +14,39 @@ import {
|
|
|
14
14
|
|
|
15
15
|
extendZodWithOpenApi(z);
|
|
16
16
|
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
email: z.string().email().optional().nullable().openapi({
|
|
31
|
-
example: "user@example.com",
|
|
32
|
-
description: "User email address",
|
|
33
|
-
}),
|
|
34
|
-
timezone: z.string().optional().openapi({
|
|
35
|
-
example: "Europe/Berlin",
|
|
36
|
-
description: "IANA timezone string",
|
|
37
|
-
}),
|
|
38
|
-
role: z.enum(["user", "admin", "patient", "onlyself"]).optional().openapi({
|
|
39
|
-
description: "Role assigned to the user",
|
|
17
|
+
export const userAppsSchema = z
|
|
18
|
+
.record(z.string(), z.unknown())
|
|
19
|
+
.optional()
|
|
20
|
+
.openapi({ description: "Application-specific user fields" });
|
|
21
|
+
|
|
22
|
+
export const createUserBodyShape = {
|
|
23
|
+
meta: z
|
|
24
|
+
.object({})
|
|
25
|
+
.passthrough()
|
|
26
|
+
.optional()
|
|
27
|
+
.openapi({
|
|
28
|
+
example: { key: "value" },
|
|
29
|
+
description: "Additional metadata for the user",
|
|
40
30
|
}),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
apps: userAppsSchema,
|
|
32
|
+
organization: zObjectId.openapi({
|
|
33
|
+
description: "Organization ObjectId",
|
|
34
|
+
}),
|
|
35
|
+
email: z.string().email().optional().nullable().openapi({
|
|
36
|
+
example: "user@example.com",
|
|
37
|
+
description: "User email address",
|
|
38
|
+
}),
|
|
39
|
+
timezone: z.string().optional().openapi({
|
|
40
|
+
example: "Europe/Berlin",
|
|
41
|
+
description: "IANA timezone string",
|
|
47
42
|
}),
|
|
43
|
+
role: z.enum(["user", "admin", "patient", "onlyself"]).optional().openapi({
|
|
44
|
+
description: "Role assigned to the user",
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const createUserSchema = {
|
|
49
|
+
body: z.object(createUserBodyShape),
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export const createCurrentUserSchema = createUserSchema;
|
|
@@ -69,39 +71,39 @@ export const getCurrentUserSchema = {
|
|
|
69
71
|
}),
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
export const updateUserBodyShape = {
|
|
75
|
+
name: z.string().optional().openapi({ description: "User full name" }),
|
|
76
|
+
timezone: z.string().optional().openapi({ description: "IANA timezone" }),
|
|
77
|
+
avatar: z.string().optional().openapi({ description: "Avatar URL" }),
|
|
78
|
+
meta: z
|
|
79
|
+
.object({})
|
|
80
|
+
.passthrough()
|
|
81
|
+
.optional()
|
|
82
|
+
.openapi({ description: "Additional metadata" }),
|
|
83
|
+
apps: userAppsSchema,
|
|
84
|
+
email: z
|
|
85
|
+
.string()
|
|
86
|
+
.email()
|
|
87
|
+
.nullable()
|
|
88
|
+
.optional()
|
|
89
|
+
.openapi({ description: "User email address" }),
|
|
90
|
+
role: z
|
|
91
|
+
.enum(["user", "admin", "patient", "onlyself"])
|
|
92
|
+
.optional()
|
|
93
|
+
.openapi({ description: "User role" }),
|
|
94
|
+
inviteCode: z
|
|
95
|
+
.string()
|
|
96
|
+
.nullable()
|
|
97
|
+
.optional()
|
|
98
|
+
.openapi({ description: "Invite code" }),
|
|
99
|
+
organization: zObjectId
|
|
100
|
+
.optional()
|
|
101
|
+
.openapi({ description: "Organization ObjectId" }),
|
|
102
|
+
};
|
|
103
|
+
|
|
72
104
|
export const updateUserSchema = {
|
|
73
105
|
...zUpdate("userId"),
|
|
74
|
-
body: zPatchBody(
|
|
75
|
-
name: z.string().optional().openapi({ description: "User full name" }),
|
|
76
|
-
timezone: z.string().optional().openapi({ description: "IANA timezone" }),
|
|
77
|
-
avatar: z.string().optional().openapi({ description: "Avatar URL" }),
|
|
78
|
-
meta: z
|
|
79
|
-
.object({})
|
|
80
|
-
.optional()
|
|
81
|
-
.openapi({ description: "Additional metadata" }),
|
|
82
|
-
category: z
|
|
83
|
-
.enum(["doctor", "nurse", "patient", "pharmacist", "relative"])
|
|
84
|
-
.optional()
|
|
85
|
-
.openapi({ description: "User category" }),
|
|
86
|
-
email: z
|
|
87
|
-
.string()
|
|
88
|
-
.email()
|
|
89
|
-
.nullable()
|
|
90
|
-
.optional()
|
|
91
|
-
.openapi({ description: "User email address" }),
|
|
92
|
-
role: z
|
|
93
|
-
.enum(["user", "admin", "patient", "onlyself"])
|
|
94
|
-
.optional()
|
|
95
|
-
.openapi({ description: "User role" }),
|
|
96
|
-
inviteCode: z
|
|
97
|
-
.string()
|
|
98
|
-
.nullable()
|
|
99
|
-
.optional()
|
|
100
|
-
.openapi({ description: "Invite code" }),
|
|
101
|
-
organization: zObjectId
|
|
102
|
-
.optional()
|
|
103
|
-
.openapi({ description: "Organization ObjectId" }),
|
|
104
|
-
}),
|
|
106
|
+
body: zPatchBody(updateUserBodyShape),
|
|
105
107
|
};
|
|
106
108
|
|
|
107
109
|
export const deleteUserSchema = zDelete("userId");
|
|
@@ -30,16 +30,25 @@ export type RouteSpec = {
|
|
|
30
30
|
summary: string;
|
|
31
31
|
description?: string;
|
|
32
32
|
privateDocs?: boolean;
|
|
33
|
-
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BuildRouterAndDocsOptions = {
|
|
36
|
+
routeSpecs?: (specs: RouteSpec[]) => RouteSpec[];
|
|
37
|
+
includeInDocs?: (spec: RouteSpec) => boolean;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
export default function buildAiRouterAndDocs(
|
|
37
41
|
router: Router,
|
|
38
|
-
routeSpecs:
|
|
42
|
+
routeSpecs: RouteSpec[],
|
|
39
43
|
basePath = "/",
|
|
40
44
|
tags: string[] = [],
|
|
45
|
+
options: BuildRouterAndDocsOptions = {},
|
|
41
46
|
) {
|
|
42
|
-
|
|
47
|
+
const effectiveRouteSpecs = options.routeSpecs
|
|
48
|
+
? options.routeSpecs(routeSpecs)
|
|
49
|
+
: routeSpecs;
|
|
50
|
+
|
|
51
|
+
effectiveRouteSpecs.forEach((spec) => {
|
|
43
52
|
const validate = spec.validate || [];
|
|
44
53
|
const routeMiddleware =
|
|
45
54
|
spec.validateWithRequestSchema ||
|
|
@@ -68,7 +77,7 @@ export default function buildAiRouterAndDocs(
|
|
|
68
77
|
spec.responseSchema &&
|
|
69
78
|
!hasRoleValidation(spec.validateWithRequestSchema || validate) &&
|
|
70
79
|
spec.privateDocs !== true &&
|
|
71
|
-
|
|
80
|
+
(options.includeInDocs ? options.includeInDocs(spec) : true)
|
|
72
81
|
) {
|
|
73
82
|
// collect all middleware fn names (falls back to '<anonymous>' if unnamed)
|
|
74
83
|
const middlewareNames = (spec.validateWithRequestSchema || validate).map(
|
|
@@ -96,7 +105,7 @@ export default function buildAiRouterAndDocs(
|
|
|
96
105
|
// (optionally) expose them as a custom extension instead:
|
|
97
106
|
"x-middlewares": middlewareNames,
|
|
98
107
|
|
|
99
|
-
security: [{ [
|
|
108
|
+
security: [{ [xApiKey.name]: [] }, { [bearerAuth.name]: [] }],
|
|
100
109
|
responses: {
|
|
101
110
|
200: {
|
|
102
111
|
description: "Object with user data.",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import httpStatus from "http-status";
|
|
2
|
-
import catchAsync from "../utils/catchAsync.js";
|
|
3
|
-
import pdfService from "./pdf.service.js";
|
|
4
|
-
import ApiError from "../utils/ApiError.js";
|
|
5
|
-
export const generatePdfFromUrl = catchAsync(async (req, res) => {
|
|
6
|
-
const fileName = "memo-print";
|
|
7
|
-
const authHeader = req.headers["authorization"];
|
|
8
|
-
if (!authHeader) {
|
|
9
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing Authorization header");
|
|
10
|
-
}
|
|
11
|
-
const token = authHeader.split(" ")[1];
|
|
12
|
-
if (!token) {
|
|
13
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing bearer token");
|
|
14
|
-
}
|
|
15
|
-
const urlPath = typeof req.query.urlPath === "string" ? req.query.urlPath : undefined;
|
|
16
|
-
if (!urlPath) {
|
|
17
|
-
throw new ApiError(httpStatus.BAD_REQUEST, "Missing urlPath query parameter");
|
|
18
|
-
}
|
|
19
|
-
const result = await pdfService.generatePdfFromUrl({ urlPath, token });
|
|
20
|
-
res.status(httpStatus.CREATED).send({ signed: result });
|
|
21
|
-
});
|
|
22
|
-
export default {
|
|
23
|
-
generatePdfFromUrl,
|
|
24
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Router } from "express";
|
|
2
|
-
import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
|
|
3
|
-
import { generatePdfSchema } from "./pdf.validation.js";
|
|
4
|
-
import { pdfResponseSchema } from "./pdf.schemas.js";
|
|
5
|
-
import { generatePdfFromUrl } from "./pdf.controller.js";
|
|
6
|
-
import auth from "../middlewares/auth.js";
|
|
7
|
-
export const pdfRouteSpecs = [
|
|
8
|
-
{
|
|
9
|
-
method: "get",
|
|
10
|
-
path: "/",
|
|
11
|
-
validate: [auth("manageUsers")],
|
|
12
|
-
requestSchema: generatePdfSchema,
|
|
13
|
-
responseSchema: pdfResponseSchema,
|
|
14
|
-
handler: generatePdfFromUrl,
|
|
15
|
-
summary: "Generate a PDF from a provided URL",
|
|
16
|
-
description: "This endpoint allows users to generate a PDF document from a specified URL.",
|
|
17
|
-
memoOnly: true,
|
|
18
|
-
},
|
|
19
|
-
];
|
|
20
|
-
const router = Router();
|
|
21
|
-
buildRouterAndDocs(router, pdfRouteSpecs, "/pdf", ["PDF"]);
|
|
22
|
-
export default router;
|