@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,54 @@
1
+ import httpStatus from "http-status";
2
+ import ApiError from "../utils/ApiError";
3
+ import devicesService from "../devices/devices.service";
4
+ import { isAdmin } from "./validateAdmin";
5
+
6
+ import type { Request, Response, NextFunction } from "express";
7
+ import type { Device } from "../devices/devices.types";
8
+ import type { User } from "../users/users.types";
9
+ import userService from "../users/users.service";
10
+
11
+ export const validateDeviceUserOrganization = async (
12
+ req: Request,
13
+ res: Response,
14
+ next: NextFunction,
15
+ ): Promise<void> => {
16
+ if (isAdmin(res.req.auth)) {
17
+ next();
18
+ return;
19
+ }
20
+
21
+ if (req.body.patient) {
22
+ const currentDevice: Device | null = await devicesService.getById(
23
+ req.params.deviceId,
24
+ );
25
+ if (!currentDevice) {
26
+ next(
27
+ new ApiError(
28
+ httpStatus.FORBIDDEN,
29
+ "Device was not found (validateDeviceUserOrganization)",
30
+ ),
31
+ );
32
+ return;
33
+ }
34
+
35
+ const currentUser: User | null =
36
+ await userService.getUsersByOrganizationAndId(
37
+ currentDevice.organization,
38
+ req.body.patient,
39
+ );
40
+ if (!currentUser) {
41
+ next(
42
+ new ApiError(
43
+ httpStatus.FORBIDDEN,
44
+ "User is not part of the organization which has access to the device (validateDeviceUserOrganization)",
45
+ ),
46
+ );
47
+ return;
48
+ }
49
+
50
+ req.currentUser = currentUser;
51
+ }
52
+
53
+ next();
54
+ };
@@ -0,0 +1,109 @@
1
+ import type { Request, Response, NextFunction } from "express";
2
+ import type { UserService } from "../users/users.service";
3
+ import userService from "../users/users.service";
4
+
5
+ import httpStatus from "http-status";
6
+ import ApiError from "../utils/ApiError";
7
+ import { isAdmin } from "./validateAdmin";
8
+
9
+ export const validateUser = async (
10
+ req: Request,
11
+ res: Response,
12
+ next: NextFunction,
13
+ ): Promise<void> => {
14
+ next();
15
+ };
16
+
17
+ export const validateOrganization = async (
18
+ req: Request,
19
+ res: Response,
20
+ next: NextFunction,
21
+ ): Promise<void> => {
22
+ if (isAdmin(res.req.auth)) {
23
+ next();
24
+ } else {
25
+ const currentUser = await userService.getUserByOwner(
26
+ res.req.auth.sub,
27
+ req.params.organizationId,
28
+ );
29
+ if (!currentUser) {
30
+ next(
31
+ new ApiError(
32
+ httpStatus.FORBIDDEN,
33
+ "User is not part of the organization (validateOrganization)",
34
+ ),
35
+ );
36
+ return;
37
+ }
38
+ /* if (currentUser.role && currentUser.role === 'onlyself') {
39
+ next(new ApiError(httpStatus.FORBIDDEN, 'User does not have sufficient permissions in the organization'));
40
+ return;
41
+ } */
42
+ req.currentUser = currentUser;
43
+ next();
44
+ }
45
+ };
46
+
47
+ export const validateQueryOrganization = async (
48
+ req: Request,
49
+ res: Response,
50
+ next: NextFunction,
51
+ ): Promise<void> => {
52
+ if (isAdmin(res.req.auth)) {
53
+ next();
54
+ } else {
55
+ const currentUser = await userService.getUserByOwner(
56
+ res.req.auth.sub,
57
+ req.query.organization,
58
+ );
59
+ if (!currentUser) {
60
+ next(
61
+ new ApiError(
62
+ httpStatus.FORBIDDEN,
63
+ "User is not part of the organization (validateQueryOrganization)",
64
+ ),
65
+ );
66
+ return;
67
+ }
68
+
69
+ /* if (currentUser.role && currentUser.role === 'onlyself') {
70
+ next(new ApiError(httpStatus.FORBIDDEN, 'User does not have sufficient permissions in the organization'));
71
+ return;
72
+ } */
73
+ req.currentUser = currentUser;
74
+ next();
75
+ }
76
+ };
77
+
78
+ export const validateBodyOrganization = async (
79
+ req: Request,
80
+ res: Response,
81
+ next: NextFunction,
82
+ ): Promise<void> => {
83
+ if (isAdmin(res.req.auth)) {
84
+ next();
85
+ } else {
86
+ const currentUser = await userService.getUserByOwner(
87
+ res.req.auth.sub,
88
+ req.body.organization,
89
+ );
90
+ if (!currentUser) {
91
+ next(
92
+ new ApiError(
93
+ httpStatus.FORBIDDEN,
94
+ "User is not part of the organization (validateBodyOrganization)",
95
+ ),
96
+ );
97
+ return;
98
+ }
99
+ req.currentUser = currentUser;
100
+ next();
101
+ }
102
+ };
103
+
104
+ export default {
105
+ validateOrganization,
106
+ validateQueryOrganization,
107
+ validateBodyOrganization,
108
+ validateUser,
109
+ };
@@ -0,0 +1,75 @@
1
+ import httpStatus from "http-status";
2
+ import ApiError from "../utils/ApiError";
3
+ import { isAdmin } from "./validateAdmin";
4
+ import type { Request, Response, NextFunction } from "express";
5
+ import userService from "../users/users.service";
6
+ import type { UserService } from "../users/users.service";
7
+
8
+ const validateQuerySearchUserAndOrganization = async (
9
+ req: Request,
10
+ res: Response,
11
+ next: NextFunction,
12
+ ): Promise<void> => {
13
+ if (isAdmin(res.req.auth)) {
14
+ next();
15
+ } else {
16
+ console.log("validateQuerySearchUserAndOrganization", req.query);
17
+ if (req.query.organization) {
18
+ const currentUser = await userService.getUserByOwner(
19
+ res.req.auth.sub,
20
+ req.query.organization as string,
21
+ );
22
+ if (!currentUser) {
23
+ next(
24
+ new ApiError(
25
+ httpStatus.FORBIDDEN,
26
+ "User is not part of the organization (validateQuerySearchUserAndOrganization)",
27
+ ),
28
+ );
29
+ return;
30
+ }
31
+ /* if (currentUser.role && currentUser.role === 'onlyself') {
32
+ next(new ApiError(httpStatus.FORBIDDEN, 'User does not have sufficient permissions in the organization'));
33
+ return;
34
+ } */
35
+ req.currentUser = currentUser;
36
+ next();
37
+ } else if (req.query.patient) {
38
+ const activeUser = await userService.getById(req.query.patient as string);
39
+ if (!activeUser) {
40
+ next(
41
+ new ApiError(
42
+ httpStatus.FORBIDDEN,
43
+ "User not found (validateQuerySearchUserAndOrganization)",
44
+ ),
45
+ );
46
+ return;
47
+ }
48
+ const currentUser = await userService.getUserByOwner(
49
+ res.req.auth.sub,
50
+ activeUser.organization,
51
+ );
52
+
53
+ if (!currentUser) {
54
+ next(
55
+ new ApiError(
56
+ httpStatus.FORBIDDEN,
57
+ "User is not part of the organization which has access to the device (validateDevice)",
58
+ ),
59
+ );
60
+ return;
61
+ }
62
+ req.currentUser = currentUser;
63
+ next();
64
+ } else {
65
+ next(
66
+ new ApiError(
67
+ httpStatus.FORBIDDEN,
68
+ "No filter defined (validateQuerySearchUserAndOrganization)",
69
+ ),
70
+ );
71
+ }
72
+ }
73
+ };
74
+
75
+ export { validateQuerySearchUserAndOrganization };
@@ -0,0 +1,36 @@
1
+ import httpStatus from "http-status";
2
+ import type { Request, Response, NextFunction } from "express";
3
+ import ApiError from "../utils/ApiError";
4
+ import { getTokenById } from "../tokens/tokens.service";
5
+
6
+ export async function validateParamsToken(
7
+ req: Request,
8
+ res: Response,
9
+ next: NextFunction,
10
+ ): Promise<void> {
11
+ // assume tokenId comes from req.params
12
+ const tokenId = req.params.tokenId as string | undefined;
13
+ if (!tokenId) {
14
+ return next(new ApiError(httpStatus.BAD_REQUEST, "Token ID is required"));
15
+ }
16
+
17
+ // fetch your token entity
18
+ const token = await getTokenById(tokenId);
19
+ if (!token) {
20
+ return next(new ApiError(httpStatus.NOT_FOUND, "Token not found"));
21
+ }
22
+
23
+ // compare owner vs. authenticated sub
24
+ if (token.owner !== res.req.auth.sub) {
25
+ return next(
26
+ new ApiError(
27
+ httpStatus.FORBIDDEN,
28
+ "You are not allowed to access this token",
29
+ ),
30
+ );
31
+ }
32
+
33
+ // attach for downstream handlers and continue
34
+ (req as any).token = token;
35
+ next();
36
+ }
@@ -0,0 +1,75 @@
1
+ import httpStatus from "http-status";
2
+ import ApiError from "../utils/ApiError";
3
+ import usersService from "../users/users.service";
4
+ import { isAdmin } from "./validateAdmin";
5
+
6
+ import type { Request, Response, NextFunction } from "express";
7
+ import type { User } from "../users/users.types";
8
+
9
+ // extend makeValidateUser to accept an optional key (defaulting to 'patient')
10
+ const makeValidateUser =
11
+ (source: "query" | "body" | "params", key: string = "patient") =>
12
+ async (req: Request, res: Response, next: NextFunction): Promise<void> => {
13
+ const context = `validate${source.charAt(0).toUpperCase() + source.slice(1)}User`;
14
+ if (isAdmin(res.req.auth)) {
15
+ return next();
16
+ }
17
+
18
+ // pull the configured key instead of hard‐coded 'patient'
19
+ const id = (req[source] as any)[key] as string | undefined;
20
+ if (!id) {
21
+ return next(new ApiError(httpStatus.FORBIDDEN, `No user (${context})`));
22
+ }
23
+
24
+ const patient: User | null = await usersService.getById(id);
25
+ if (!patient) {
26
+ return next(
27
+ new ApiError(
28
+ httpStatus.FORBIDDEN,
29
+ `User was with id ${id} not found (${context})`,
30
+ ),
31
+ );
32
+ }
33
+
34
+ const currentUser: User | null = await usersService.getUserByOwner(
35
+ res.req.auth.sub,
36
+ patient.organization,
37
+ );
38
+ if (!currentUser) {
39
+ return next(
40
+ new ApiError(
41
+ httpStatus.FORBIDDEN,
42
+ `Current user is not part of the organization as the requested user (${context})`,
43
+ ),
44
+ );
45
+ }
46
+
47
+ (req as any).currentUser = currentUser;
48
+ next();
49
+ };
50
+
51
+ // query and body still use the default 'patient' key
52
+ export function validateQueryUser(
53
+ req: Request,
54
+ res: Response,
55
+ next: NextFunction,
56
+ ): Promise<void> {
57
+ return makeValidateUser("query")(req, res, next);
58
+ }
59
+
60
+ export function validateBodyUser(
61
+ req: Request,
62
+ res: Response,
63
+ next: NextFunction,
64
+ ): Promise<void> {
65
+ return makeValidateUser("body")(req, res, next);
66
+ }
67
+
68
+ // params now pulls from req.params.userId
69
+ export function validateParamsUser(
70
+ req: Request,
71
+ res: Response,
72
+ next: NextFunction,
73
+ ): Promise<void> {
74
+ return makeValidateUser("params", "userId")(req, res, next);
75
+ }
@@ -0,0 +1,54 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { AnyZodObject } from 'zod';
3
+ import ApiError from '../utils/ApiError';
4
+ import httpStatus from 'http-status';
5
+ import z from 'zod';
6
+
7
+ interface Schema {
8
+ body?: AnyZodObject;
9
+ query?: AnyZodObject;
10
+ params?: AnyZodObject;
11
+ }
12
+
13
+ export const validateZod = (schema: Schema) => (req: Request, res: Response, next: NextFunction) => {
14
+ try {
15
+ schema.body ||= z.object({});
16
+ schema.query ||= z.object({});
17
+ schema.params ||= z.object({});
18
+
19
+ // 1) run safeParse on each
20
+ const result = {
21
+ body: schema.body.strict().safeParse(req.body || {}),
22
+ query: schema.query.strict().safeParse(req.query),
23
+ params: schema.params.strict().safeParse(req.params),
24
+ };
25
+
26
+ // 2) if any failure, short-circuit
27
+ if (!result.body.success || !result.query.success || !result.params.success) {
28
+ if (process.env.NODE_ENV === 'development') {
29
+ return res.status(400).send(result);
30
+ }
31
+ return next(new ApiError(httpStatus.BAD_REQUEST, 'Validation error'));
32
+ }
33
+
34
+ // 3) merge parsed data back in
35
+ req.body = result.body.data;
36
+ Object.assign(req.query as Record<string, any>, result.query.data);
37
+ Object.assign(req.params as Record<string, any>, result.params.data);
38
+
39
+ return next();
40
+ } catch (err: any) {
41
+ console.error('Zod validation error:', err);
42
+ return next(
43
+ new ApiError(
44
+ httpStatus.BAD_REQUEST,
45
+ 'Validation error',
46
+ undefined,
47
+ undefined,
48
+ process.env.NODE_ENV === 'development' ? err : undefined,
49
+ ),
50
+ );
51
+ }
52
+ };
53
+
54
+ export default validateZod;
@@ -0,0 +1,7 @@
1
+ import type { ToJSONPlugin } from './toJSON.plugin';
2
+ import type { PaginatePlugin } from './paginate.plugin';
3
+ // import type { PaginateNewPlugin } from './paginateNew.plugin';
4
+
5
+ export { default as toJSON } from './toJSON.plugin';
6
+ export { default as paginate } from './paginate.plugin';
7
+ // export { default as paginateNew } from './paginateNew.plugin';
@@ -0,0 +1,145 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import { Document, Model, Query, Types } from 'mongoose';
3
+
4
+ export interface QueryResult<T> {
5
+ results: T[];
6
+ page: number;
7
+ limit: number;
8
+ totalPages: number;
9
+ totalResults: number;
10
+ }
11
+
12
+ export interface PaginateOptions {
13
+ sortBy?: string;
14
+ populate?: string;
15
+ limit?: number;
16
+ page?: number;
17
+ fuzzySearch?: string;
18
+ // Add more options as needed
19
+ }
20
+
21
+ type PluginFunction = (query: Query<any, any>) => Query<any, any>;
22
+
23
+ function paginate<T extends Document>(schema: any) {
24
+ schema.statics.paginate = async function (
25
+ filter: Record<string, any>,
26
+ options: PaginateOptions = {},
27
+ plugin?: PluginFunction,
28
+ ): Promise<QueryResult<T>> {
29
+ let sort = '';
30
+ if (options.sortBy) {
31
+ const sortingCriteria: string[] = [];
32
+ options.sortBy.split(',').forEach((sortOption) => {
33
+ const [key, order] = sortOption.split(':');
34
+ sortingCriteria.push((order === 'desc' ? '-' : '') + key);
35
+ });
36
+ sort = sortingCriteria.join(' ');
37
+ } else {
38
+ sort = 'createdAt';
39
+ }
40
+
41
+ const limit = options.limit && parseInt(String(options.limit), 10) > 0 ? parseInt(String(options.limit), 10) : 10000;
42
+ const page = options.page && parseInt(String(options.page), 10) > 0 ? parseInt(String(options.page), 10) : 1;
43
+ const skip = (page - 1) * limit;
44
+
45
+ let results: any[] = [];
46
+ let totalResults = 0;
47
+ let totalPages = 0;
48
+
49
+ if (options.fuzzySearch && options.fuzzySearch.search) {
50
+ // Fuzzy search branch
51
+
52
+ const fuzzyFields = options.fuzzySearch.fields;
53
+ const mustClauses = Object.entries(filter).map(([key, value]) => {
54
+ if (typeof value === 'string' && value.match(/^[a-fA-F0-9]{24}$/)) {
55
+ return { equals: { path: key, value: new Types.ObjectId(value) } };
56
+ }
57
+ return { equals: { path: key, value } };
58
+ });
59
+ const pipeline = [
60
+ {
61
+ $search: {
62
+ index: options.fuzzySearch.index,
63
+ compound: {
64
+ must: mustClauses,
65
+ should: [
66
+ {
67
+ text: {
68
+ query: options.fuzzySearch.search,
69
+ path: fuzzyFields,
70
+ fuzzy: {},
71
+ },
72
+ },
73
+ ],
74
+ minimumShouldMatch: 1,
75
+ },
76
+ },
77
+ },
78
+ { $sort: { createdAt: -1 } },
79
+ { $skip: skip },
80
+ { $limit: limit },
81
+ {
82
+ $facet: {
83
+ results: [],
84
+ totalCount: [{ $count: 'count' }],
85
+ },
86
+ },
87
+ ];
88
+ const aggResult = await this.aggregate(pipeline).exec();
89
+ results = (aggResult[0]?.results || []).map((doc: any) => new this(doc));
90
+ totalResults = aggResult[0]?.totalCount[0]?.count || 0;
91
+ totalPages = Math.ceil(totalResults / limit);
92
+ } else {
93
+ // Regular find branch
94
+ const countPromise = this.countDocuments(filter).exec();
95
+
96
+ let docsPromise: any = this.find(filter).sort(sort).skip(skip).limit(limit);
97
+ if (options.populate) {
98
+ options.populate.split(',').forEach((populateOption) => {
99
+ docsPromise = docsPromise.populate(
100
+ populateOption
101
+ .split('.')
102
+ .reverse()
103
+ .reduce((a, b) => ({ path: b, populate: a })),
104
+ );
105
+ });
106
+ }
107
+ if (plugin) docsPromise = plugin(docsPromise);
108
+ docsPromise = docsPromise.exec();
109
+ const values = await Promise.all([countPromise, docsPromise]);
110
+ totalResults = values[0];
111
+ results = values[1];
112
+ totalPages = Math.ceil(totalResults / limit);
113
+ }
114
+
115
+ // Populate and plugin for both branches (if not already applied)
116
+ if (options.fuzzySearch && options.fuzzySearch.search) {
117
+ if (options.populate) {
118
+ results = await this.populate(
119
+ results,
120
+ options.populate.split(',').map((populateOption) =>
121
+ populateOption
122
+ .split('.')
123
+ .reverse()
124
+ .reduce((a, b) => ({ path: b, populate: a })),
125
+ ),
126
+ );
127
+ }
128
+ if (plugin) {
129
+ // plugin expects a Query, so wrap results in a Query if needed
130
+ // Not possible for array, so skip plugin for fuzzySearch unless you have a custom handler
131
+ }
132
+ }
133
+
134
+ const result: QueryResult<T> = {
135
+ results,
136
+ page,
137
+ limit,
138
+ totalPages,
139
+ totalResults,
140
+ };
141
+ return Promise.resolve(result);
142
+ };
143
+ }
144
+
145
+ export default paginate;