@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,48 @@
1
+ import sharp from 'sharp';
2
+ import pixelmatch from 'pixelmatch';
3
+
4
+ /**
5
+ * Compares two image buffers and returns the similarity percentage
6
+ * @param {Buffer} imgBuffer1 - First image buffer
7
+ * @param {Buffer} imgBuffer2 - Second image buffer
8
+ * @returns {Promise<number>} - Percentage of similarity (0-100)
9
+ */
10
+ export async function compareImages(imgBuffer1: Buffer, imgBuffer2: Buffer): Promise<number> {
11
+ // Load the images using sharp and resize them to the same size
12
+
13
+ const image1 = await sharp(imgBuffer1).raw().ensureAlpha().toBuffer({ resolveWithObject: true });
14
+ const image2 = await sharp(imgBuffer2).raw().ensureAlpha().toBuffer({ resolveWithObject: true });
15
+
16
+ // Ensure both images are of the same dimensions
17
+ const width = Math.min(image1.info.width, image2.info.width);
18
+ const height = Math.min(image1.info.height, image2.info.height);
19
+
20
+ if (image1.info.width !== image2.info.width || image1.info.height !== image2.info.height) {
21
+ return 0;
22
+ }
23
+
24
+ // Resize the images to match in size
25
+ const resizedImage1 = await sharp(imgBuffer1).resize(width, height).raw().ensureAlpha().toBuffer();
26
+ const resizedImage2 = await sharp(imgBuffer2).resize(width, height).raw().ensureAlpha().toBuffer();
27
+
28
+ // Create an empty array to store pixel differences
29
+ const diff = new Uint8Array(width * height * 4);
30
+
31
+ // Compare the two images pixel by pixel
32
+ const numDiffPixels = pixelmatch(
33
+ resizedImage1,
34
+ resizedImage2,
35
+ diff,
36
+ width,
37
+ height,
38
+ { threshold: 0.3 } // Threshold for pixel comparison
39
+ );
40
+
41
+ // Calculate similarity percentage
42
+ const totalPixels = width * height;
43
+ const similarityPercentage = ((totalPixels - numDiffPixels) / totalPixels) * 100;
44
+
45
+ return similarityPercentage;
46
+ }
47
+
48
+ export default { compareImages };
@@ -0,0 +1,37 @@
1
+ import mongoose from 'mongoose';
2
+ import type { FilterQuery } from 'mongoose';
3
+
4
+ const { ObjectId } = mongoose.Types;
5
+
6
+ interface FilterOptions {
7
+ objectIds: string[];
8
+ search: string[];
9
+ }
10
+
11
+ const filterOptions = (query: { search?: string }, filter: FilterQuery<any>, options: FilterOptions): FilterQuery<any> => {
12
+ let filterAlt = filter;
13
+
14
+ if (query.search && mongoose.isValidObjectId(query.search)) {
15
+ const idList = options.objectIds.map((e) => ({
16
+ [e]: new ObjectId(query.search),
17
+ }));
18
+
19
+ filterAlt = {
20
+ $and: [filter, { $or: idList }],
21
+ };
22
+ } else if (query.search) {
23
+ const regexp = new RegExp(query.search, 'i');
24
+
25
+ const searchList = options.search.map((e) => ({
26
+ [e]: { $regex: regexp },
27
+ }));
28
+
29
+ filterAlt = {
30
+ $and: [filter, { $or: searchList }],
31
+ };
32
+ }
33
+
34
+ return filterAlt;
35
+ };
36
+
37
+ export { filterOptions };
@@ -0,0 +1,12 @@
1
+ import i18n from '../i18n/i18n';
2
+ import type { MedicationEntry } from '../types/MedicationEntry';
3
+
4
+ export const medicationName = (entry: MedicationEntry, lng: string): string => {
5
+ const name = entry.medicationData?.Kurzname
6
+ ? entry.medicationData.Kurzname
7
+ : entry.localMedicationData?.meta?.name
8
+ ? entry.localMedicationData.meta.name.replace(/ .*/, '')
9
+ : i18n.t('A medication', { lng });
10
+
11
+ return name;
12
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Create an object composed of the picked object properties
3
+ * @param {Record<string, any>} object - The source object
4
+ * @param {string[]} keys - The keys to pick
5
+ * @returns {Partial<Record<string, any>>} - The new object with picked properties
6
+ */
7
+ const pick = <T extends Record<string, any>>(object: T, keys: (keyof T)[]): Partial<T> => {
8
+ return keys.reduce((obj, key) => {
9
+ if (object && Object.prototype.hasOwnProperty.call(object, key)) {
10
+ obj[key] = object[key];
11
+ }
12
+ return obj;
13
+ }, {} as Partial<T>);
14
+ };
15
+
16
+ export default pick;
@@ -0,0 +1,32 @@
1
+ import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
2
+
3
+ export const registry = new OpenAPIRegistry();
4
+
5
+ // add Bearer JWT auth
6
+ export const bearerAuth = registry.registerComponent('securitySchemes', 'bearerAuth', {
7
+ type: 'http',
8
+ scheme: 'bearer',
9
+ bearerFormat: 'JWT',
10
+ description: 'JWT Bearer authentication',
11
+ });
12
+
13
+ export const xApiKey = registry.registerComponent('securitySchemes', 'x-api-key', {
14
+ type: 'apiKey',
15
+ in: 'header',
16
+ name: 'x-api-key',
17
+ description: 'API key for authentication',
18
+ });
19
+
20
+ /*
21
+ registry.registerComponent('global', 'wwww', [
22
+ { name: 'ai', description: 'All AI-powered endpoints.' },
23
+ { name: 'auth', description: 'Authentication and authorization.' },
24
+ { name: 'Users', description: 'User management (create, read, update, delete).' },
25
+ // add more as needed
26
+ ]);
27
+
28
+ registry.registerComponent('tag', 'users', {
29
+ name: 'users',
30
+ description: 'Operations about users',
31
+ });
32
+ */
@@ -0,0 +1,14 @@
1
+ export const resolvePossiblyRelativeUrl = (maybeUrl?: string | null, baseUrl?: string | null) => {
2
+ if (!maybeUrl) return null;
3
+ if (maybeUrl.startsWith('http')) return maybeUrl;
4
+
5
+ if (baseUrl && baseUrl.startsWith('http')) {
6
+ try {
7
+ return new URL(maybeUrl, baseUrl).toString();
8
+ } catch {
9
+ return maybeUrl;
10
+ }
11
+ }
12
+
13
+ return maybeUrl;
14
+ };
@@ -0,0 +1,27 @@
1
+ import { getUserById } from "../accounts/auth0.service";
2
+ import type { Patient } from "../types/patient"; // Assuming a `Patient` type exists in your project
3
+
4
+ const generateUserName = async (patient: Patient): Promise<string | null> => {
5
+ if (patient.owner) {
6
+ const auth0UserById = await getUserById(patient.owner);
7
+ patient.auth0User = auth0UserById.data;
8
+ }
9
+
10
+ if (patient?.auth0User?.app_metadata?.first_name) {
11
+ return `${patient.auth0User.app_metadata.first_name} ${patient.auth0User.app_metadata.last_name}`;
12
+ }
13
+ if (patient?.auth0User?.family_name) {
14
+ return `${patient.auth0User.given_name} ${patient.auth0User.family_name}`;
15
+ }
16
+
17
+ if (patient && patient?.auth0User) {
18
+ return patient.auth0User.name;
19
+ }
20
+
21
+ if (patient && (patient.meta?.firstName || patient.meta?.lastName)) {
22
+ return `${patient.meta?.firstName}${patient.meta?.firstName ? " " : ""}${patient.meta?.lastName}`;
23
+ }
24
+ return null;
25
+ };
26
+
27
+ export default generateUserName;
@@ -0,0 +1,89 @@
1
+ import { z } from 'zod';
2
+ import { objectId } from '../validations/custom.validation';
3
+ import mongoose from 'mongoose';
4
+
5
+ export const zPagination = {
6
+ query: z.object({
7
+ name: z.string().optional(),
8
+ role: z.string().openapi({ example: 'admin', description: 'Role to filter by' }).optional(),
9
+ sortBy: z.string().openapi({ example: 'createdAt', description: 'Field to sort by' }).optional(),
10
+ search: z
11
+ .string()
12
+ .openapi({
13
+ example: 'search term',
14
+ description: 'Search term to filter results',
15
+ })
16
+ .optional(),
17
+ limit: z.coerce
18
+ .number()
19
+ .openapi({
20
+ example: 10,
21
+ description: 'Number of items per page',
22
+ })
23
+ .int()
24
+ .min(1)
25
+ .max(100000)
26
+ .optional(),
27
+ offset: z
28
+ .number() // was z.int()
29
+ .openapi({
30
+ example: 0,
31
+ description: 'Offset for pagination, used to skip a number of items',
32
+ })
33
+ .int()
34
+ .min(0)
35
+ .max(100000)
36
+ .optional(),
37
+ page: z
38
+ .number()
39
+ .int()
40
+ .openapi({
41
+ example: 1,
42
+ description: 'Page number for pagination',
43
+ })
44
+ .min(1)
45
+ .max(100000)
46
+ .optional(),
47
+ }),
48
+ };
49
+
50
+ export const zPaginationResponse = () =>
51
+ z.object({
52
+ results: z.array(z.unknown()), // Replace with your specific item schema
53
+ totalResults: z.number().openapi({ example: 100, description: 'Total number of items' }),
54
+ totalPages: z.number().openapi({ example: 10, description: 'Total number of pages' }),
55
+ page: z.number().openapi({ example: 1, description: 'Current page number' }),
56
+ limit: z.number().openapi({ example: 10, description: 'Number of items per page' }),
57
+ });
58
+
59
+ export const zGet = (id: string) => ({
60
+ params: z.object({
61
+ [id]: zObjectId /* .openapi({ example: '682fd0d7d4a6325d9d45b86d' }) */,
62
+ }),
63
+ });
64
+
65
+ export function zPatchBody<T extends ZodRawShape>(shape: T, message = 'At least one field must be provided for update') {
66
+ // assume user passed in already-optional vals
67
+ return z.object(shape); // .refine((o) => Object.keys(o).length > 0, { message });
68
+ }
69
+
70
+ export const zUpdate = (id: string) => ({
71
+ params: z.object({
72
+ [id]: zObjectId,
73
+ }),
74
+ });
75
+
76
+ export const zDelete = (id: string) => ({
77
+ params: z.object({
78
+ [id]: zObjectId,
79
+ }),
80
+ });
81
+
82
+ export const zObjectId = z
83
+ .string()
84
+ .refine((val) => {
85
+ return mongoose.Types.ObjectId.isValid(val);
86
+ })
87
+ .openapi({ example: '682fd0d7d4a6325d9d45b86d', description: 'A valid MongoDB ObjectId' });
88
+
89
+ export const zDate = () => z.string().pipe(z.coerce.date());
@@ -0,0 +1,60 @@
1
+ const Joi = require('joi');
2
+ const { password } = require('./custom.validation.ts');
3
+
4
+ const register = {
5
+ body: Joi.object().keys({
6
+ email: Joi.string().required().email(),
7
+ password: Joi.string().required().custom(password),
8
+ name: Joi.string().required(),
9
+ }),
10
+ };
11
+
12
+ const login = {
13
+ body: Joi.object().keys({
14
+ email: Joi.string().required(),
15
+ password: Joi.string().required(),
16
+ }),
17
+ };
18
+
19
+ const logout = {
20
+ body: Joi.object().keys({
21
+ refreshToken: Joi.string().required(),
22
+ }),
23
+ };
24
+
25
+ const refreshTokens = {
26
+ body: Joi.object().keys({
27
+ refreshToken: Joi.string().required(),
28
+ }),
29
+ };
30
+
31
+ const forgotPassword = {
32
+ body: Joi.object().keys({
33
+ email: Joi.string().email().required(),
34
+ }),
35
+ };
36
+
37
+ const resetPassword = {
38
+ query: Joi.object().keys({
39
+ token: Joi.string().required(),
40
+ }),
41
+ body: Joi.object().keys({
42
+ password: Joi.string().required().custom(password),
43
+ }),
44
+ };
45
+
46
+ const verifyEmail = {
47
+ query: Joi.object().keys({
48
+ token: Joi.string().required(),
49
+ }),
50
+ };
51
+
52
+ module.exports = {
53
+ register,
54
+ login,
55
+ logout,
56
+ refreshTokens,
57
+ forgotPassword,
58
+ resetPassword,
59
+ verifyEmail,
60
+ };
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+
3
+ // Can be anything starting with nrf- or epd7, or epd- followed by a number
4
+ export const deviceId = () =>
5
+ z.string().regex(/^(?:nrf-.*|epd7.*|epd-\d+)$/, {
6
+ message: 'must start with "nrf-", "epd7" or "epd-" followed by a number',
7
+ });
8
+
9
+ export const objectId = () =>
10
+ z.string().regex(/^[0-9a-fA-F]{24}$/, {
11
+ message: 'must be a valid mongo id',
12
+ });
13
+
14
+ export const password = () =>
15
+ z
16
+ .string()
17
+ .min(8, 'password must be at least 8 characters')
18
+ .refine((val) => /\d/.test(val) && /[a-zA-Z]/.test(val), {
19
+ message: 'password must contain at least 1 letter and 1 number',
20
+ });
21
+
22
+ export default {
23
+ deviceId,
24
+ objectId,
25
+ password,
26
+ };
@@ -0,0 +1,2 @@
1
+ module.exports.authValidation = require('./auth.validation.cjs');
2
+ module.exports.userValidation = require('./user.validation.cjs');
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "NodeNext",
5
+ "baseUrl": ".",
6
+ "rootDir": ".",
7
+ "outDir": "./dist",
8
+ "esModuleInterop": true,
9
+ "strict": true,
10
+ "moduleResolution": "NodeNext",
11
+ "resolveJsonModule": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "skipLibCheck": true,
14
+ "allowJs": true
15
+ },
16
+ "ts-node": {
17
+ "esm": true,
18
+ "experimentalSpecifierResolution": "node"
19
+ },
20
+ "include": ["src/**/*", "common.legac, "../memo-api/src/middlewares/validateMessage.ts"y/**/*"],
21
+ "exclude": ["node_modules", "dist"]
22
+ }