@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,77 @@
1
+ # GitHub Copilot Instructions
2
+
3
+ ## Project Overview
4
+
5
+ This is a NodeJS/Express API codebase (part of a monorepo workspace) using TypeScript (ESM). It serves as a shared library of API modules (`@internetderdinge/api`) used by other applications.
6
+
7
+ ## Architecture & Code Organization
8
+
9
+ - **Pattern**: Modular, feature-based architecture. Each domain entity (Users, Devices, etc.) has its own folder containing:
10
+ - `*.route.ts`: Router definition and OpenAPI/Swagger configuration.
11
+ - `*.controller.ts`: Request parsing and response handling.
12
+ - `*.service.ts`: Business logic and database interactions.
13
+ - `*.model.ts`: Mongoose schema and interface definitions.
14
+ - `*.validation.ts`: Zod schemas for request validation (`body`, `query`, `params`).
15
+ - `*.schemas.ts`: Zod schemas for response objects (OpenAPI definitions).
16
+
17
+ - **Data Access**: MongoDB with Mongoose.
18
+ - **Validation**: Zod (preferred) for both runtime validation and OpenAPI generation. Joi is present but legacy.
19
+
20
+ ## Key Development Patterns
21
+
22
+ ### 1. Route Definitions
23
+
24
+ Routes are defined declaratively using a `RouteSpec` array and `buildRouterAndDocs`. **Do not** simply write `router.get(...)`.
25
+
26
+ - Define an array of `RouteSpec` objects.
27
+ - Include `method`, `path`, `validate` (middleware), `requestSchema` (validation), `responseSchema` (docs), and `handler`.
28
+ - Bind the router at the end of the file: `buildRouterAndDocs(router, routeSpecs, ...)`
29
+
30
+ ```typescript
31
+ // Example from users.route.ts
32
+ export const userRouteSpecs: RouteSpec[] = [
33
+ {
34
+ method: "post",
35
+ path: "/",
36
+ validate: [auth("manageUsers"), validateBodyOrganization],
37
+ requestSchema: createUserSchema,
38
+ responseSchema: createUserResponseSchema,
39
+ handler: userController.createUser,
40
+ summary: "...",
41
+ },
42
+ ];
43
+ ```
44
+
45
+ ### 2. Request Validation
46
+
47
+ Use the `validateZod` middleware. Define schemas in `*.validation.ts`.
48
+
49
+ - Export an object matching `{ body: z.object(...), query: ..., params: ... }`.
50
+ - Use custom Zod helpers from `../utils/zValidations.js` (e.g., `zObjectId`, `zPagination`).
51
+
52
+ ### 3. Database & Models
53
+
54
+ - Interfaces: Define `I[Entity]`, `I[Entity]Document` (extends `I[Entity], Document`), and `I[Entity]Model` (extends `Model<I[Entity]Document>`).
55
+ - Plugins: Always apply `toJSON` and `paginate` plugins in the schema.
56
+ - Reference: `src/users/users.model.ts` is the canonical example.
57
+
58
+ ### 4. Controllers & Async
59
+
60
+ - Always wrap controller functions with `catchAsync`.
61
+ - Do not use `try/catch` blocks inside controllers unless handling specific non-fatal errors; let global error handler catch exceptions.
62
+ - Throw `ApiError` for known application errors: `throw new ApiError(httpStatus.NOT_FOUND, 'User not found');`.
63
+
64
+ ## Tech Stack & Libraries
65
+
66
+ - **Runtime**: Node.js >= 24
67
+ - **Framework**: Express v5
68
+ - **ORM**: Mongoose
69
+ - **Validation**: Zod (with `@asteasolutions/zod-to-openapi`)
70
+ - **Main Dependencies**: `http-status`, `winston`, `auth0`, `googleapis`.
71
+ - **Testing**: `vitest`.
72
+
73
+ ## Specific Conventions
74
+
75
+ - **Naming**: camelCase for files and functions.
76
+ - **Imports**: Use `.js` extension for local imports (ESM requirement in TypeScript).
77
+ - **Environment**: Configuration via `dotenv` and `checkJwt` for Auth0.
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ## [1.224.2](https://dev.azure.com/wirewire/memo/_git/memo-mono/compare/v1.224.1...v1.224.2) (2026-02-07)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **iot:** updated private ([560732f](https://dev.azure.com/wirewire/memo/_git/memo-mono/commits/560732fe697e55ff5496801cff4d3d7aff0df4b0))
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @wirewire/openiot-api
2
+
3
+ Shared OpenIoT API modules used by IoT hardware. This package provides prebuilt Express routers and shared middleware/models for OpenIoT-related features.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js >= 24
8
+ - Yarn (workspace dependency management)
9
+
10
+ ## Install (workspace)
11
+
12
+ From the repo root:
13
+
14
+ - `yarn install`
15
+
16
+ ## Build
17
+
18
+ From the repo root:
19
+
20
+ - `yarn --cwd packages/openiot-api build`
21
+
22
+ ## Lint
23
+
24
+ From the repo root:
25
+
26
+ - `yarn --cwd packages/openiot-api lint`
27
+
28
+ ## Usage
29
+
30
+ This package is ESM (`"type": "module"`). Import the routers and mount them in an Express app:
31
+
32
+ - `usersRoute`
33
+ - `accountsRoute`
34
+ - `organizationsRoute`
35
+ - `devicesRoute`
36
+ - `devicesNotificationsRoute`
37
+ - `pdfRoute`
38
+ - `tokensRoute`
39
+
40
+ Example:
41
+
42
+ - `import { usersRoute } from "@wirewire/openiot-api";`
43
+ - `app.use("/users", usersRoute);`
44
+
45
+ ## Source layout
46
+
47
+ - `src/*`: Routers, middleware, models, validation, utils
48
+ - `src/index.ts`: Public exports
49
+
50
+ ## Notes
51
+
52
+ - This is a private workspace package and is not published publicly.
package/package.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "@internetderdinge/api",
3
+ "version": "1.224.2",
4
+ "description": "Shared OpenIoT API modules",
5
+ "main": "src/index.ts",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=24.0.0"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "lint": "eslint .",
13
+ "lint:fix": "eslint . --fix"
14
+ },
15
+ "dependencies": {
16
+ "@asteasolutions/zod-to-openapi": "^7.3.2",
17
+ "@aws-sdk/client-cloudwatch-events": "^3.216.0",
18
+ "@aws-sdk/client-s3": "^3.216.0",
19
+ "@aws-sdk/client-sesv2": "^3.328.0",
20
+ "@aws-sdk/credential-provider-node": "^3.830.0",
21
+ "@aws-sdk/lib-storage": "^3.216.0",
22
+ "@aws-sdk/s3-request-presigner": "^3.216.0",
23
+ "@aws-sdk/util-endpoints": "^3.216.0",
24
+ "@sentry/node": "^10.25.0",
25
+ "@sentry/profiling-node": "^10.25.0",
26
+ "@wirewire/fhir": "^1.212.0",
27
+ "@wirewire/fhir-helpers": "^1.212.0",
28
+ "@wirewire/helpers": "^1.223.0",
29
+ "agenda": "^5.0.0",
30
+ "agendash": "^4.0.0",
31
+ "auth0": "^4.23.1",
32
+ "aws-crt": "^1.27.3",
33
+ "aws-iot-device-sdk-v2": "^1.22.0",
34
+ "aws-sdk": "^2.1261.0",
35
+ "aws4": "^1.11.0",
36
+ "axios": "^1.9.0",
37
+ "body-parser": "~1.15.0",
38
+ "body-parser-xml": "~1.1.0",
39
+ "canvas": "^3.1.0",
40
+ "compression": "^1.8.0",
41
+ "config": "^4.0.0",
42
+ "cors": "^2.8.5",
43
+ "cron": "^4.3.1",
44
+ "date-fns": "^2.28.0",
45
+ "date-fns-tz": "2.0.0",
46
+ "dotenv": "^10.0.0",
47
+ "elevenlabs": "^0.5.0",
48
+ "express": "^5.1.0",
49
+ "express-jwt": "^8.5.1",
50
+ "express-mongo-sanitize": "^2.2.0",
51
+ "express-rate-limit": "^7.5.0",
52
+ "express-ws": "^5.0.2",
53
+ "firebase-admin": "^11.0.0",
54
+ "googleapis": "^144.0.0",
55
+ "he": "^1.2.0",
56
+ "helmet": "^4.1.0",
57
+ "http-status": "^1.4.0",
58
+ "https": "^1.0.0",
59
+ "i18next": "^23.10.1",
60
+ "joi": "^17.3.0",
61
+ "js-yaml": "^3.8.4",
62
+ "jsonwebtoken": "^8.5.1",
63
+ "jwks-rsa": "^2.0.3",
64
+ "moment": "^2.24.0",
65
+ "moment-timezone": "^0.5.34",
66
+ "mongoose": "^8.15.1",
67
+ "morgan": "^1.9.1",
68
+ "multer": "^1.4.5-lts.1",
69
+ "multer-s3": "^3.0.1",
70
+ "multiparty": "~4.1.2",
71
+ "node-uuid": "~1.4.7",
72
+ "nodemailer": "^6.3.1",
73
+ "openai": "^5.0.1",
74
+ "passport": "^0.4.0",
75
+ "passport-jwt": "^4.0.0",
76
+ "path": "~0.12.7",
77
+ "pixelmatch": "^7.1.0",
78
+ "promise": "~8.3.0",
79
+ "puppeteer": "^24.9.0",
80
+ "qs": "^6.14.0",
81
+ "request": "^2.81.0",
82
+ "rrule": "^2.7.1",
83
+ "sharp": "^0.33.5",
84
+ "stripe": "^12.6.0",
85
+ "swagger-ui-express": "^5.0.1",
86
+ "ts-node": "^10.9.2",
87
+ "typescript": "^5.8.3",
88
+ "validator": "^13.0.0",
89
+ "winston": "^3.2.1",
90
+ "xss-clean": "^0.1.4",
91
+ "zod": "^3.20.2"
92
+ },
93
+ "devDependencies": {
94
+ "@vitest/coverage-v8": "^2.1.9",
95
+ "coveralls": "^3.1.1",
96
+ "eslint": "^7.0.0",
97
+ "eslint-config-airbnb-base": "^14.0.0",
98
+ "eslint-config-prettier": "^8.1.0",
99
+ "eslint-plugin-import": "^2.18.2",
100
+ "eslint-plugin-prettier": "^3.1.1",
101
+ "eslint-plugin-security": "^1.4.0",
102
+ "faker": "^5.1.0",
103
+ "husky": "^5.1.2",
104
+ "lint-staged": "^10.0.7",
105
+ "node-mocks-http": "^1.8.0",
106
+ "nodemon": "^3.1.10",
107
+ "prettier": "^3.5.3",
108
+ "supertest": "^7.1.1",
109
+ "vitest": "^2.1.9"
110
+ },
111
+ "gitHead": "9556e8e376045c1e532aded7ec7132818190fa91"
112
+ }
@@ -0,0 +1,166 @@
1
+ import type { Request, Response } from "express";
2
+ import httpStatus from "http-status";
3
+ import deviceNotifications from "../devicesNotifications/devicesNotifications.service";
4
+ import ApiError from "../utils/ApiError.js";
5
+ import catchAsync from "../utils/catchAsync.js";
6
+ import * as accountsService from "./accounts.service.js";
7
+
8
+ interface AuthenticatedRequest extends Request {
9
+ auth: {
10
+ sub: string;
11
+ };
12
+ }
13
+
14
+ interface Device {
15
+ fck: string;
16
+ }
17
+
18
+ interface UpdateBody {
19
+ given_name?: string;
20
+ family_name?: string;
21
+ email?: string;
22
+ notification?: any;
23
+ [key: string]: any;
24
+ }
25
+
26
+ const getAccountById = catchAsync(
27
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
28
+ const account = await accountsService.getAccountById(req.auth.sub);
29
+
30
+ const entryDeviceNotifications = await deviceNotifications.getByUser(
31
+ req.auth.sub,
32
+ );
33
+
34
+ if (!account) {
35
+ throw new ApiError(httpStatus.NOT_FOUND, "Account not found");
36
+ }
37
+ res.send({
38
+ ...account.data,
39
+ notification: entryDeviceNotifications?.settings,
40
+ });
41
+ },
42
+ );
43
+
44
+ const setDeviceToken = catchAsync(
45
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
46
+ const account = await accountsService.getAccountById(req.auth.sub);
47
+
48
+ const devices: Device[] = account.data.app_metadata.devices || [];
49
+ const alreadyExisting = devices.find((d) => d.fck === req.body.token);
50
+ if (!alreadyExisting) {
51
+ devices.push({ fck: req.body.token });
52
+ }
53
+ const update = {
54
+ ...account.app_metadata,
55
+ devices: devices.slice(Math.max(devices.length - 3, 1)),
56
+ };
57
+ const entry = await accountsService.updateMetaDataById(
58
+ req.auth.sub,
59
+ update,
60
+ );
61
+ res.send(entry);
62
+ },
63
+ );
64
+
65
+ const avatar = catchAsync(
66
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
67
+ const avatarImage = await auth0.avatar(req.auth.sub);
68
+ res.send(avatarImage);
69
+ },
70
+ );
71
+
72
+ const updateEntry = catchAsync(
73
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
74
+ const account = await accountsService.getAccountById(req.auth.sub);
75
+
76
+ const { isSocial } = account?.data.identities[0];
77
+
78
+ const {
79
+ given_name,
80
+ family_name,
81
+ email,
82
+ notification,
83
+ ...updateBody
84
+ }: UpdateBody = req.body;
85
+
86
+ const trimmedGivenName =
87
+ typeof given_name === "string" ? given_name.trim() : undefined;
88
+ const trimmedFamilyName =
89
+ typeof family_name === "string" ? family_name.trim() : undefined;
90
+ const hasGivenName = !!trimmedGivenName;
91
+ const hasFamilyName = !!trimmedFamilyName;
92
+
93
+ const update = isSocial
94
+ ? {
95
+ ...account.data.app_metadata,
96
+ ...updateBody,
97
+ ...(hasGivenName ? { first_name: trimmedGivenName } : {}),
98
+ ...(hasFamilyName ? { last_name: trimmedFamilyName } : {}),
99
+ }
100
+ : updateBody;
101
+ const entry = await accountsService.updateMetaDataById(
102
+ req.auth.sub,
103
+ update,
104
+ );
105
+
106
+ if (notification) {
107
+ await deviceNotifications.updateByUserId(req.auth.sub, {
108
+ settings: notification,
109
+ });
110
+ }
111
+
112
+ if (!isSocial && (hasGivenName || hasFamilyName || email)) {
113
+ try {
114
+ await accountsService.updateUserById(req.auth.sub, {
115
+ ...(hasGivenName ? { given_name: trimmedGivenName } : {}),
116
+ ...(hasFamilyName ? { family_name: trimmedFamilyName } : {}),
117
+ ...(email ? { email } : {}),
118
+ });
119
+ } catch (error: any) {
120
+ console.error("error", error.message);
121
+ throw new ApiError(httpStatus.CONFLICT, error.message);
122
+ }
123
+ }
124
+ res.send(entry);
125
+ },
126
+ );
127
+
128
+ const deleteCurrent = catchAsync(
129
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
130
+ const entry = await accountsService.deleteById(req.auth.sub);
131
+ res.send(entry);
132
+ },
133
+ );
134
+
135
+ const current = catchAsync(
136
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
137
+ const user = await accountsService.getAccountById(req.auth.sub);
138
+ res.send(user.data);
139
+ },
140
+ );
141
+
142
+ const mfaEnroll = catchAsync(
143
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
144
+ const { mfaEnroll } = req.body;
145
+ const user = await accountsService.mfaEnroll(req.auth.sub, mfaEnroll);
146
+ res.send(user);
147
+ },
148
+ );
149
+
150
+ const mfaDisable = catchAsync(
151
+ async (req: AuthenticatedRequest, res: Response): Promise<void> => {
152
+ const user = await accountsService.mfaDisable(req.auth.sub);
153
+ res.send(user);
154
+ },
155
+ );
156
+
157
+ export {
158
+ avatar,
159
+ getAccountById,
160
+ current,
161
+ setDeviceToken,
162
+ mfaEnroll,
163
+ updateEntry,
164
+ deleteCurrent,
165
+ mfaDisable,
166
+ };
@@ -0,0 +1,107 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import auth from "../middlewares/auth.js";
4
+ import { validateOrganization } from "../middlewares/validateOrganization.js";
5
+ import validateCurrentUser from "../middlewares/validateCurrentUser.js";
6
+ import type { RouteSpec } from "../types/routeSpec";
7
+ import * as accountsValidation from "./accounts.validation.js";
8
+ import * as accountsController from "./accounts.controller.js";
9
+ import { accountResponseSchema } from "./accounts.schemas.js";
10
+ import { validateParamsAccount } from "../middlewares/validateCurrentAuthUser.js";
11
+
12
+ export const accountsRouteSpecs: RouteSpec[] = [
13
+ {
14
+ method: "get",
15
+ path: "/current",
16
+ validate: [auth("manageUsers")],
17
+ requestSchema: accountsValidation.currentAccountSchema,
18
+ responseSchema: accountResponseSchema,
19
+ privateDocs: true,
20
+ handler: accountsController.current,
21
+ summary: "Get the current account",
22
+ },
23
+ {
24
+ method: "post",
25
+ path: "/current/mfa/enroll",
26
+ validate: [auth("manageUsers")],
27
+ requestSchema: accountsValidation.currentAccountMfaEnrollSchema,
28
+ responseSchema: accountResponseSchema,
29
+ privateDocs: true,
30
+ handler: accountsController.mfaEnroll,
31
+ summary: "Enroll current account in MFA",
32
+ },
33
+ {
34
+ method: "post",
35
+ path: "/current/mfa/disable",
36
+ validate: [auth("manageUsers")],
37
+ requestSchema: accountsValidation.currentAccountMfaEnrollSchema,
38
+ responseSchema: accountResponseSchema,
39
+ privateDocs: true,
40
+ handler: accountsController.mfaDisable,
41
+ summary: "Disable current account MFA",
42
+ },
43
+ {
44
+ method: "post",
45
+ path: "/:accountId/setDeviceToken",
46
+ validate: [auth("manageUsers"), validateParamsAccount],
47
+ requestSchema: accountsValidation.setDeviceTokenSchema,
48
+ responseSchema: accountResponseSchema,
49
+ privateDocs: true,
50
+ handler: accountsController.setDeviceToken,
51
+ summary: "Set a device token for an account",
52
+ },
53
+ {
54
+ method: "get",
55
+ path: "/:accountId/avatar.jpg",
56
+ validate: [auth("manageUsers")],
57
+ requestSchema: accountsValidation.getAvatarSchema,
58
+ responseSchema: accountResponseSchema, // or a binary/blob schema if you have one
59
+ privateDocs: true,
60
+ handler: accountsController.avatar,
61
+ summary: "Fetch account avatar",
62
+ },
63
+ {
64
+ method: "delete",
65
+ path: "/deleteCurrent",
66
+ validate: [auth("manageUsers")],
67
+ requestSchema: accountsValidation.deleteCurrentSchema,
68
+ responseSchema: accountResponseSchema,
69
+ privateDocs: true,
70
+ handler: accountsController.deleteCurrent,
71
+ summary: "Delete the current account",
72
+ },
73
+ {
74
+ method: "get",
75
+ path: "/:accountId",
76
+ validate: [auth("getUsers"), validateParamsAccount],
77
+ requestSchema: accountsValidation.getAccountSchema,
78
+ responseSchema: accountResponseSchema,
79
+ handler: accountsController.getAccountById,
80
+ summary: "Get an account by ID",
81
+ },
82
+ {
83
+ method: "post",
84
+ path: "/:accountId",
85
+ validate: [auth("manageUsers"), validateParamsAccount],
86
+ requestSchema: accountsValidation.updateAccountSchema,
87
+ responseSchema: accountResponseSchema,
88
+ privateDocs: true,
89
+ handler: accountsController.updateEntry,
90
+ summary: "Create or replace an account by ID",
91
+ },
92
+ {
93
+ method: "patch",
94
+ path: "/:accountId",
95
+ validate: [auth("manageUsers"), validateParamsAccount],
96
+ requestSchema: accountsValidation.updateAccountSchema,
97
+ responseSchema: accountResponseSchema,
98
+ privateDocs: true,
99
+ handler: accountsController.updateEntry,
100
+ summary: "Update fields on an account by ID",
101
+ },
102
+ ];
103
+
104
+ const router: Router = Router();
105
+ buildRouterAndDocs(router, accountsRouteSpecs, "/accounts", ["Accounts"]);
106
+
107
+ export default router;
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+
3
+ export const accountResponseSchema = z.object({
4
+ id: z.string().uuid(),
5
+ email: z.string().email(),
6
+ firstName: z.string().optional(),
7
+ lastName: z.string().optional(),
8
+ organizationId: z.string().uuid(),
9
+ roles: z.array(z.string()), // e.g. ['admin','user']
10
+ isMfaEnabled: z.boolean(),
11
+ createdAt: z.string(), // ISO timestamp
12
+ updatedAt: z.string(), // ISO timestamp
13
+ });
14
+
15
+ // If you ever need the TS type:
16
+ export type AccountResponse = z.infer<typeof accountResponseSchema>;
@@ -0,0 +1,85 @@
1
+ import { auth0, mfaDisableAccount, mfaEnrollAccount } from "./auth0.service.js";
2
+
3
+ type ObjectId = string; // Replace with the actual ObjectId type if available
4
+ type Stock = any; // Replace with the actual Stock type if available
5
+
6
+ /**
7
+ * Get user by id
8
+ * @param {ObjectId} id
9
+ * @returns {Promise<Stock>}
10
+ */
11
+ export const getAccountById = async (id: ObjectId): Promise<Stock> => {
12
+ return auth0.users.get({ id });
13
+ };
14
+
15
+ /**
16
+ * Get user by email
17
+ * @param {string} email
18
+ * @returns {Promise<Stock>}
19
+ */
20
+ /*
21
+ export const getAccountByEmail = async (email: string): Promise<Stock> => {
22
+ return auth0.getUsersByEmail(email); // Fixed incorrect variable `postID` to `email`
23
+ }; */
24
+
25
+ /**
26
+ * Enroll user in MFA
27
+ * @param {ObjectId} userId
28
+ * @param {string} mfaToken
29
+ * @returns {Promise<Stock>}
30
+ */
31
+ export const mfaEnroll = async (
32
+ userId: ObjectId,
33
+ mfaToken: string,
34
+ ): Promise<Stock> => {
35
+ const params = { id: userId };
36
+ const body = {
37
+ mfa_token: mfaToken,
38
+ };
39
+
40
+ return mfaEnrollAccount(userId, body);
41
+ };
42
+
43
+ export const mfaDisable = async (userId: ObjectId): Promise<Stock> => {
44
+ await mfaDisableAccount(userId);
45
+ return { success: true };
46
+ };
47
+
48
+ /**
49
+ * Update user metadata by id
50
+ */
51
+ export const updateMetaDataById = async (
52
+ id: ObjectId,
53
+ updateBody: Record<string, any>,
54
+ ): Promise<Stock> => {
55
+ // now use the generic update and pass app_metadata
56
+ return auth0.users.update({ id }, { app_metadata: updateBody });
57
+ };
58
+
59
+ /**
60
+ * Update user by id
61
+ */
62
+ export const updateUserById = async (
63
+ id: ObjectId,
64
+ updateBody: Record<string, any>,
65
+ ): Promise<Stock> => {
66
+ // switch to the v3 ManagementClient users.update
67
+ return auth0.users.update({ id }, updateBody);
68
+ };
69
+
70
+ /**
71
+ * Delete user by id
72
+ */
73
+ export const deleteById = async (userId: ObjectId): Promise<Stock> => {
74
+ return auth0.users.delete({ id: userId });
75
+ };
76
+
77
+ export default {
78
+ getAccountById,
79
+ // getAccountByEmail,
80
+ mfaEnroll,
81
+ mfaDisable,
82
+ updateMetaDataById,
83
+ updateUserById,
84
+ deleteById,
85
+ };