@internetderdinge/api 1.229.0 → 1.229.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 (171) hide show
  1. package/dist/src/accounts/accounts.controller.js +89 -0
  2. package/dist/src/accounts/accounts.route.js +101 -0
  3. package/dist/src/accounts/accounts.schemas.js +12 -0
  4. package/dist/src/accounts/accounts.service.js +65 -0
  5. package/dist/src/accounts/accounts.validation.js +99 -0
  6. package/dist/src/accounts/auth0.service.js +188 -0
  7. package/dist/src/config/config.js +48 -0
  8. package/dist/src/config/logger.js +27 -0
  9. package/dist/src/config/morgan.js +16 -0
  10. package/dist/src/config/passport.cjs +28 -0
  11. package/dist/src/config/roles.js +11 -0
  12. package/dist/src/config/tokens.cjs +10 -0
  13. package/dist/src/devices/devices.controller.js +172 -0
  14. package/dist/src/devices/devices.model.js +94 -0
  15. package/dist/src/devices/devices.route.js +153 -0
  16. package/dist/src/devices/devices.schemas.js +84 -0
  17. package/dist/src/devices/devices.service.js +198 -0
  18. package/dist/src/devices/devices.types.js +1 -0
  19. package/dist/src/devices/devices.validation.js +257 -0
  20. package/dist/src/devicesNotifications/devicesNotifications.controller.js +69 -0
  21. package/dist/src/devicesNotifications/devicesNotifications.model.js +39 -0
  22. package/dist/src/devicesNotifications/devicesNotifications.route.js +124 -0
  23. package/dist/src/devicesNotifications/devicesNotifications.schemas.js +10 -0
  24. package/dist/src/devicesNotifications/devicesNotifications.service.js +181 -0
  25. package/dist/src/devicesNotifications/devicesNotifications.validation.js +46 -0
  26. package/dist/src/email/email.service.js +580 -0
  27. package/dist/src/files/upload.service.js +124 -0
  28. package/dist/src/i18n/i18n.js +38 -0
  29. package/dist/src/i18n/saveMissingLocalJsonBackend.js +53 -0
  30. package/dist/src/i18n/types.js +1 -0
  31. package/dist/src/index.js +48 -0
  32. package/dist/src/iotdevice/iotdevice.controller.js +96 -0
  33. package/dist/src/iotdevice/iotdevice.model.js +17 -0
  34. package/dist/src/iotdevice/iotdevice.route.js +143 -0
  35. package/dist/src/iotdevice/iotdevice.schemas.js +60 -0
  36. package/dist/src/iotdevice/iotdevice.service.js +579 -0
  37. package/dist/src/iotdevice/iotdevice.types.js +1 -0
  38. package/dist/src/iotdevice/iotdevice.validation.js +54 -0
  39. package/dist/src/middlewares/auth.js +75 -0
  40. package/dist/src/middlewares/checkJwt.cjs +17 -0
  41. package/dist/src/middlewares/error.js +36 -0
  42. package/dist/src/middlewares/mongooseValidations/ensureSameOrganization.js +13 -0
  43. package/dist/src/middlewares/rateLimiter.js +7 -0
  44. package/dist/src/middlewares/validate.js +18 -0
  45. package/dist/src/middlewares/validateAction.js +35 -0
  46. package/dist/src/middlewares/validateAdmin.js +18 -0
  47. package/dist/src/middlewares/validateAi.js +16 -0
  48. package/dist/src/middlewares/validateCurrentAuthUser.js +17 -0
  49. package/dist/src/middlewares/validateCurrentUser.js +20 -0
  50. package/dist/src/middlewares/validateDevice.js +98 -0
  51. package/dist/src/middlewares/validateDeviceUserOrganization.js +26 -0
  52. package/dist/src/middlewares/validateOrganization.js +63 -0
  53. package/dist/src/middlewares/validateQuerySearchUserAndOrganization.js +44 -0
  54. package/dist/src/middlewares/validateTokens.js +23 -0
  55. package/dist/src/middlewares/validateUser.js +38 -0
  56. package/dist/src/middlewares/validateZod.js +33 -0
  57. package/dist/src/models/plugins/index.js +4 -0
  58. package/dist/src/models/plugins/paginate.plugin.js +117 -0
  59. package/dist/src/models/plugins/paginateNew.plugin.js +185 -0
  60. package/dist/src/models/plugins/simplePopulate.js +16 -0
  61. package/dist/src/models/plugins/toJSON.plugin.js +35 -0
  62. package/dist/src/organizations/organizations.controller.js +64 -0
  63. package/dist/src/organizations/organizations.model.js +41 -0
  64. package/dist/src/organizations/organizations.route.js +98 -0
  65. package/dist/src/organizations/organizations.schemas.js +7 -0
  66. package/dist/src/organizations/organizations.service.js +59 -0
  67. package/dist/src/organizations/organizations.validation.js +62 -0
  68. package/dist/src/pdf/pdf.controller.js +24 -0
  69. package/dist/src/pdf/pdf.route.js +22 -0
  70. package/dist/src/pdf/pdf.schemas.js +6 -0
  71. package/dist/src/pdf/pdf.service.js +65 -0
  72. package/dist/src/pdf/pdf.validation.js +27 -0
  73. package/dist/src/tokens/tokens.controller.js +60 -0
  74. package/dist/src/tokens/tokens.model.js +17 -0
  75. package/dist/src/tokens/tokens.route.js +52 -0
  76. package/dist/src/tokens/tokens.schemas.js +14 -0
  77. package/dist/src/tokens/tokens.service.js +30 -0
  78. package/dist/src/tokens/tokens.validation.js +9 -0
  79. package/dist/src/types/routeSpec.js +1 -0
  80. package/dist/src/users/users.controller.js +147 -0
  81. package/dist/src/users/users.model.js +50 -0
  82. package/dist/src/users/users.route.js +137 -0
  83. package/dist/src/users/users.schemas.js +69 -0
  84. package/dist/src/users/users.service.js +295 -0
  85. package/dist/src/users/users.types.js +1 -0
  86. package/dist/src/users/users.validation.js +144 -0
  87. package/dist/src/utils/ApiError.js +16 -0
  88. package/dist/src/utils/buildRouterAndDocs.js +72 -0
  89. package/dist/src/utils/catchAsync.js +4 -0
  90. package/dist/src/utils/comparePapers.service.js +32 -0
  91. package/dist/src/utils/deviceUtils.js +63 -0
  92. package/dist/src/utils/filterOptions.js +24 -0
  93. package/dist/src/utils/medicationName.js +10 -0
  94. package/dist/src/utils/pick.js +16 -0
  95. package/dist/src/utils/registerOpenApi.js +67 -0
  96. package/dist/src/utils/urlUtils.js +15 -0
  97. package/dist/src/utils/userName.js +22 -0
  98. package/dist/src/utils/zValidations.js +143 -0
  99. package/dist/src/validations/auth.validation.cjs +53 -0
  100. package/dist/src/validations/custom.validation.js +19 -0
  101. package/dist/src/validations/index.cjs +3 -0
  102. package/dist/tsconfig.tsbuildinfo +1 -0
  103. package/package.json +97 -80
  104. package/scripts/release-and-sync-paperless.mjs +137 -0
  105. package/src/accounts/accounts.controller.ts +1 -0
  106. package/src/accounts/accounts.service.ts +1 -0
  107. package/src/accounts/accounts.validation.ts +8 -5
  108. package/src/accounts/auth0.service.ts +55 -28
  109. package/src/config/config.ts +6 -0
  110. package/src/config/logger.ts +15 -9
  111. package/src/devices/devices.controller.ts +7 -1
  112. package/src/devices/devices.model.ts +4 -1
  113. package/src/devices/devices.schemas.ts +11 -9
  114. package/src/devices/devices.service.ts +1 -0
  115. package/src/devices/devices.types.ts +1 -0
  116. package/src/devices/devices.validation.ts +93 -32
  117. package/src/devicesNotifications/devicesNotifications.controller.ts +57 -28
  118. package/src/devicesNotifications/devicesNotifications.model.ts +20 -12
  119. package/src/devicesNotifications/devicesNotifications.service.ts +35 -17
  120. package/src/files/upload.service.ts +52 -28
  121. package/src/i18n/i18n.ts +1 -1
  122. package/src/i18n/types.ts +1 -0
  123. package/src/index.ts +47 -0
  124. package/src/iotdevice/iotdevice.controller.ts +1 -0
  125. package/src/iotdevice/iotdevice.model.ts +6 -3
  126. package/src/iotdevice/iotdevice.route.ts +85 -76
  127. package/src/iotdevice/iotdevice.service.ts +4 -3
  128. package/src/iotdevice/iotdevice.types.ts +6 -0
  129. package/src/middlewares/auth.ts +2 -8
  130. package/src/middlewares/error.ts +26 -12
  131. package/src/middlewares/mongooseValidations/ensureSameOrganization.ts +4 -3
  132. package/src/middlewares/validateAi.ts +17 -9
  133. package/src/middlewares/validateDevice.ts +1 -0
  134. package/src/middlewares/validateDeviceUserOrganization.ts +1 -0
  135. package/src/middlewares/validateOrganization.ts +1 -1
  136. package/src/middlewares/validateQuerySearchUserAndOrganization.ts +1 -0
  137. package/src/middlewares/validateTokens.ts +2 -1
  138. package/src/middlewares/validateUser.ts +1 -0
  139. package/src/middlewares/validateZod.ts +5 -5
  140. package/src/models/plugins/index.ts +5 -4
  141. package/src/models/plugins/paginate.plugin.ts +26 -16
  142. package/src/models/plugins/paginateNew.plugin.ts +33 -21
  143. package/src/models/plugins/simplePopulate.ts +8 -3
  144. package/src/models/plugins/toJSON.plugin.ts +12 -5
  145. package/src/organizations/organizations.controller.ts +1 -2
  146. package/src/organizations/organizations.model.ts +4 -4
  147. package/src/organizations/organizations.route.ts +1 -1
  148. package/src/organizations/organizations.service.ts +15 -6
  149. package/src/organizations/organizations.validation.ts +1 -1
  150. package/src/pdf/pdf.controller.ts +18 -1
  151. package/src/pdf/pdf.service.ts +25 -16
  152. package/src/tokens/tokens.controller.ts +6 -8
  153. package/src/tokens/tokens.model.ts +3 -1
  154. package/src/tokens/tokens.service.ts +3 -2
  155. package/src/types/express.d.ts +17 -0
  156. package/src/types/mongoose.d.ts +22 -0
  157. package/src/users/users.controller.ts +8 -9
  158. package/src/users/users.model.ts +6 -5
  159. package/src/users/users.route.ts +0 -1
  160. package/src/users/users.service.ts +16 -0
  161. package/src/users/users.types.ts +1 -0
  162. package/src/users/users.validation.ts +6 -2
  163. package/src/utils/ApiError.ts +8 -1
  164. package/src/utils/buildRouterAndDocs.ts +57 -22
  165. package/src/utils/catchAsync.ts +27 -3
  166. package/src/utils/medicationName.ts +5 -4
  167. package/src/utils/pick.ts +5 -1
  168. package/src/utils/registerOpenApi.ts +75 -24
  169. package/src/utils/userName.ts +1 -0
  170. package/src/utils/zValidations.ts +98 -27
  171. package/tsconfig.json +13 -4
@@ -0,0 +1,65 @@
1
+ import puppeteer from "puppeteer";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import AWS from "aws-sdk";
4
+ import path from "path";
5
+ // Configure AWS SDK
6
+ const s3 = new AWS.S3({
7
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
8
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
9
+ region: process.env.AWS_REGION,
10
+ });
11
+ // Function to upload a file to S3
12
+ const uploadBuffer = async (buffer, fileName) => {
13
+ const params = {
14
+ Bucket: process.env.AWS_S3_BUCKET_NAME,
15
+ Key: fileName,
16
+ Body: buffer,
17
+ ContentType: "application/pdf",
18
+ ACL: "private",
19
+ };
20
+ const data = await s3.upload(params).promise();
21
+ return data.Location;
22
+ };
23
+ // Generate a signed URL
24
+ const generateSignedUrl = (fileName) => {
25
+ const params = {
26
+ Bucket: process.env.AWS_S3_BUCKET_NAME,
27
+ Key: fileName,
28
+ Expires: 60 * 60, // URL expiration time in seconds
29
+ };
30
+ return s3.getSignedUrl("getObject", params);
31
+ };
32
+ const generatePdfFromUrl = async ({ urlPath, token, }) => {
33
+ const domain = process.env.FRONTEND_URL;
34
+ const browser = await puppeteer.launch({
35
+ defaultViewport: {
36
+ width: 1300,
37
+ height: 1200,
38
+ deviceScaleFactor: 1,
39
+ },
40
+ executablePath: process.env.CHROME_BIN,
41
+ args: ["--no-sandbox"],
42
+ });
43
+ const page = await browser.newPage();
44
+ await page.goto(domain);
45
+ // Set the token in local storage
46
+ await page.evaluate((token) => {
47
+ localStorage.setItem("print-token", token);
48
+ }, token);
49
+ await page.goto(domain + urlPath, { waitUntil: "networkidle2" });
50
+ await page.waitForSelector(".pdf-render-complete");
51
+ const pdf = await page.pdf({ format: "A4", printBackground: true });
52
+ await page.evaluate(() => {
53
+ localStorage.setItem("print-token", "");
54
+ });
55
+ await browser.close();
56
+ const fileUrl = await uploadBuffer(Buffer.from(pdf), `download-${uuidv4()}.pdf`);
57
+ console.log(`File uploaded successfully. File URL: ${fileUrl}`);
58
+ const fileName = path.basename(fileUrl);
59
+ const signedUrl = generateSignedUrl(fileName);
60
+ console.log(`Signed URL: ${signedUrl}`);
61
+ return signedUrl;
62
+ };
63
+ export default {
64
+ generatePdfFromUrl,
65
+ };
@@ -0,0 +1,27 @@
1
+ import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
2
+ import { z } from 'zod';
3
+ extendZodWithOpenApi(z);
4
+ export const generatePdfSchema = {
5
+ query: z.object({
6
+ urlPath: z
7
+ .string()
8
+ /* .url()
9
+ .refine(
10
+ (value) =>
11
+ value.startsWith('https://memo.wirewire.de') ||
12
+ value.startsWith('https://web.wirewire.de') ||
13
+ (process.env.NODE_ENV !== 'production' && value.startsWith('http://localhost:3200')),
14
+ {
15
+ message:
16
+ process.env.NODE_ENV !== 'production'
17
+ ? 'urlPath must start with https://memo.wirewire.de, https://web.wirewire.de, or http://localhost:3200'
18
+ : 'urlPath must start with https://memo.wirewire.de or https://web.wirewire.de',
19
+ }
20
+ ) */
21
+ .openapi({
22
+ example: 'https://memo.wirewire.de/example.pdf',
23
+ description: 'URL path to the PDF generation endpoint. Allowed domains: https://memo.wirewire.de, https://web.wirewire.de' +
24
+ (process.env.NODE_ENV !== 'production' ? ', or http://localhost:3200 (dev)' : ''),
25
+ }),
26
+ }),
27
+ };
@@ -0,0 +1,60 @@
1
+ import httpStatus from "http-status";
2
+ import * as tokensService from "./tokens.service.js";
3
+ import pick from "../utils/pick.js";
4
+ export const getTokens = async (req, res, next) => {
5
+ const filter = pick({ ...req.query, owner: res.req.auth.sub }, [
6
+ "name",
7
+ "role",
8
+ "owner",
9
+ ]);
10
+ const options = pick(req.query, ["sortBy", "limit", "page"]);
11
+ const result = await tokensService.queryTokens(filter, options);
12
+ res.send(result);
13
+ };
14
+ // Create a new token
15
+ export const createToken = async (req, res, next) => {
16
+ try {
17
+ const tokenData = {
18
+ ...req.body,
19
+ owner: res.req.auth.sub, // Always set owner from authenticated user
20
+ };
21
+ const token = await tokensService.createToken(tokenData);
22
+ res.status(httpStatus.CREATED).send(token);
23
+ }
24
+ catch (error) {
25
+ next(error);
26
+ }
27
+ };
28
+ // Get a token by ID
29
+ export const getToken = async (req, res, next) => {
30
+ try {
31
+ const token = await tokensService.getTokenById(req.params.tokenId);
32
+ if (!token) {
33
+ res.status(httpStatus.NOT_FOUND).send({ message: "Token not found" });
34
+ return;
35
+ }
36
+ res.send(token);
37
+ }
38
+ catch (error) {
39
+ next(error);
40
+ }
41
+ };
42
+ // Delete a token by ID
43
+ export const deleteToken = async (req, res, next) => {
44
+ try {
45
+ const token = await tokensService.deleteTokenById(req.params.tokenId);
46
+ if (!token) {
47
+ res.status(httpStatus.NOT_FOUND).send({ message: "Token not found" });
48
+ return;
49
+ }
50
+ res.status(httpStatus.NO_CONTENT).send();
51
+ }
52
+ catch (error) {
53
+ next(error);
54
+ }
55
+ };
56
+ export default {
57
+ createToken,
58
+ getToken,
59
+ deleteToken,
60
+ };
@@ -0,0 +1,17 @@
1
+ // @ts-nocheck
2
+ import mongoose, { Schema } from "mongoose";
3
+ import { toJSON, paginate } from "../models/plugins/index.js";
4
+ const tokenSchema = new mongoose.Schema({
5
+ name: { type: String, required: true, trim: true },
6
+ value: { type: String, required: true, select: false }, // Store hash only
7
+ owner: { type: String, required: true, index: true, trim: true },
8
+ expiresAt: { type: Date, required: false, index: true },
9
+ usedAt: { type: Date, required: false, index: true },
10
+ meta: { type: Schema.Types.Mixed, required: false },
11
+ }, {
12
+ timestamps: true,
13
+ });
14
+ tokenSchema.plugin(toJSON, true);
15
+ tokenSchema.plugin(paginate);
16
+ const Token = mongoose.models.Token || mongoose.model("Token", tokenSchema);
17
+ export default Token;
@@ -0,0 +1,52 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import { createTokenSchema, getTokenSchema, deleteTokenSchema, } from "./tokens.validation.js";
4
+ import { tokenResponseSchema } from "./tokens.schemas.js";
5
+ import { createToken, getToken, deleteToken, getTokens, } from "./tokens.controller.js";
6
+ import auth from "../middlewares/auth.js";
7
+ import { validateParamsToken } from "../middlewares/validateTokens.js";
8
+ export const tokensRouteSpecs = [
9
+ {
10
+ method: "get",
11
+ path: "/",
12
+ validate: [auth("manageTokens")],
13
+ requestSchema: {},
14
+ responseSchema: tokenResponseSchema,
15
+ handler: getTokens,
16
+ privateDocs: true,
17
+ summary: "List all tokens of the current user",
18
+ },
19
+ {
20
+ method: "post",
21
+ path: "/",
22
+ validate: [auth("manageTokens")],
23
+ requestSchema: createTokenSchema,
24
+ responseSchema: tokenResponseSchema,
25
+ handler: createToken,
26
+ privateDocs: true,
27
+ summary: "Create a new token",
28
+ },
29
+ {
30
+ method: "get",
31
+ path: "/:tokenId",
32
+ validate: [auth("getTokens"), validateParamsToken],
33
+ requestSchema: getTokenSchema,
34
+ responseSchema: tokenResponseSchema,
35
+ handler: getToken,
36
+ privateDocs: true,
37
+ summary: "Get a token by ID",
38
+ },
39
+ {
40
+ method: "delete",
41
+ path: "/:tokenId",
42
+ validate: [auth("manageTokens"), validateParamsToken],
43
+ requestSchema: deleteTokenSchema,
44
+ responseSchema: tokenResponseSchema,
45
+ handler: deleteToken,
46
+ privateDocs: true,
47
+ summary: "Delete a token by ID",
48
+ },
49
+ ];
50
+ const router = Router();
51
+ buildRouterAndDocs(router, tokensRouteSpecs, "/tokens", ["Tokens"]);
52
+ export default router;
@@ -0,0 +1,14 @@
1
+ import { z } from 'zod';
2
+ export const tokenResponseSchema = z.object({
3
+ // unique identifier of the token
4
+ tokenId: z.string().uuid(),
5
+ // the owning user
6
+ userId: z.string().uuid(),
7
+ // optional name or description
8
+ name: z.string().optional(),
9
+ // the token value (never returned after creation in real-world apps)
10
+ token: z.string(),
11
+ // timestamps
12
+ createdAt: z.string().datetime(),
13
+ updatedAt: z.string().datetime(),
14
+ });
@@ -0,0 +1,30 @@
1
+ import crypto from "crypto";
2
+ import Token from "./tokens.model.js";
3
+ export const queryTokens = async (filter, options) => {
4
+ return Token.paginate(filter, options);
5
+ };
6
+ export const createToken = async (tokenData) => {
7
+ // 1. Generate raw token
8
+ const rawToken = crypto.randomBytes(32).toString("hex");
9
+ // 2. Hash the token (e.g., using SHA-256)
10
+ const hashedToken = crypto
11
+ .createHash("sha256")
12
+ .update(rawToken)
13
+ .digest("hex");
14
+ const tokenDataHased = {
15
+ ...tokenData,
16
+ value: hashedToken,
17
+ };
18
+ const newToken = await Token.create(tokenDataHased);
19
+ return {
20
+ ...newToken.toObject(),
21
+ raw: rawToken, // Return the raw token to the user
22
+ };
23
+ };
24
+ export const getTokenById = async (tokenId) => {
25
+ return Token.findById(tokenId);
26
+ };
27
+ // Delete a token by ID
28
+ export const deleteTokenById = async (tokenId) => {
29
+ return Token.findByIdAndDelete(tokenId);
30
+ };
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ import { zGet, zDelete } from "../utils/zValidations.js";
3
+ export const createTokenSchema = {
4
+ body: z.object({
5
+ name: z.string().openapi({ example: "my sample token" }),
6
+ }),
7
+ };
8
+ export const getTokenSchema = zGet("tokenId");
9
+ export const deleteTokenSchema = zDelete("tokenId");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,147 @@
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
+ export const createUser = catchAsync(async (req, res) => {
11
+ const { body } = req;
12
+ if (body.email) {
13
+ const auth0users = await auth0Service.getUserIdByEmail(body.email);
14
+ body.status = "invited";
15
+ body.inviteCode = crypto.randomBytes(48).toString("base64url");
16
+ if (auth0users[0]) {
17
+ const userFound = await userService.getUserByOwner(auth0users[0].user_id, body.organization);
18
+ if (userFound) {
19
+ throw new ApiError(httpStatus.CONFLICT, "User already added in organization");
20
+ }
21
+ }
22
+ }
23
+ const user = await userService.createUser(body);
24
+ if (body.email) {
25
+ await userService.sendInviteEmail({
26
+ user,
27
+ email: body.email,
28
+ inviteCode: body.inviteCode,
29
+ auth: req.auth,
30
+ });
31
+ }
32
+ res.status(httpStatus.CREATED).send(user);
33
+ });
34
+ export const createCurrentUser = catchAsync(async (req, res) => {
35
+ const user = await userService.createUser({ owner: req.auth.sub });
36
+ res.status(httpStatus.CREATED).send(user);
37
+ });
38
+ export const sendVerificationEmail = catchAsync(async (req, res) => {
39
+ const result = await auth0Service.sendVerificationEmail(req.auth.sub);
40
+ res
41
+ .status(httpStatus.OK)
42
+ .send({ message: "Verification email sent", status: result?.status });
43
+ });
44
+ export const getUsers = catchAsync(async (req, res) => {
45
+ // console.log('getUsers', req.query);
46
+ const filter = pick(req.query, ["name", "role"]);
47
+ const options = pick(req.query, ["sortBy", "limit", "page"]);
48
+ options.fuzzySearch = req.query.search
49
+ ? {
50
+ search: req.query.search,
51
+ index: "users",
52
+ fields: ["meta.firstName", "meta.lastName"],
53
+ }
54
+ : undefined;
55
+ const result = await userService.queryUsers({ ...filter, organization: req.query.organization }, options);
56
+ // console.log('getUsers result', req.currentUser);
57
+ if (req.currentUser?.role &&
58
+ req.currentUser.role === "onlyself" &&
59
+ result?.results) {
60
+ // If the user is onlyself, restrict the results to their own user document
61
+ result.results = result.results.filter((user) => user.owner === req.auth.sub);
62
+ }
63
+ res.send(result);
64
+ });
65
+ export const getCurrentUser = catchAsync(async (req, res) => {
66
+ const organization = req.query.organization;
67
+ const result = await userService.getUserByOwner(req.auth.sub, organization);
68
+ res.send(result);
69
+ });
70
+ export const getUserImage = catchAsync(async (_req, res) => {
71
+ try {
72
+ // await userImageUpload.getPhoto(req.query.file, res);
73
+ }
74
+ catch (error) {
75
+ res.status(500).json(`Failed to receive image file: ${error.message}`);
76
+ }
77
+ });
78
+ export const updateUserImage = catchAsync(async (_req, _res) => {
79
+ // placeholder for image upload
80
+ });
81
+ export const getUser = catchAsync(async (req, res) => {
82
+ const user = await userService.getByIdWithAuth0(req.params.userId);
83
+ if (!user) {
84
+ throw new ApiError(httpStatus.NOT_FOUND, `User not found ID: ${req.params.userId}`);
85
+ }
86
+ res.send(user);
87
+ });
88
+ export const updateUser = catchAsync(async (req, res) => {
89
+ const authReq = req;
90
+ const user = await userService.updateUserById(authReq.params.userId, authReq.body, authReq.auth);
91
+ res.send(user);
92
+ });
93
+ export const deleteUser = catchAsync(async (req, res) => {
94
+ const devices = await devicesService.getDeviceByUserId(req.params.userId);
95
+ if (devices) {
96
+ throw new ApiError(httpStatus.NOT_FOUND, "User still has devices assigned");
97
+ }
98
+ const user = await userService.deleteUserById(req.params.userId);
99
+ res.send(user);
100
+ });
101
+ export const getInvite = catchAsync(async (req, res) => {
102
+ // console.log('getInvite', req.params.inviteCode);
103
+ const authReq = req;
104
+ const user = await userService.getInvite({
105
+ inviteCode: authReq.params.inviteCode,
106
+ });
107
+ if (!user) {
108
+ throw new ApiError(httpStatus.NOT_FOUND, `User not found token: ${authReq.params.inviteCode}`);
109
+ }
110
+ const organizationId = String(user.organization);
111
+ const already = await userService.getUserByOwner(authReq.auth.sub, organizationId);
112
+ if (already) {
113
+ throw new ApiError(httpStatus.CONFLICT, "User already in organization");
114
+ }
115
+ res.send(user);
116
+ });
117
+ export const updateInvite = catchAsync(async (req, res) => {
118
+ const user = await userService.updateInvite({
119
+ owner: req.auth.sub,
120
+ ...req.body,
121
+ });
122
+ if (!user) {
123
+ throw new ApiError(httpStatus.NOT_FOUND, "User not found");
124
+ }
125
+ res.send(user);
126
+ });
127
+ export const organizationUpdate = catchAsync(async (req, res) => {
128
+ const user = await userService.organizationUpdate(req.body);
129
+ res.send(user);
130
+ });
131
+ export const organizationRemove = catchAsync(async (req, res) => {
132
+ const user = await userService.organizationRemove(req.body);
133
+ res.send(user);
134
+ });
135
+ export const cleanup = catchAsync(async (_req, res) => {
136
+ const all = await userService.queryAllCalendars();
137
+ const filtered = all.filter((e) => e.organizationData === null);
138
+ const ids = filtered.map((e) => String(e._id));
139
+ const deleted = await userService.deleteMany(ids);
140
+ res.send({
141
+ deleted,
142
+ ids,
143
+ filteredCount: filtered.length,
144
+ totalCount: all.length,
145
+ filtered,
146
+ });
147
+ });
@@ -0,0 +1,50 @@
1
+ // @ts-nocheck
2
+ import mongoose, { Schema } from "mongoose";
3
+ import { toJSON, paginate } from "../models/plugins/index.js";
4
+ const userSchema = new Schema({
5
+ name: { type: String, trim: true },
6
+ avatar: { type: String },
7
+ timezone: { type: String, default: "Europe/Berlin" },
8
+ owner: { type: String },
9
+ organization: { type: Schema.Types.ObjectId, ref: "User", immutable: true },
10
+ inviteCode: { type: String },
11
+ email: { type: String },
12
+ role: { type: String, default: "patient" /* , enum: Object.keys(roles) */ },
13
+ category: { type: String, default: "patient" },
14
+ status: { type: String },
15
+ meta: { type: Schema.Types.Mixed },
16
+ }, {
17
+ timestamps: true,
18
+ toObject: { virtuals: true },
19
+ toJSON: { virtuals: true },
20
+ });
21
+ userSchema.virtual("organizationData", {
22
+ ref: "Organization",
23
+ localField: "organization",
24
+ foreignField: "_id",
25
+ justOne: true,
26
+ });
27
+ // plugins
28
+ userSchema.plugin(toJSON);
29
+ userSchema.plugin(paginate);
30
+ /**
31
+ * Check if email is taken
32
+ * @param {string} email - The user's email
33
+ * @param {ObjectId} [excludeUserId] - The id of the user to be excluded
34
+ * @returns {Promise<boolean>}
35
+ */
36
+ userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
37
+ const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
38
+ return !!user;
39
+ };
40
+ /**
41
+ * Check if password matches the user's password
42
+ * @param {string} password
43
+ * @returns {Promise<boolean>}
44
+ */
45
+ userSchema.methods.isPasswordMatch = async function (password) {
46
+ // TODO: implement with bcrypt
47
+ return false;
48
+ };
49
+ export const User = mongoose.models.User ||
50
+ mongoose.model("User", userSchema);
@@ -0,0 +1,137 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import auth from "../middlewares/auth.js";
4
+ import { validateQueryOrganization, validateBodyOrganization, } from "../middlewares/validateOrganization.js";
5
+ import { validateAdmin } from "../middlewares/validateAdmin.js";
6
+ import { createCurrentUserSchema, createUserSchema, deleteUserSchema, updateUserSchema, getUserSchema, getCurrentUserSchema, queryUsersSchema, updateInviteSchema, validateGetInviteSchema, sendVerificationEmailValidationSchema, } from "./users.validation.js";
7
+ import * as userController from "./users.controller.js";
8
+ import { createUserResponseSchema, getUsersResponseSchema, updateUserResponseSchema, deleteUserResponseSchema, } from "./users.schemas.js";
9
+ import { validateParamsUser, } from "../middlewares/validateUser.js";
10
+ export const userRouteSpecs = [
11
+ {
12
+ method: "post",
13
+ path: "/",
14
+ validate: [auth("manageUsers"), validateBodyOrganization],
15
+ requestSchema: createUserSchema,
16
+ responseSchema: createUserResponseSchema,
17
+ handler: userController.createUser,
18
+ summary: "Create a new user",
19
+ description: "Creates a new user within the current organization.",
20
+ memoOnly: true,
21
+ },
22
+ {
23
+ method: "get",
24
+ path: "/",
25
+ validate: [auth("getUsers"), validateQueryOrganization],
26
+ requestSchema: queryUsersSchema,
27
+ responseSchema: getUsersResponseSchema,
28
+ handler: userController.getUsers,
29
+ summary: "Get a list of users",
30
+ description: "Retrieves a paginated list of all users in the current organization.",
31
+ },
32
+ {
33
+ method: "get",
34
+ path: "/cleanup",
35
+ validate: [auth("cleanup"), validateAdmin],
36
+ handler: userController.cleanup,
37
+ summary: "Cleanup user data",
38
+ description: "Performs cleanup of stale or orphaned user records.",
39
+ },
40
+ {
41
+ method: "post",
42
+ path: "/current/send-verification-email",
43
+ validate: [auth("manageUsers")],
44
+ requestSchema: sendVerificationEmailValidationSchema,
45
+ responseSchema: getUsersResponseSchema,
46
+ handler: userController.sendVerificationEmail,
47
+ summary: "Send verification email to the current user",
48
+ description: "Sends a new verification email to the authenticated user’s address.",
49
+ privateDocs: true,
50
+ },
51
+ {
52
+ method: "get",
53
+ path: "/current",
54
+ validate: [auth("getUsers"), validateQueryOrganization],
55
+ requestSchema: getCurrentUserSchema,
56
+ handler: userController.getCurrentUser,
57
+ summary: "Get the current user",
58
+ description: "Fetches details about the currently authenticated user.",
59
+ },
60
+ {
61
+ method: "post",
62
+ path: "/current",
63
+ validate: [auth("manageUsers")],
64
+ requestSchema: createCurrentUserSchema,
65
+ responseSchema: createUserResponseSchema,
66
+ handler: userController.createCurrentUser,
67
+ privateDocs: true,
68
+ summary: "Create the current user",
69
+ description: "Creates or initializes a profile for the authenticated user.",
70
+ },
71
+ {
72
+ method: "get",
73
+ path: "/invite/:inviteCode",
74
+ validate: [auth("getUsers")],
75
+ requestSchema: validateGetInviteSchema,
76
+ responseSchema: getUsersResponseSchema,
77
+ handler: userController.getInvite,
78
+ privateDocs: true,
79
+ summary: "Get invite details by code",
80
+ description: "Retrieves information about a pending invite using its code.",
81
+ },
82
+ {
83
+ method: "post",
84
+ path: "/invite",
85
+ validate: [auth("getUsers")],
86
+ requestSchema: updateInviteSchema,
87
+ handler: userController.updateInvite,
88
+ privateDocs: true,
89
+ summary: "Update invite details",
90
+ description: "Modifies the details or status of an existing invite.",
91
+ },
92
+ {
93
+ method: "get",
94
+ path: "/:userId",
95
+ validate: [auth("getUsers"), validateParamsUser],
96
+ requestSchema: getUserSchema,
97
+ responseSchema: getUsersResponseSchema,
98
+ handler: userController.getUser,
99
+ summary: "Get a user by ID",
100
+ description: "Fetches a single user’s details by their unique ID.",
101
+ },
102
+ {
103
+ method: "post",
104
+ path: "/:userId",
105
+ validate: [auth("manageUsers"), validateParamsUser],
106
+ requestSchema: updateUserSchema,
107
+ responseSchema: updateUserResponseSchema,
108
+ handler: userController.updateUser,
109
+ summary: "Update a user by ID",
110
+ description: "Replaces a user’s full record with the provided data.",
111
+ memoOnly: true,
112
+ },
113
+ {
114
+ method: "patch",
115
+ path: "/:userId",
116
+ validate: [auth("manageUsers"), validateParamsUser],
117
+ requestSchema: updateUserSchema,
118
+ responseSchema: updateUserResponseSchema,
119
+ handler: userController.updateUser,
120
+ summary: "Partially update a user by ID",
121
+ description: "Applies partial updates to a user’s record by ID.",
122
+ memoOnly: true,
123
+ },
124
+ {
125
+ method: "delete",
126
+ path: "/:userId",
127
+ validate: [auth("manageUsers"), validateParamsUser],
128
+ requestSchema: deleteUserSchema,
129
+ responseSchema: deleteUserResponseSchema,
130
+ handler: userController.deleteUser,
131
+ summary: "Delete a user by ID",
132
+ description: "Removes a user from the system by their unique ID.",
133
+ },
134
+ ];
135
+ const router = Router();
136
+ buildRouterAndDocs(router, userRouteSpecs, "/users", ["Users"]);
137
+ export default router;