@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.
- package/.github/copilot-instructions.md +77 -0
- package/CHANGELOG.md +11 -0
- package/README.md +52 -0
- package/package.json +112 -0
- package/src/accounts/accounts.controller.ts +166 -0
- package/src/accounts/accounts.route.ts +107 -0
- package/src/accounts/accounts.schemas.ts +16 -0
- package/src/accounts/accounts.service.ts +85 -0
- package/src/accounts/accounts.validation.ts +118 -0
- package/src/accounts/auth0.service.ts +226 -0
- package/src/config/config.ts +49 -0
- package/src/config/logger.ts +33 -0
- package/src/config/morgan.ts +22 -0
- package/src/config/passport.cjs +30 -0
- package/src/config/roles.ts +13 -0
- package/src/config/tokens.cjs +10 -0
- package/src/devices/devices.controller.ts +276 -0
- package/src/devices/devices.model.ts +126 -0
- package/src/devices/devices.route.ts +198 -0
- package/src/devices/devices.schemas.ts +94 -0
- package/src/devices/devices.service.ts +320 -0
- package/src/devices/devices.validation.ts +221 -0
- package/src/devicesNotifications/devicesNotifications.controller.ts +72 -0
- package/src/devicesNotifications/devicesNotifications.model.ts +67 -0
- package/src/devicesNotifications/devicesNotifications.route.ts +150 -0
- package/src/devicesNotifications/devicesNotifications.schemas.ts +11 -0
- package/src/devicesNotifications/devicesNotifications.service.ts +222 -0
- package/src/devicesNotifications/devicesNotifications.validation.ts +56 -0
- package/src/email/email.service.ts +609 -0
- package/src/files/upload.service.ts +145 -0
- package/src/i18n/i18n.ts +51 -0
- package/src/i18n/saveMissingLocalJsonBackend.ts +92 -0
- package/src/index.ts +7 -0
- package/src/iotdevice/iotdevice.controller.ts +136 -0
- package/src/iotdevice/iotdevice.model.ts +32 -0
- package/src/iotdevice/iotdevice.route.ts +181 -0
- package/src/iotdevice/iotdevice.schemas.ts +79 -0
- package/src/iotdevice/iotdevice.service.ts +732 -0
- package/src/iotdevice/iotdevice.validation.ts +61 -0
- package/src/middlewares/auth.ts +110 -0
- package/src/middlewares/checkJwt.cjs +19 -0
- package/src/middlewares/error.js.legacy +44 -0
- package/src/middlewares/error.ts +41 -0
- package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +15 -0
- package/src/middlewares/rateLimiter.ts +10 -0
- package/src/middlewares/validate.ts +25 -0
- package/src/middlewares/validateAction.ts +41 -0
- package/src/middlewares/validateAdmin.ts +21 -0
- package/src/middlewares/validateAi.ts +24 -0
- package/src/middlewares/validateCurrentAuthUser.ts +23 -0
- package/src/middlewares/validateCurrentUser.ts +35 -0
- package/src/middlewares/validateDevice.ts +191 -0
- package/src/middlewares/validateDeviceUserOrganization.ts +54 -0
- package/src/middlewares/validateOrganization.ts +109 -0
- package/src/middlewares/validateQuerySearchUserAndOrganization.ts +75 -0
- package/src/middlewares/validateTokens.ts +36 -0
- package/src/middlewares/validateUser.ts +75 -0
- package/src/middlewares/validateZod.ts +54 -0
- package/src/models/plugins/index.ts +7 -0
- package/src/models/plugins/paginate.plugin.ts +145 -0
- package/src/models/plugins/paginateNew.plugin.ts +206 -0
- package/src/models/plugins/simplePopulate.ts +12 -0
- package/src/models/plugins/toJSON.plugin.ts +51 -0
- package/src/organizations/organizations.controller.ts +101 -0
- package/src/organizations/organizations.model.ts +62 -0
- package/src/organizations/organizations.route.ts +119 -0
- package/src/organizations/organizations.schemas.ts +8 -0
- package/src/organizations/organizations.service.ts +85 -0
- package/src/organizations/organizations.validation.ts +76 -0
- package/src/pdf/pdf.controller.ts +18 -0
- package/src/pdf/pdf.route.ts +28 -0
- package/src/pdf/pdf.schemas.ts +7 -0
- package/src/pdf/pdf.service.ts +89 -0
- package/src/pdf/pdf.validation.ts +30 -0
- package/src/tokens/tokens.controller.ts +81 -0
- package/src/tokens/tokens.model.ts +24 -0
- package/src/tokens/tokens.route.ts +66 -0
- package/src/tokens/tokens.schemas.ts +15 -0
- package/src/tokens/tokens.service.ts +46 -0
- package/src/tokens/tokens.validation.ts +13 -0
- package/src/types/routeSpec.ts +1 -0
- package/src/users/users.controller.ts +234 -0
- package/src/users/users.model.ts +89 -0
- package/src/users/users.route.ts +171 -0
- package/src/users/users.schemas.ts +79 -0
- package/src/users/users.service.ts +393 -0
- package/src/users/users.validation.ts +166 -0
- package/src/utils/ApiError.ts +18 -0
- package/src/utils/buildRouterAndDocs.ts +85 -0
- package/src/utils/catchAsync.ts +9 -0
- package/src/utils/comparePapers.service.ts +48 -0
- package/src/utils/filterOptions.ts +37 -0
- package/src/utils/medicationName.ts +12 -0
- package/src/utils/pick.ts +16 -0
- package/src/utils/registerOpenApi.ts +32 -0
- package/src/utils/urlUtils.ts +14 -0
- package/src/utils/userName.ts +27 -0
- package/src/utils/zValidations.ts +89 -0
- package/src/validations/auth.validation.cjs +60 -0
- package/src/validations/custom.validation.ts +26 -0
- package/src/validations/index.cjs +2 -0
- 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
|
+
};
|