@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,76 @@
1
+ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
2
+ import { z } from "zod";
3
+ import { objectId } from "../validations/custom.validation.js";
4
+ import {
5
+ zPagination,
6
+ zGet,
7
+ zObjectId,
8
+ zPatchBody,
9
+ zUpdate,
10
+ zDelete,
11
+ } from "../utils/zValidations.js";
12
+
13
+ extendZodWithOpenApi(z);
14
+
15
+ export const createOrganizationSchema = {
16
+ body: z.object({
17
+ /* name: z.string().openapi({
18
+ example: 'Acme Inc.',
19
+ description: 'The name of the organization',
20
+ }),
21
+ email: z.string().email().openapi({
22
+ example: 'contact@acme.com',
23
+ description: 'Contact email for the organization',
24
+ }), */
25
+ kind: z.string().openapi({
26
+ example: "private",
27
+ description: "The type or category of the organization",
28
+ }),
29
+ }),
30
+ };
31
+
32
+ export const queryOrganizationsSchema = zPagination;
33
+
34
+ export const getOrganizationByIdSchema = zGet("organizationId");
35
+
36
+ export const updateOrganizationSchema = {
37
+ ...zUpdate("organizationId"),
38
+ body: z.object({
39
+ name: z
40
+ .string()
41
+ .openapi({
42
+ example: "Acme Inc.",
43
+ description: "The name of the organization",
44
+ })
45
+ .optional(),
46
+ organization: zObjectId, // Legacy field, to be removed later
47
+ kind: z
48
+ .string()
49
+ .openapi({
50
+ example: "private",
51
+ description: "The type or category of the organization",
52
+ })
53
+ .optional(),
54
+ meta: z
55
+ .record(z.any())
56
+ .openapi({
57
+ example: { key: "value" },
58
+ description: "Additional metadata for the entry",
59
+ })
60
+ .optional(),
61
+ }),
62
+ //...zUpdate('organizationId'),
63
+ /* body: z.object({
64
+ meta: z.any,
65
+ /* organization: zObjectId,
66
+ kind: z
67
+ .string()
68
+ .openapi({
69
+ example: 'private',
70
+ description: 'The type or category of the organization',
71
+ })
72
+ .optional(),
73
+ }),*/
74
+ };
75
+
76
+ export const deleteOrganizationSchema = zDelete("organizationId");
@@ -0,0 +1,18 @@
1
+ import httpStatus from "http-status";
2
+ import catchAsync from "../utils/catchAsync.js";
3
+ import pdfService from "./pdf.service.js";
4
+
5
+ export const generatePdfFromUrl = catchAsync(async (req, res) => {
6
+ const fileName = "memo-print";
7
+
8
+ const authHeader = req.headers["authorization"];
9
+ const token = authHeader.split(" ")[1];
10
+
11
+ const result = await pdfService.generatePdfFromUrl({ ...req.query, token });
12
+
13
+ res.status(httpStatus.CREATED).send({ signed: result });
14
+ });
15
+
16
+ export default {
17
+ generatePdfFromUrl,
18
+ };
@@ -0,0 +1,28 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import { generatePdfSchema } from "./pdf.validation.js";
4
+ import { pdfResponseSchema } from "./pdf.schemas.js";
5
+ import { generatePdfFromUrl } from "./pdf.controller.js";
6
+ import type { RouteSpec } from "../types/routeSpec";
7
+ import auth from "../middlewares/auth.js";
8
+
9
+ export const pdfRouteSpecs: RouteSpec[] = [
10
+ {
11
+ method: "get",
12
+ path: "/",
13
+ validate: [auth("manageUsers")],
14
+ requestSchema: generatePdfSchema,
15
+ responseSchema: pdfResponseSchema,
16
+ handler: generatePdfFromUrl,
17
+ summary: "Generate a PDF from a provided URL",
18
+ description:
19
+ "This endpoint allows users to generate a PDF document from a specified URL.",
20
+ memoOnly: true,
21
+ },
22
+ ];
23
+
24
+ const router: Router = Router();
25
+
26
+ buildRouterAndDocs(router, pdfRouteSpecs, "/pdf", ["PDF"]);
27
+
28
+ export default router;
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+
3
+ export const pdfResponseSchema = z.object({
4
+ url: z.string().url(),
5
+ filename: z.string(),
6
+ size: z.number(),
7
+ });
@@ -0,0 +1,89 @@
1
+ import puppeteer from 'puppeteer';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import AWS from 'aws-sdk';
4
+ import path from 'path';
5
+
6
+ // Configure AWS SDK
7
+ const s3 = new AWS.S3({
8
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
9
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
10
+ region: process.env.AWS_REGION,
11
+ });
12
+
13
+ // Function to upload a file to S3
14
+ const uploadBuffer = async (buffer: Buffer, fileName: string): Promise<string> => {
15
+ const params = {
16
+ Bucket: process.env.AWS_S3_BUCKET_NAME!,
17
+ Key: fileName,
18
+ Body: buffer,
19
+ ContentType: 'application/pdf',
20
+ ACL: 'private',
21
+ };
22
+
23
+ const data = await s3.upload(params).promise();
24
+ return data.Location;
25
+ };
26
+
27
+ // Generate a signed URL
28
+ const generateSignedUrl = (fileName: string): string => {
29
+ const params = {
30
+ Bucket: process.env.AWS_S3_BUCKET_NAME!,
31
+ Key: fileName,
32
+ Expires: 60 * 60, // URL expiration time in seconds
33
+ };
34
+
35
+ return s3.getSignedUrl('getObject', params);
36
+ };
37
+
38
+ interface GeneratePdfOptions {
39
+ urlPath: string;
40
+ token: string;
41
+ }
42
+
43
+ const generatePdfFromUrl = async ({ urlPath, token }: GeneratePdfOptions): Promise<string> => {
44
+ const domain = process.env.FRONTEND_URL!;
45
+ const browser = await puppeteer.launch({
46
+ defaultViewport: {
47
+ width: 1300,
48
+ height: 1200,
49
+ deviceScaleFactor: 1,
50
+ },
51
+ executablePath: process.env.CHROME_BIN,
52
+ args: ['--no-sandbox'],
53
+ });
54
+
55
+ const page = await browser.newPage();
56
+
57
+ await page.goto(domain);
58
+
59
+ // Set the token in local storage
60
+ await page.evaluate((token) => {
61
+ localStorage.setItem('print-token', token);
62
+ }, token);
63
+
64
+ await page.goto(domain + urlPath, { waitUntil: 'networkidle2' });
65
+
66
+ await page.waitForSelector('.pdf-render-complete');
67
+
68
+ const pdf = await page.pdf({ format: 'A4', printBackground: true });
69
+
70
+ await page.evaluate(() => {
71
+ localStorage.setItem('print-token', '');
72
+ });
73
+
74
+ await browser.close();
75
+
76
+ const fileUrl = await uploadBuffer(pdf, `download-${uuidv4()}.pdf`);
77
+
78
+ console.log(`File uploaded successfully. File URL: ${fileUrl}`);
79
+
80
+ const fileName = path.basename(fileUrl);
81
+ const signedUrl = generateSignedUrl(fileName);
82
+ console.log(`Signed URL: ${signedUrl}`);
83
+
84
+ return signedUrl;
85
+ };
86
+
87
+ export default {
88
+ generatePdfFromUrl,
89
+ };
@@ -0,0 +1,30 @@
1
+ import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
2
+ import { z } from 'zod';
3
+
4
+ extendZodWithOpenApi(z);
5
+
6
+ export const generatePdfSchema = {
7
+ query: z.object({
8
+ urlPath: z
9
+ .string()
10
+ /* .url()
11
+ .refine(
12
+ (value) =>
13
+ value.startsWith('https://memo.wirewire.de') ||
14
+ value.startsWith('https://web.wirewire.de') ||
15
+ (process.env.NODE_ENV !== 'production' && value.startsWith('http://localhost:3200')),
16
+ {
17
+ message:
18
+ process.env.NODE_ENV !== 'production'
19
+ ? 'urlPath must start with https://memo.wirewire.de, https://web.wirewire.de, or http://localhost:3200'
20
+ : 'urlPath must start with https://memo.wirewire.de or https://web.wirewire.de',
21
+ }
22
+ ) */
23
+ .openapi({
24
+ example: 'https://memo.wirewire.de/example.pdf',
25
+ description:
26
+ 'URL path to the PDF generation endpoint. Allowed domains: https://memo.wirewire.de, https://web.wirewire.de' +
27
+ (process.env.NODE_ENV !== 'production' ? ', or http://localhost:3200 (dev)' : ''),
28
+ }),
29
+ }),
30
+ };
@@ -0,0 +1,81 @@
1
+ import httpStatus from "http-status";
2
+ import type { Request, Response, NextFunction } from "express";
3
+ import * as tokensService from "./tokens.service.js";
4
+ import pick from "../utils/pick.js";
5
+
6
+ export const getTokens = async (
7
+ req: Request,
8
+ res: Response,
9
+ next: NextFunction,
10
+ ): Promise<void> => {
11
+ const filter = pick({ ...req.query, owner: res.req.auth.sub }, [
12
+ "name",
13
+ "role",
14
+ "owner",
15
+ ]);
16
+ const options = pick(req.query, ["sortBy", "limit", "page"]);
17
+ const result = await tokensService.queryTokens(filter, options);
18
+ res.send(result);
19
+ };
20
+
21
+ // Create a new token
22
+ export const createToken = async (
23
+ req: Request,
24
+ res: Response,
25
+ next: NextFunction,
26
+ ): Promise<void> => {
27
+ try {
28
+ const tokenData = {
29
+ ...req.body,
30
+ owner: res.req.auth.sub, // Always set owner from authenticated user
31
+ };
32
+ const token = await tokensService.createToken(tokenData);
33
+ res.status(httpStatus.CREATED).send(token);
34
+ } catch (error) {
35
+ next(error);
36
+ }
37
+ };
38
+
39
+ // Get a token by ID
40
+ export const getToken = async (
41
+ req: Request,
42
+ res: Response,
43
+ next: NextFunction,
44
+ ): Promise<void> => {
45
+ try {
46
+ const token = await tokensService.getTokenById(req.params.tokenId);
47
+ if (!token) {
48
+ return res
49
+ .status(httpStatus.NOT_FOUND)
50
+ .send({ message: "Token not found" });
51
+ }
52
+ res.send(token);
53
+ } catch (error) {
54
+ next(error);
55
+ }
56
+ };
57
+
58
+ // Delete a token by ID
59
+ export const deleteToken = async (
60
+ req: Request,
61
+ res: Response,
62
+ next: NextFunction,
63
+ ): Promise<void> => {
64
+ try {
65
+ const token = await tokensService.deleteTokenById(req.params.tokenId);
66
+ if (!token) {
67
+ return res
68
+ .status(httpStatus.NOT_FOUND)
69
+ .send({ message: "Token not found" });
70
+ }
71
+ res.status(httpStatus.NO_CONTENT).send();
72
+ } catch (error) {
73
+ next(error);
74
+ }
75
+ };
76
+
77
+ export default {
78
+ createToken,
79
+ getToken,
80
+ deleteToken,
81
+ };
@@ -0,0 +1,24 @@
1
+ import mongoose, { Schema, Model } from "mongoose";
2
+ import crypto from "crypto";
3
+ import { toJSON, paginate } from "../models/plugins/index.js";
4
+
5
+ const tokenSchema: Schema = new mongoose.Schema(
6
+ {
7
+ name: { type: String, required: true, trim: true },
8
+ value: { type: String, required: true, select: false }, // Store hash only
9
+ owner: { type: String, required: true, index: true, trim: true },
10
+ expiresAt: { type: Date, required: false, index: true },
11
+ usedAt: { type: Date, required: false, index: true },
12
+ meta: { type: Schema.Types.Mixed, required: false },
13
+ },
14
+ {
15
+ timestamps: true,
16
+ },
17
+ );
18
+
19
+ tokenSchema.plugin(toJSON, true);
20
+ tokenSchema.plugin(paginate);
21
+
22
+ const Token: Model<any> = mongoose.model("Token", tokenSchema);
23
+
24
+ export default Token;
@@ -0,0 +1,66 @@
1
+ import { Router } from "express";
2
+ import buildRouterAndDocs from "../utils/buildRouterAndDocs.js";
3
+ import {
4
+ createTokenSchema,
5
+ getTokenSchema,
6
+ deleteTokenSchema,
7
+ } from "./tokens.validation.js";
8
+ import { tokenResponseSchema } from "./tokens.schemas.js";
9
+ import {
10
+ createToken,
11
+ getToken,
12
+ deleteToken,
13
+ getTokens,
14
+ } from "./tokens.controller.js";
15
+ import auth from "../middlewares/auth.js";
16
+ import type { RouteSpec } from "../types/routeSpec";
17
+ import { validateParamsToken } from "../middlewares/validateTokens.js";
18
+
19
+ export const tokensRouteSpecs: RouteSpec[] = [
20
+ {
21
+ method: "get",
22
+ path: "/",
23
+ validate: [auth("manageTokens")],
24
+ requestSchema: {},
25
+ responseSchema: tokenResponseSchema,
26
+ handler: getTokens,
27
+ privateDocs: true,
28
+ summary: "List all tokens of the current user",
29
+ },
30
+ {
31
+ method: "post",
32
+ path: "/",
33
+ validate: [auth("manageTokens")],
34
+ requestSchema: createTokenSchema,
35
+ responseSchema: tokenResponseSchema,
36
+ handler: createToken,
37
+ privateDocs: true,
38
+ summary: "Create a new token",
39
+ },
40
+ {
41
+ method: "get",
42
+ path: "/:tokenId",
43
+ validate: [auth("getTokens"), validateParamsToken],
44
+ requestSchema: getTokenSchema,
45
+ responseSchema: tokenResponseSchema,
46
+ handler: getToken,
47
+ privateDocs: true,
48
+ summary: "Get a token by ID",
49
+ },
50
+ {
51
+ method: "delete",
52
+ path: "/:tokenId",
53
+ validate: [auth("manageTokens"), validateParamsToken],
54
+ requestSchema: deleteTokenSchema,
55
+ responseSchema: tokenResponseSchema,
56
+ handler: deleteToken,
57
+ privateDocs: true,
58
+ summary: "Delete a token by ID",
59
+ },
60
+ ];
61
+
62
+ const router: Router = Router();
63
+
64
+ buildRouterAndDocs(router, tokensRouteSpecs, "/tokens", ["Tokens"]);
65
+
66
+ export default router;
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+
3
+ export const tokenResponseSchema = z.object({
4
+ // unique identifier of the token
5
+ tokenId: z.string().uuid(),
6
+ // the owning user
7
+ userId: z.string().uuid(),
8
+ // optional name or description
9
+ name: z.string().optional(),
10
+ // the token value (never returned after creation in real-world apps)
11
+ token: z.string(),
12
+ // timestamps
13
+ createdAt: z.string().datetime(),
14
+ updatedAt: z.string().datetime(),
15
+ });
@@ -0,0 +1,46 @@
1
+ import crypto from "crypto";
2
+ import type { Document } from "mongoose";
3
+ import Token from "./tokens.model.js";
4
+
5
+ export const queryTokens = async (
6
+ filter: Record<string, any>,
7
+ options: { sortBy?: string; limit?: number; page?: number },
8
+ ): Promise<QueryResult> => {
9
+ return Token.paginate(filter, options);
10
+ };
11
+
12
+ export const createToken = async (
13
+ tokenData: Record<string, any>,
14
+ ): Promise<{ raw: string } & Document> => {
15
+ // 1. Generate raw token
16
+ const rawToken = crypto.randomBytes(32).toString("hex");
17
+ // 2. Hash the token (e.g., using SHA-256)
18
+ const hashedToken = crypto
19
+ .createHash("sha256")
20
+ .update(rawToken)
21
+ .digest("hex");
22
+
23
+ const tokenDataHased = {
24
+ ...tokenData,
25
+ value: hashedToken,
26
+ };
27
+
28
+ const newToken = await Token.create(tokenDataHased);
29
+ return {
30
+ ...newToken.toObject(),
31
+ raw: rawToken, // Return the raw token to the user
32
+ };
33
+ };
34
+
35
+ export const getTokenById = async (
36
+ tokenId: string,
37
+ ): Promise<Document | null> => {
38
+ return Token.findById(tokenId);
39
+ };
40
+
41
+ // Delete a token by ID
42
+ export const deleteTokenById = async (
43
+ tokenId: string,
44
+ ): Promise<Document | null> => {
45
+ return Token.findByIdAndDelete(tokenId);
46
+ };
@@ -0,0 +1,13 @@
1
+ import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";
2
+ import { z } from "zod";
3
+ import { zGet, zDelete } from "../utils/zValidations.js";
4
+
5
+ export const createTokenSchema = {
6
+ body: z.object({
7
+ name: z.string().openapi({ example: "my sample token" }),
8
+ }),
9
+ };
10
+
11
+ export const getTokenSchema = zGet("tokenId");
12
+
13
+ export const deleteTokenSchema = zDelete("tokenId");
@@ -0,0 +1 @@
1
+ export type { RouteSpec } from "../utils/buildRouterAndDocs.js";