@internetderdinge/api 1.224.2

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 (102) hide show
  1. package/.github/copilot-instructions.md +77 -0
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +52 -0
  4. package/package.json +112 -0
  5. package/src/accounts/accounts.controller.ts +166 -0
  6. package/src/accounts/accounts.route.ts +107 -0
  7. package/src/accounts/accounts.schemas.ts +16 -0
  8. package/src/accounts/accounts.service.ts +85 -0
  9. package/src/accounts/accounts.validation.ts +118 -0
  10. package/src/accounts/auth0.service.ts +226 -0
  11. package/src/config/config.ts +49 -0
  12. package/src/config/logger.ts +33 -0
  13. package/src/config/morgan.ts +22 -0
  14. package/src/config/passport.cjs +30 -0
  15. package/src/config/roles.ts +13 -0
  16. package/src/config/tokens.cjs +10 -0
  17. package/src/devices/devices.controller.ts +276 -0
  18. package/src/devices/devices.model.ts +126 -0
  19. package/src/devices/devices.route.ts +198 -0
  20. package/src/devices/devices.schemas.ts +94 -0
  21. package/src/devices/devices.service.ts +320 -0
  22. package/src/devices/devices.validation.ts +221 -0
  23. package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
  24. package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
  25. package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
  26. package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
  27. package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
  28. package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
  29. package/src/email/email.service.ts +609 -0
  30. package/src/files/upload.service.ts +145 -0
  31. package/src/i18n/i18n.ts +51 -0
  32. package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
  33. package/src/index.ts +7 -0
  34. package/src/iotdevice/iotdevice.controller.ts +136 -0
  35. package/src/iotdevice/iotdevice.model.ts +32 -0
  36. package/src/iotdevice/iotdevice.route.ts +181 -0
  37. package/src/iotdevice/iotdevice.schemas.ts +79 -0
  38. package/src/iotdevice/iotdevice.service.ts +732 -0
  39. package/src/iotdevice/iotdevice.validation.ts +61 -0
  40. package/src/middlewares/auth.ts +110 -0
  41. package/src/middlewares/checkJwt.cjs +19 -0
  42. package/src/middlewares/error.js.legacy +44 -0
  43. package/src/middlewares/error.ts +41 -0
  44. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
  45. package/src/middlewares/rateLimiter.ts +10 -0
  46. package/src/middlewares/validate.ts +25 -0
  47. package/src/middlewares/validateAction.ts +41 -0
  48. package/src/middlewares/validateAdmin.ts +21 -0
  49. package/src/middlewares/validateAi.ts +24 -0
  50. package/src/middlewares/validateCurrentAuthUser.ts +23 -0
  51. package/src/middlewares/validateCurrentUser.ts +35 -0
  52. package/src/middlewares/validateDevice.ts +191 -0
  53. package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
  54. package/src/middlewares/validateOrganization.ts +109 -0
  55. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
  56. package/src/middlewares/validateTokens.ts +36 -0
  57. package/src/middlewares/validateUser.ts +75 -0
  58. package/src/middlewares/validateZod.ts +54 -0
  59. package/src/models/plugins/index.ts +7 -0
  60. package/src/models/plugins/paginate.plugin.ts +145 -0
  61. package/src/models/plugins/paginateNew.plugin.ts +206 -0
  62. package/src/models/plugins/simplePopulate.ts +12 -0
  63. package/src/models/plugins/toJSON.plugin.ts +51 -0
  64. package/src/organizations/organizations.controller.ts +101 -0
  65. package/src/organizations/organizations.model.ts +62 -0
  66. package/src/organizations/organizations.route.ts +119 -0
  67. package/src/organizations/organizations.schemas.ts +8 -0
  68. package/src/organizations/organizations.service.ts +85 -0
  69. package/src/organizations/organizations.validation.ts +76 -0
  70. package/src/pdf/pdf.controller.ts +18 -0
  71. package/src/pdf/pdf.route.ts +28 -0
  72. package/src/pdf/pdf.schemas.ts +7 -0
  73. package/src/pdf/pdf.service.ts +89 -0
  74. package/src/pdf/pdf.validation.ts +30 -0
  75. package/src/tokens/tokens.controller.ts +81 -0
  76. package/src/tokens/tokens.model.ts +24 -0
  77. package/src/tokens/tokens.route.ts +66 -0
  78. package/src/tokens/tokens.schemas.ts +15 -0
  79. package/src/tokens/tokens.service.ts +46 -0
  80. package/src/tokens/tokens.validation.ts +13 -0
  81. package/src/types/routeSpec.ts +1 -0
  82. package/src/users/users.controller.ts +234 -0
  83. package/src/users/users.model.ts +89 -0
  84. package/src/users/users.route.ts +171 -0
  85. package/src/users/users.schemas.ts +79 -0
  86. package/src/users/users.service.ts +393 -0
  87. package/src/users/users.validation.ts +166 -0
  88. package/src/utils/ApiError.ts +18 -0
  89. package/src/utils/buildRouterAndDocs.ts +85 -0
  90. package/src/utils/catchAsync.ts +9 -0
  91. package/src/utils/comparePapers.service.ts +48 -0
  92. package/src/utils/filterOptions.ts +37 -0
  93. package/src/utils/medicationName.ts +12 -0
  94. package/src/utils/pick.ts +16 -0
  95. package/src/utils/registerOpenApi.ts +32 -0
  96. package/src/utils/urlUtils.ts +14 -0
  97. package/src/utils/userName.ts +27 -0
  98. package/src/utils/zValidations.ts +89 -0
  99. package/src/validations/auth.validation.cjs +60 -0
  100. package/src/validations/custom.validation.ts +26 -0
  101. package/src/validations/index.cjs +2 -0
  102. package/tsconfig.json +22 -0
@@ -0,0 +1,234 @@
1
+ import httpStatus from "http-status";
2
+ import pick from "../utils/pick.js";
3
+ import ApiError from "../utils/ApiError.js";
4
+ import crypto from "crypto";
5
+ import catchAsync from "../utils/catchAsync.js";
6
+ import * as userService from "./users.service.js";
7
+ import * as devicesService from "../devices/devices.service";
8
+ // import userImageUpload from '../files/upload.service';
9
+ import auth0Service from "../accounts/auth0.service";
10
+ import { sendEmail } from "../email/email.service";
11
+ import type { Request, Response } from "express";
12
+
13
+ type AuthRequest<P = any, B = any, Q = any> = Request<P, any, B, Q> & {
14
+ auth: { sub: string };
15
+ };
16
+
17
+ export const createUser = catchAsync(
18
+ async (req: AuthRequest<{}, any, {}>, res: Response) => {
19
+ const { body } = req;
20
+ if (body.email) {
21
+ const auth0user = await auth0Service.getUserIdByEmail(body.email);
22
+ body.status = "invited";
23
+ body.inviteCode = crypto.randomBytes(48).toString("base64url");
24
+
25
+ if (auth0user.data[0]) {
26
+ const userFound = await userService.getUserByOwner(
27
+ auth0user.data[0].user_id,
28
+ body.organization,
29
+ );
30
+ if (userFound) {
31
+ throw new ApiError(
32
+ httpStatus.CONFLICT,
33
+ "User already added in organization",
34
+ );
35
+ }
36
+ }
37
+ }
38
+
39
+ const user = await userService.createUser(body);
40
+
41
+ if (body.email) {
42
+ await userService.sendInviteEmail({
43
+ user,
44
+ email: body.email,
45
+ inviteCode: body.inviteCode!,
46
+ auth: req.auth,
47
+ });
48
+ }
49
+
50
+ res.status(httpStatus.CREATED).send(user);
51
+ },
52
+ );
53
+
54
+ export const createCurrentUser = catchAsync(
55
+ async (req: AuthRequest, res: Response) => {
56
+ const user = await userService.createUser({ owner: req.auth.sub });
57
+ res.status(httpStatus.CREATED).send(user);
58
+ },
59
+ );
60
+
61
+ export const sendVerificationEmail = catchAsync(
62
+ async (req: AuthRequest, res: Response) => {
63
+ const result = await auth0Service.sendVerificationEmail(req.auth.sub);
64
+ res
65
+ .status(httpStatus.OK)
66
+ .send({ message: "Verification email sent", status: result?.status });
67
+ },
68
+ );
69
+
70
+ export const getUsers = catchAsync(async (req: Request, res: Response) => {
71
+ // console.log('getUsers', req.query);
72
+ const filter = pick(req.query, ["name", "role"]);
73
+ const options = pick(req.query, ["sortBy", "limit", "page"]);
74
+ options.fuzzySearch = req.query.search
75
+ ? {
76
+ search: req.query.search,
77
+ index: "users",
78
+ fields: ["meta.firstName", "meta.lastName"],
79
+ }
80
+ : undefined;
81
+ const result = await userService.queryUsers(
82
+ { ...filter, organization: req.query.organization },
83
+ options,
84
+ );
85
+ // console.log('getUsers result', req.currentUser);
86
+ if (
87
+ req.currentUser?.role &&
88
+ req.currentUser.role === "onlyself" &&
89
+ result?.results
90
+ ) {
91
+ // If the user is onlyself, restrict the results to their own user document
92
+ result.results = result.results.filter(
93
+ (user) => user.owner === req.auth.sub,
94
+ );
95
+ }
96
+ res.send(result);
97
+ });
98
+
99
+ export const getCurrentUser = catchAsync(
100
+ async (
101
+ req: AuthRequest<{}, any, { organization?: string }>,
102
+ res: Response,
103
+ ) => {
104
+ const result = await userService.getUserByOwner(
105
+ req.auth.sub,
106
+ req.query.organization,
107
+ );
108
+ res.send(result);
109
+ },
110
+ );
111
+
112
+ export const getUserImage = catchAsync(async (_req: Request, res: Response) => {
113
+ try {
114
+ // await userImageUpload.getPhoto(req.query.file, res);
115
+ } catch (error: any) {
116
+ res.status(500).json(`Failed to receive image file: ${error.message}`);
117
+ }
118
+ });
119
+
120
+ export const updateUserImage = catchAsync(
121
+ async (_req: Request, _res: Response) => {
122
+ // placeholder for image upload
123
+ },
124
+ );
125
+
126
+ export const getUser = catchAsync(
127
+ async (req: Request<{ userId: string }>, res: Response) => {
128
+ const user = await userService.getByIdWithAuth0(req.params.userId);
129
+ if (!user) {
130
+ throw new ApiError(
131
+ httpStatus.NOT_FOUND,
132
+ `User not found ID: ${req.params.userId}`,
133
+ );
134
+ }
135
+ res.send(user);
136
+ },
137
+ );
138
+
139
+ export const updateUser = catchAsync(
140
+ async (
141
+ req: Request<{ userId: string }, any, any, any> & { auth: any },
142
+ res: Response,
143
+ ) => {
144
+ const authReq = req as AuthRequest<{ userId: string }, any, any>;
145
+ const user = await userService.updateUserById(
146
+ authReq.params.userId,
147
+ authReq.body,
148
+ authReq.auth,
149
+ );
150
+ res.send(user);
151
+ },
152
+ );
153
+
154
+ export const deleteUser = catchAsync(
155
+ async (req: Request<{ userId: string }>, res: Response) => {
156
+ const devices = await devicesService.getDeviceByUserId(req.params.userId);
157
+ if (devices) {
158
+ throw new ApiError(
159
+ httpStatus.NOT_FOUND,
160
+ "User still has devices assigned",
161
+ );
162
+ }
163
+ const user = await userService.deleteUserById(req.params.userId);
164
+ res.send(user);
165
+ },
166
+ );
167
+
168
+ export const getInvite = catchAsync(
169
+ async (
170
+ req: Request<{ inviteCode: string }> & { auth: any },
171
+ res: Response,
172
+ ) => {
173
+ // console.log('getInvite', req.params.inviteCode);
174
+ const authReq = req as AuthRequest<{ inviteCode: string }>;
175
+ const user = await userService.getInvite({
176
+ inviteCode: authReq.params.inviteCode,
177
+ });
178
+ if (!user) {
179
+ throw new ApiError(
180
+ httpStatus.NOT_FOUND,
181
+ `User not found token: ${authReq.params.inviteCode}`,
182
+ );
183
+ }
184
+ const already = await userService.getUserByOwner(
185
+ authReq.auth.sub,
186
+ user.organization,
187
+ );
188
+ if (already) {
189
+ throw new ApiError(httpStatus.CONFLICT, "User already in organization");
190
+ }
191
+ res.send(user);
192
+ },
193
+ );
194
+
195
+ export const updateInvite = catchAsync(
196
+ async (req: AuthRequest<{}, any, any>, res: Response) => {
197
+ const user = await userService.updateInvite({
198
+ owner: req.auth.sub,
199
+ ...req.body,
200
+ });
201
+ if (!user) {
202
+ throw new ApiError(httpStatus.NOT_FOUND, "User not found");
203
+ }
204
+ res.send(user);
205
+ },
206
+ );
207
+
208
+ export const organizationUpdate = catchAsync(
209
+ async (req: Request<any, any, any>, res: Response) => {
210
+ const user = await userService.organizationUpdate(req.body);
211
+ res.send(user);
212
+ },
213
+ );
214
+
215
+ export const organizationRemove = catchAsync(
216
+ async (req: Request<any, any, any>, res: Response) => {
217
+ const user = await userService.organizationRemove(req.body);
218
+ res.send(user);
219
+ },
220
+ );
221
+
222
+ export const cleanup = catchAsync(async (_req: Request, res: Response) => {
223
+ const all = await userService.queryAllCalendars();
224
+ const filtered = all.filter((e) => e.organizationData === null);
225
+ const ids = filtered.map((e) => e._id);
226
+ const deleted = await userService.deleteMany(ids);
227
+ res.send({
228
+ deleted,
229
+ ids,
230
+ filteredCount: filtered.length,
231
+ totalCount: all.length,
232
+ filtered,
233
+ });
234
+ });
@@ -0,0 +1,89 @@
1
+ import mongoose, { Schema, Document, Model, Types } from "mongoose";
2
+ import validator from "validator";
3
+ import { toJSON, paginate } from "../models/plugins/index.js";
4
+ import { roles } from "../config/roles.js";
5
+
6
+ export interface IUser {
7
+ name?: string;
8
+ avatar?: string;
9
+ timezone: string;
10
+ owner?: string;
11
+ organization?: Types.ObjectId;
12
+ inviteCode?: string;
13
+ email?: string;
14
+ role: keyof typeof roles;
15
+ category: string;
16
+ status?: string;
17
+ meta?: Record<string, any>;
18
+ }
19
+
20
+ export interface IUserDocument extends IUser, Document {
21
+ isPasswordMatch(password: string): Promise<boolean>;
22
+ }
23
+
24
+ export interface IUserModel extends Model<IUserDocument> {
25
+ isEmailTaken(email: string, excludeUserId?: Types.ObjectId): Promise<boolean>;
26
+ }
27
+
28
+ const userSchema = new Schema<IUserDocument, IUserModel>(
29
+ {
30
+ name: { type: String, trim: true },
31
+ avatar: { type: String },
32
+ timezone: { type: String, default: "Europe/Berlin" },
33
+ owner: { type: String },
34
+ organization: { type: Schema.Types.ObjectId, ref: "User", immutable: true },
35
+ inviteCode: { type: String },
36
+ email: { type: String },
37
+ role: { type: String, default: "patient" /* , enum: Object.keys(roles) */ },
38
+ category: { type: String, default: "patient" },
39
+ status: { type: String },
40
+ meta: { type: Schema.Types.Mixed },
41
+ },
42
+ {
43
+ timestamps: true,
44
+ toObject: { virtuals: true },
45
+ toJSON: { virtuals: true },
46
+ },
47
+ );
48
+
49
+ userSchema.virtual("organizationData", {
50
+ ref: "Organization",
51
+ localField: "organization",
52
+ foreignField: "_id",
53
+ justOne: true,
54
+ });
55
+
56
+ // plugins
57
+ userSchema.plugin(toJSON);
58
+ userSchema.plugin(paginate);
59
+
60
+ /**
61
+ * Check if email is taken
62
+ * @param {string} email - The user's email
63
+ * @param {ObjectId} [excludeUserId] - The id of the user to be excluded
64
+ * @returns {Promise<boolean>}
65
+ */
66
+ userSchema.statics.isEmailTaken = async function (
67
+ email: string,
68
+ excludeUserId?: Types.ObjectId,
69
+ ): Promise<boolean> {
70
+ const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
71
+ return !!user;
72
+ };
73
+
74
+ /**
75
+ * Check if password matches the user's password
76
+ * @param {string} password
77
+ * @returns {Promise<boolean>}
78
+ */
79
+ userSchema.methods.isPasswordMatch = async function (
80
+ password: string,
81
+ ): Promise<boolean> {
82
+ // TODO: implement with bcrypt
83
+ return false;
84
+ };
85
+
86
+ export const User = mongoose.model<IUserDocument, IUserModel>(
87
+ "User",
88
+ userSchema,
89
+ );
@@ -0,0 +1,171 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import auth from "../middlewares/auth.js";
4
+ import validateZod from "../middlewares/validateZod.js";
5
+ import validateCurrentUser from "../middlewares/validateCurrentUser.js";
6
+ import {
7
+ validateQueryOrganization,
8
+ validateBodyOrganization,
9
+ validateUser,
10
+ } from "../middlewares/validateOrganization.js";
11
+ import { validateAdmin } from "../middlewares/validateAdmin.js";
12
+ import {
13
+ createCurrentUserSchema,
14
+ createUserSchema,
15
+ deleteUserSchema,
16
+ updateUserSchema,
17
+ getUserSchema,
18
+ getCurrentUserSchema,
19
+ queryUsersSchema,
20
+ updateInviteSchema,
21
+ validateGetInviteSchema,
22
+ sendVerificationEmailValidationSchema,
23
+ } from "./users.validation.js";
24
+ import * as userController from "./users.controller.js";
25
+ import type { RouteSpec } from "../types/routeSpec";
26
+ import {
27
+ createUserResponseSchema,
28
+ getUsersResponseSchema,
29
+ updateUserResponseSchema,
30
+ deleteUserResponseSchema,
31
+ } from "./users.schemas.js";
32
+ import {
33
+ validateParamsUser,
34
+ validateQueryUser,
35
+ } from "../middlewares/validateUser.js";
36
+ import { request } from "https";
37
+
38
+ export const userRouteSpecs: RouteSpec[] = [
39
+ {
40
+ method: "post",
41
+ path: "/",
42
+ validate: [auth("manageUsers"), validateBodyOrganization],
43
+ requestSchema: createUserSchema,
44
+ responseSchema: createUserResponseSchema,
45
+ handler: userController.createUser,
46
+ summary: "Create a new user",
47
+ description: "Creates a new user within the current organization.",
48
+ memoOnly: true,
49
+ },
50
+ {
51
+ method: "get",
52
+ path: "/",
53
+ validate: [auth("getUsers"), validateQueryOrganization],
54
+ requestSchema: queryUsersSchema,
55
+ responseSchema: getUsersResponseSchema,
56
+ handler: userController.getUsers,
57
+ summary: "Get a list of users",
58
+ description:
59
+ "Retrieves a paginated list of all users in the current organization.",
60
+ },
61
+ {
62
+ method: "get",
63
+ path: "/cleanup",
64
+ validate: [auth("cleanup"), validateAdmin],
65
+ handler: userController.cleanup,
66
+ summary: "Cleanup user data",
67
+ description: "Performs cleanup of stale or orphaned user records.",
68
+ },
69
+ {
70
+ method: "post",
71
+ path: "/current/send-verification-email",
72
+ validate: [auth("manageUsers")],
73
+ requestSchema: sendVerificationEmailValidationSchema,
74
+ responseSchema: getUsersResponseSchema,
75
+ handler: userController.sendVerificationEmail,
76
+ summary: "Send verification email to the current user",
77
+ description:
78
+ "Sends a new verification email to the authenticated user’s address.",
79
+ privateDocs: true,
80
+ },
81
+ {
82
+ method: "get",
83
+ path: "/current",
84
+ validate: [auth("getUsers"), validateQueryOrganization],
85
+ requestSchema: getCurrentUserSchema,
86
+ handler: userController.getCurrentUser,
87
+ summary: "Get the current user",
88
+ description: "Fetches details about the currently authenticated user.",
89
+ },
90
+ {
91
+ method: "post",
92
+ path: "/current",
93
+ validate: [auth("manageUsers")],
94
+ requestSchema: createCurrentUserSchema,
95
+ responseSchema: createUserResponseSchema,
96
+ handler: userController.createCurrentUser,
97
+ privateDocs: true,
98
+ summary: "Create the current user",
99
+ description: "Creates or initializes a profile for the authenticated user.",
100
+ },
101
+ {
102
+ method: "get",
103
+ path: "/invite/:inviteCode",
104
+ validate: [auth("getUsers")],
105
+ requestSchema: validateGetInviteSchema,
106
+ responseSchema: getUsersResponseSchema,
107
+ handler: userController.getInvite,
108
+ privateDocs: true,
109
+ summary: "Get invite details by code",
110
+ description: "Retrieves information about a pending invite using its code.",
111
+ privateDocs: true,
112
+ },
113
+ {
114
+ method: "post",
115
+ path: "/invite",
116
+ validate: [auth("getUsers")],
117
+ requestSchema: updateInviteSchema,
118
+ handler: userController.updateInvite,
119
+ privateDocs: true,
120
+ summary: "Update invite details",
121
+ description: "Modifies the details or status of an existing invite.",
122
+ },
123
+ {
124
+ method: "get",
125
+ path: "/:userId",
126
+ validate: [auth("getUsers"), validateParamsUser],
127
+ requestSchema: getUserSchema,
128
+ responseSchema: getUsersResponseSchema,
129
+ handler: userController.getUser,
130
+ summary: "Get a user by ID",
131
+ description: "Fetches a single user’s details by their unique ID.",
132
+ },
133
+ {
134
+ method: "post",
135
+ path: "/:userId",
136
+ validate: [auth("manageUsers"), validateParamsUser],
137
+ requestSchema: updateUserSchema,
138
+ responseSchema: updateUserResponseSchema,
139
+ handler: userController.updateUser,
140
+ summary: "Update a user by ID",
141
+ description: "Replaces a user’s full record with the provided data.",
142
+ memoOnly: true,
143
+ },
144
+ {
145
+ method: "patch",
146
+ path: "/:userId",
147
+ validate: [auth("manageUsers"), validateParamsUser],
148
+ requestSchema: updateUserSchema,
149
+ responseSchema: updateUserResponseSchema,
150
+ handler: userController.updateUser,
151
+ summary: "Partially update a user by ID",
152
+ description: "Applies partial updates to a user’s record by ID.",
153
+ memoOnly: true,
154
+ },
155
+ {
156
+ method: "delete",
157
+ path: "/:userId",
158
+ validate: [auth("manageUsers"), validateParamsUser],
159
+ requestSchema: deleteUserSchema,
160
+ responseSchema: deleteUserResponseSchema,
161
+ handler: userController.deleteUser,
162
+ summary: "Delete a user by ID",
163
+ description: "Removes a user from the system by their unique ID.",
164
+ },
165
+ ];
166
+
167
+ const router: Router = Router();
168
+
169
+ buildRouterAndDocs(router, userRouteSpecs, "/users", ["Users"]);
170
+
171
+ export default router;
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
3
+ extendZodWithOpenApi(z);
4
+
5
+ export const createUserResponseSchema = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ email: z.string(),
9
+ });
10
+
11
+ export const getUsersResponseSchema = z.array(
12
+ z.object({
13
+ id: z.string(),
14
+ name: z.string(),
15
+ email: z.string(),
16
+ })
17
+ );
18
+
19
+ export const updateUserSchema = z.object({
20
+ name: z.string().optional(),
21
+ email: z.string().email().optional(),
22
+ });
23
+
24
+ export const updateUserResponseSchema = z.object({
25
+ id: z.string(),
26
+ name: z.string(),
27
+ email: z.string(),
28
+ });
29
+
30
+ export const deleteUserResponseSchema = z.object({
31
+ success: z.boolean(),
32
+ });
33
+
34
+ export const updateTimesByIdResponseSchema = z
35
+ .array(
36
+ z.object({
37
+ rrule: z
38
+ .object({
39
+ freq: z.string().optional().openapi({ example: 'DAILY', description: 'Recurrence frequency' }),
40
+ byweekday: z
41
+ .array(z.number())
42
+ .openapi({ example: [0, 1, 2, 6, 3], description: 'Days of week to repeat (0=Sunday)' }),
43
+ exclude: z.array(z.string()).openapi({ example: ['2024-03-28T10:45:00.000Z'], description: 'Dates to skip' }),
44
+ })
45
+ .openapi({ description: 'Recurrence rule object' }),
46
+ medication: z.string().optional().openapi({ example: '6152c5f3902e7f91374d9f75', description: 'Medication ObjectId' }),
47
+ patient: z.string().openapi({ example: '614fb1d709dd9f6de85d6374', description: 'Patient ObjectId' }),
48
+ date: z.string().openapi({ example: '2024-03-25T00:30:00.000Z', description: 'Scheduled date/time (ISO)' }),
49
+ timeCategory: z.string().openapi({ example: 'noon', description: 'Time category (e.g. morning, noon)' }),
50
+ amount: z.number().openapi({ example: 1, description: 'Dosage amount' }),
51
+ emptyStomach: z.boolean().openapi({ example: false, description: 'Whether to take on empty stomach' }),
52
+ instruction: z.string().optional().openapi({ example: '', description: 'Additional instructions' }),
53
+ unit: z.string().optional().openapi({ example: 'St', description: 'Dosage unit' }),
54
+ bake: z.boolean().openapi({ example: false, description: 'Baking flag (if applicable)' }),
55
+ id: z.string().openapi({ example: '660079fd11fdc2dd935e43af', description: 'Entry identifier' }),
56
+ })
57
+ )
58
+ .openapi({
59
+ example: [
60
+ {
61
+ rrule: {
62
+ freq: 'DAILY',
63
+ byweekday: [0, 1, 2, 6, 3],
64
+ exclude: ['2024-03-28T10:45:00.000Z'],
65
+ },
66
+ patient: '614fb1d709dd9f6de85d6374',
67
+ date: '2024-03-25T00:30:00.000Z',
68
+ timeCategory: 'noon',
69
+ amount: 1,
70
+ emptyStomach: false,
71
+ instruction: '',
72
+ unit: 'St',
73
+ bake: false,
74
+ id: '660079fd11fdc2dd935e43af',
75
+ },
76
+ // ...other items
77
+ ],
78
+ description: 'Array of updated time entries by ID',
79
+ });