@internetderdinge/api 1.229.32 → 1.229.39
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/dist/src/accounts/accounts.route.js +18 -5
- package/dist/src/admin/adminSearch.controller.js +0 -19
- package/dist/src/admin/adminSearch.route.js +5 -5
- package/dist/src/devices/devices.controller.js +4 -6
- package/dist/src/devices/devices.route.js +14 -14
- package/dist/src/devices/devices.validation.js +15 -54
- package/dist/src/email/email.service.js +6 -6
- package/dist/src/index.js +5 -1
- package/dist/src/iotdevice/iotdevice.route.js +3 -1
- package/dist/src/iotdevice/iotdevice.service.js +5 -5
- package/dist/src/iotdevice/iotdevice.validation.js +12 -4
- package/dist/src/middlewares/validateAdminOrSupport.js +20 -0
- package/dist/src/organizations/organizations.controller.js +17 -6
- package/dist/src/organizations/organizations.route.js +2 -1
- package/dist/src/users/users.model.js +4 -2
- package/dist/src/users/users.route.js +2 -2
- package/dist/src/users/users.service.js +26 -10
- package/dist/src/users/users.validation.js +60 -61
- package/dist/src/utils/buildRouterAndDocs.js +7 -4
- package/dist/src/utils/zValidations.js +7 -0
- package/package.json +5 -2
- package/scripts/release-and-sync-paperless.mjs +21 -2
- package/scripts/release-version.mjs +21 -2
- package/src/accounts/accounts.route.ts +21 -5
- package/src/admin/adminSearch.controller.ts +0 -35
- package/src/admin/adminSearch.route.ts +8 -6
- package/src/admin/adminSearch.service.ts +6 -10
- package/src/devices/devices.controller.ts +4 -12
- package/src/devices/devices.route.ts +14 -15
- package/src/devices/devices.validation.ts +20 -54
- package/src/email/email.service.ts +15 -7
- package/src/index.ts +5 -1
- package/src/iotdevice/iotdevice.route.ts +3 -1
- package/src/iotdevice/iotdevice.service.ts +8 -7
- package/src/iotdevice/iotdevice.validation.ts +12 -4
- package/src/middlewares/validateAdminOrSupport.ts +34 -0
- package/src/organizations/organizations.controller.ts +38 -7
- package/src/organizations/organizations.route.ts +3 -1
- package/src/users/users.model.ts +7 -3
- package/src/users/users.route.ts +3 -2
- package/src/users/users.service.ts +50 -14
- package/src/users/users.validation.ts +62 -60
- package/src/utils/buildRouterAndDocs.ts +14 -5
- package/src/utils/zValidations.ts +8 -0
- package/dist/src/pdf/pdf.controller.js +0 -24
- package/dist/src/pdf/pdf.route.js +0 -22
- package/dist/src/pdf/pdf.schemas.js +0 -6
- package/dist/src/pdf/pdf.service.js +0 -64
- package/dist/src/pdf/pdf.validation.js +0 -27
- package/src/pdf/pdf.controller.ts +0 -35
- package/src/pdf/pdf.route.ts +0 -28
- package/src/pdf/pdf.schemas.ts +0 -7
- package/src/pdf/pdf.service.ts +0 -103
- package/src/pdf/pdf.validation.ts +0 -30
|
@@ -14,37 +14,39 @@ import {
|
|
|
14
14
|
|
|
15
15
|
extendZodWithOpenApi(z);
|
|
16
16
|
|
|
17
|
-
export const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
email: z.string().email().optional().nullable().openapi({
|
|
31
|
-
example: "user@example.com",
|
|
32
|
-
description: "User email address",
|
|
33
|
-
}),
|
|
34
|
-
timezone: z.string().optional().openapi({
|
|
35
|
-
example: "Europe/Berlin",
|
|
36
|
-
description: "IANA timezone string",
|
|
37
|
-
}),
|
|
38
|
-
role: z.enum(["user", "admin", "patient", "onlyself"]).optional().openapi({
|
|
39
|
-
description: "Role assigned to the user",
|
|
17
|
+
export const userAppsSchema = z
|
|
18
|
+
.record(z.string(), z.unknown())
|
|
19
|
+
.optional()
|
|
20
|
+
.openapi({ description: "Application-specific user fields" });
|
|
21
|
+
|
|
22
|
+
export const createUserBodyShape = {
|
|
23
|
+
meta: z
|
|
24
|
+
.object({})
|
|
25
|
+
.passthrough()
|
|
26
|
+
.optional()
|
|
27
|
+
.openapi({
|
|
28
|
+
example: { key: "value" },
|
|
29
|
+
description: "Additional metadata for the user",
|
|
40
30
|
}),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
apps: userAppsSchema,
|
|
32
|
+
organization: zObjectId.openapi({
|
|
33
|
+
description: "Organization ObjectId",
|
|
34
|
+
}),
|
|
35
|
+
email: z.string().email().optional().nullable().openapi({
|
|
36
|
+
example: "user@example.com",
|
|
37
|
+
description: "User email address",
|
|
38
|
+
}),
|
|
39
|
+
timezone: z.string().optional().openapi({
|
|
40
|
+
example: "Europe/Berlin",
|
|
41
|
+
description: "IANA timezone string",
|
|
47
42
|
}),
|
|
43
|
+
role: z.enum(["user", "admin", "patient", "onlyself"]).optional().openapi({
|
|
44
|
+
description: "Role assigned to the user",
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const createUserSchema = {
|
|
49
|
+
body: z.object(createUserBodyShape),
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export const createCurrentUserSchema = createUserSchema;
|
|
@@ -69,39 +71,39 @@ export const getCurrentUserSchema = {
|
|
|
69
71
|
}),
|
|
70
72
|
};
|
|
71
73
|
|
|
74
|
+
export const updateUserBodyShape = {
|
|
75
|
+
name: z.string().optional().openapi({ description: "User full name" }),
|
|
76
|
+
timezone: z.string().optional().openapi({ description: "IANA timezone" }),
|
|
77
|
+
avatar: z.string().optional().openapi({ description: "Avatar URL" }),
|
|
78
|
+
meta: z
|
|
79
|
+
.object({})
|
|
80
|
+
.passthrough()
|
|
81
|
+
.optional()
|
|
82
|
+
.openapi({ description: "Additional metadata" }),
|
|
83
|
+
apps: userAppsSchema,
|
|
84
|
+
email: z
|
|
85
|
+
.string()
|
|
86
|
+
.email()
|
|
87
|
+
.nullable()
|
|
88
|
+
.optional()
|
|
89
|
+
.openapi({ description: "User email address" }),
|
|
90
|
+
role: z
|
|
91
|
+
.enum(["user", "admin", "patient", "onlyself"])
|
|
92
|
+
.optional()
|
|
93
|
+
.openapi({ description: "User role" }),
|
|
94
|
+
inviteCode: z
|
|
95
|
+
.string()
|
|
96
|
+
.nullable()
|
|
97
|
+
.optional()
|
|
98
|
+
.openapi({ description: "Invite code" }),
|
|
99
|
+
organization: zObjectId
|
|
100
|
+
.optional()
|
|
101
|
+
.openapi({ description: "Organization ObjectId" }),
|
|
102
|
+
};
|
|
103
|
+
|
|
72
104
|
export const updateUserSchema = {
|
|
73
105
|
...zUpdate("userId"),
|
|
74
|
-
body: zPatchBody(
|
|
75
|
-
name: z.string().optional().openapi({ description: "User full name" }),
|
|
76
|
-
timezone: z.string().optional().openapi({ description: "IANA timezone" }),
|
|
77
|
-
avatar: z.string().optional().openapi({ description: "Avatar URL" }),
|
|
78
|
-
meta: z
|
|
79
|
-
.object({})
|
|
80
|
-
.optional()
|
|
81
|
-
.openapi({ description: "Additional metadata" }),
|
|
82
|
-
category: z
|
|
83
|
-
.enum(["doctor", "nurse", "patient", "pharmacist", "relative"])
|
|
84
|
-
.optional()
|
|
85
|
-
.openapi({ description: "User category" }),
|
|
86
|
-
email: z
|
|
87
|
-
.string()
|
|
88
|
-
.email()
|
|
89
|
-
.nullable()
|
|
90
|
-
.optional()
|
|
91
|
-
.openapi({ description: "User email address" }),
|
|
92
|
-
role: z
|
|
93
|
-
.enum(["user", "admin", "patient", "onlyself"])
|
|
94
|
-
.optional()
|
|
95
|
-
.openapi({ description: "User role" }),
|
|
96
|
-
inviteCode: z
|
|
97
|
-
.string()
|
|
98
|
-
.nullable()
|
|
99
|
-
.optional()
|
|
100
|
-
.openapi({ description: "Invite code" }),
|
|
101
|
-
organization: zObjectId
|
|
102
|
-
.optional()
|
|
103
|
-
.openapi({ description: "Organization ObjectId" }),
|
|
104
|
-
}),
|
|
106
|
+
body: zPatchBody(updateUserBodyShape),
|
|
105
107
|
};
|
|
106
108
|
|
|
107
109
|
export const deleteUserSchema = zDelete("userId");
|
|
@@ -30,16 +30,25 @@ export type RouteSpec = {
|
|
|
30
30
|
summary: string;
|
|
31
31
|
description?: string;
|
|
32
32
|
privateDocs?: boolean;
|
|
33
|
-
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BuildRouterAndDocsOptions = {
|
|
36
|
+
routeSpecs?: (specs: RouteSpec[]) => RouteSpec[];
|
|
37
|
+
includeInDocs?: (spec: RouteSpec) => boolean;
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
export default function buildAiRouterAndDocs(
|
|
37
41
|
router: Router,
|
|
38
|
-
routeSpecs:
|
|
42
|
+
routeSpecs: RouteSpec[],
|
|
39
43
|
basePath = "/",
|
|
40
44
|
tags: string[] = [],
|
|
45
|
+
options: BuildRouterAndDocsOptions = {},
|
|
41
46
|
) {
|
|
42
|
-
|
|
47
|
+
const effectiveRouteSpecs = options.routeSpecs
|
|
48
|
+
? options.routeSpecs(routeSpecs)
|
|
49
|
+
: routeSpecs;
|
|
50
|
+
|
|
51
|
+
effectiveRouteSpecs.forEach((spec) => {
|
|
43
52
|
const validate = spec.validate || [];
|
|
44
53
|
const routeMiddleware =
|
|
45
54
|
spec.validateWithRequestSchema ||
|
|
@@ -68,7 +77,7 @@ export default function buildAiRouterAndDocs(
|
|
|
68
77
|
spec.responseSchema &&
|
|
69
78
|
!hasRoleValidation(spec.validateWithRequestSchema || validate) &&
|
|
70
79
|
spec.privateDocs !== true &&
|
|
71
|
-
|
|
80
|
+
(options.includeInDocs ? options.includeInDocs(spec) : true)
|
|
72
81
|
) {
|
|
73
82
|
// collect all middleware fn names (falls back to '<anonymous>' if unnamed)
|
|
74
83
|
const middlewareNames = (spec.validateWithRequestSchema || validate).map(
|
|
@@ -96,7 +105,7 @@ export default function buildAiRouterAndDocs(
|
|
|
96
105
|
// (optionally) expose them as a custom extension instead:
|
|
97
106
|
"x-middlewares": middlewareNames,
|
|
98
107
|
|
|
99
|
-
security: [{ [
|
|
108
|
+
security: [{ [xApiKey.name]: [] }, { [bearerAuth.name]: [] }],
|
|
100
109
|
responses: {
|
|
101
110
|
200: {
|
|
102
111
|
description: "Object with user data.",
|
|
@@ -158,3 +158,11 @@ export const zDelete = (id: string) => ({
|
|
|
158
158
|
export const zObjectId = zObjectIdFor();
|
|
159
159
|
|
|
160
160
|
export const zDate = () => z.string().pipe(z.coerce.date());
|
|
161
|
+
|
|
162
|
+
export const zTypeFilter = z
|
|
163
|
+
.string()
|
|
164
|
+
.openapi({
|
|
165
|
+
description: "Event type filter. Common values include activate and state; any other string is accepted.",
|
|
166
|
+
example: "activate",
|
|
167
|
+
})
|
|
168
|
+
.optional();
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import httpStatus from "http-status";
|
|
2
|
-
import catchAsync from "../utils/catchAsync.js";
|
|
3
|
-
import pdfService from "./pdf.service.js";
|
|
4
|
-
import ApiError from "../utils/ApiError.js";
|
|
5
|
-
export const generatePdfFromUrl = catchAsync(async (req, res) => {
|
|
6
|
-
const fileName = "memo-print";
|
|
7
|
-
const authHeader = req.headers["authorization"];
|
|
8
|
-
if (!authHeader) {
|
|
9
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing Authorization header");
|
|
10
|
-
}
|
|
11
|
-
const token = authHeader.split(" ")[1];
|
|
12
|
-
if (!token) {
|
|
13
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing bearer token");
|
|
14
|
-
}
|
|
15
|
-
const urlPath = typeof req.query.urlPath === "string" ? req.query.urlPath : undefined;
|
|
16
|
-
if (!urlPath) {
|
|
17
|
-
throw new ApiError(httpStatus.BAD_REQUEST, "Missing urlPath query parameter");
|
|
18
|
-
}
|
|
19
|
-
const result = await pdfService.generatePdfFromUrl({ urlPath, token });
|
|
20
|
-
res.status(httpStatus.CREATED).send({ signed: result });
|
|
21
|
-
});
|
|
22
|
-
export default {
|
|
23
|
-
generatePdfFromUrl,
|
|
24
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
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 auth from "../middlewares/auth.js";
|
|
7
|
-
export const pdfRouteSpecs = [
|
|
8
|
-
{
|
|
9
|
-
method: "get",
|
|
10
|
-
path: "/",
|
|
11
|
-
validate: [auth("manageUsers")],
|
|
12
|
-
requestSchema: generatePdfSchema,
|
|
13
|
-
responseSchema: pdfResponseSchema,
|
|
14
|
-
handler: generatePdfFromUrl,
|
|
15
|
-
summary: "Generate a PDF from a provided URL",
|
|
16
|
-
description: "This endpoint allows users to generate a PDF document from a specified URL.",
|
|
17
|
-
memoOnly: true,
|
|
18
|
-
},
|
|
19
|
-
];
|
|
20
|
-
const router = Router();
|
|
21
|
-
buildRouterAndDocs(router, pdfRouteSpecs, "/pdf", ["PDF"]);
|
|
22
|
-
export default router;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import puppeteer from "puppeteer";
|
|
2
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import { GetObjectCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
|
4
|
-
import { getSignedUrl as getS3SignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
5
|
-
import path from "path";
|
|
6
|
-
const s3 = new S3Client({
|
|
7
|
-
region: process.env.AWS_REGION,
|
|
8
|
-
credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
|
|
9
|
-
? {
|
|
10
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
11
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
12
|
-
}
|
|
13
|
-
: undefined,
|
|
14
|
-
});
|
|
15
|
-
// Function to upload a file to S3
|
|
16
|
-
const uploadBuffer = async (buffer, fileName) => {
|
|
17
|
-
await s3.send(new PutObjectCommand({
|
|
18
|
-
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
19
|
-
Key: fileName,
|
|
20
|
-
Body: buffer,
|
|
21
|
-
ContentType: "application/pdf",
|
|
22
|
-
}));
|
|
23
|
-
};
|
|
24
|
-
// Generate a signed URL
|
|
25
|
-
const generateSignedUrl = async (fileName) => {
|
|
26
|
-
return getS3SignedUrl(s3, new GetObjectCommand({
|
|
27
|
-
Bucket: process.env.AWS_S3_BUCKET_NAME,
|
|
28
|
-
Key: fileName,
|
|
29
|
-
}), { expiresIn: 60 * 60 });
|
|
30
|
-
};
|
|
31
|
-
const generatePdfFromUrl = async ({ urlPath, token, }) => {
|
|
32
|
-
const domain = process.env.FRONTEND_URL;
|
|
33
|
-
const browser = await puppeteer.launch({
|
|
34
|
-
defaultViewport: {
|
|
35
|
-
width: 1300,
|
|
36
|
-
height: 1200,
|
|
37
|
-
deviceScaleFactor: 1,
|
|
38
|
-
},
|
|
39
|
-
executablePath: process.env.CHROME_BIN,
|
|
40
|
-
args: ["--no-sandbox"],
|
|
41
|
-
});
|
|
42
|
-
const page = await browser.newPage();
|
|
43
|
-
await page.goto(domain);
|
|
44
|
-
// Set the token in local storage
|
|
45
|
-
await page.evaluate((token) => {
|
|
46
|
-
localStorage.setItem("print-token", token);
|
|
47
|
-
}, token);
|
|
48
|
-
await page.goto(domain + urlPath, { waitUntil: "networkidle2" });
|
|
49
|
-
await page.waitForSelector(".pdf-render-complete");
|
|
50
|
-
const pdf = await page.pdf({ format: "A4", printBackground: true });
|
|
51
|
-
await page.evaluate(() => {
|
|
52
|
-
localStorage.setItem("print-token", "");
|
|
53
|
-
});
|
|
54
|
-
await browser.close();
|
|
55
|
-
const fileName = `download-${uuidv4()}.pdf`;
|
|
56
|
-
await uploadBuffer(Buffer.from(pdf), fileName);
|
|
57
|
-
console.log(`File uploaded successfully. File Name: ${fileName}`);
|
|
58
|
-
const signedUrl = await generateSignedUrl(path.basename(fileName));
|
|
59
|
-
console.log(`Signed URL: ${signedUrl}`);
|
|
60
|
-
return signedUrl;
|
|
61
|
-
};
|
|
62
|
-
export default {
|
|
63
|
-
generatePdfFromUrl,
|
|
64
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import httpStatus from "http-status";
|
|
2
|
-
import catchAsync from "../utils/catchAsync.js";
|
|
3
|
-
import pdfService from "./pdf.service.js";
|
|
4
|
-
import ApiError from "../utils/ApiError.js";
|
|
5
|
-
|
|
6
|
-
export const generatePdfFromUrl = catchAsync(async (req, res) => {
|
|
7
|
-
const fileName = "memo-print";
|
|
8
|
-
|
|
9
|
-
const authHeader = req.headers["authorization"];
|
|
10
|
-
if (!authHeader) {
|
|
11
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing Authorization header");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const token = authHeader.split(" ")[1];
|
|
15
|
-
if (!token) {
|
|
16
|
-
throw new ApiError(httpStatus.UNAUTHORIZED, "Missing bearer token");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const urlPath =
|
|
20
|
-
typeof req.query.urlPath === "string" ? req.query.urlPath : undefined;
|
|
21
|
-
if (!urlPath) {
|
|
22
|
-
throw new ApiError(
|
|
23
|
-
httpStatus.BAD_REQUEST,
|
|
24
|
-
"Missing urlPath query parameter",
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const result = await pdfService.generatePdfFromUrl({ urlPath, token });
|
|
29
|
-
|
|
30
|
-
res.status(httpStatus.CREATED).send({ signed: result });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default {
|
|
34
|
-
generatePdfFromUrl,
|
|
35
|
-
};
|
package/src/pdf/pdf.route.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
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;
|
package/src/pdf/pdf.schemas.ts
DELETED
package/src/pdf/pdf.service.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import puppeteer from "puppeteer";
|
|
2
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import {
|
|
4
|
-
GetObjectCommand,
|
|
5
|
-
PutObjectCommand,
|
|
6
|
-
S3Client,
|
|
7
|
-
} from "@aws-sdk/client-s3";
|
|
8
|
-
import { getSignedUrl as getS3SignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
9
|
-
import path from "path";
|
|
10
|
-
|
|
11
|
-
const s3 = new S3Client({
|
|
12
|
-
region: process.env.AWS_REGION,
|
|
13
|
-
credentials:
|
|
14
|
-
process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY
|
|
15
|
-
? {
|
|
16
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
17
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
18
|
-
}
|
|
19
|
-
: undefined,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// Function to upload a file to S3
|
|
23
|
-
const uploadBuffer = async (
|
|
24
|
-
buffer: Buffer,
|
|
25
|
-
fileName: string,
|
|
26
|
-
): Promise<void> => {
|
|
27
|
-
await s3.send(
|
|
28
|
-
new PutObjectCommand({
|
|
29
|
-
Bucket: process.env.AWS_S3_BUCKET_NAME!,
|
|
30
|
-
Key: fileName,
|
|
31
|
-
Body: buffer,
|
|
32
|
-
ContentType: "application/pdf",
|
|
33
|
-
}),
|
|
34
|
-
);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Generate a signed URL
|
|
38
|
-
const generateSignedUrl = async (fileName: string): Promise<string> => {
|
|
39
|
-
return getS3SignedUrl(
|
|
40
|
-
s3,
|
|
41
|
-
new GetObjectCommand({
|
|
42
|
-
Bucket: process.env.AWS_S3_BUCKET_NAME!,
|
|
43
|
-
Key: fileName,
|
|
44
|
-
}),
|
|
45
|
-
{ expiresIn: 60 * 60 },
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
interface GeneratePdfOptions {
|
|
50
|
-
urlPath: string;
|
|
51
|
-
token: string;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const generatePdfFromUrl = async ({
|
|
55
|
-
urlPath,
|
|
56
|
-
token,
|
|
57
|
-
}: GeneratePdfOptions): Promise<string> => {
|
|
58
|
-
const domain = process.env.FRONTEND_URL!;
|
|
59
|
-
const browser = await puppeteer.launch({
|
|
60
|
-
defaultViewport: {
|
|
61
|
-
width: 1300,
|
|
62
|
-
height: 1200,
|
|
63
|
-
deviceScaleFactor: 1,
|
|
64
|
-
},
|
|
65
|
-
executablePath: process.env.CHROME_BIN,
|
|
66
|
-
args: ["--no-sandbox"],
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const page = await browser.newPage();
|
|
70
|
-
|
|
71
|
-
await page.goto(domain);
|
|
72
|
-
|
|
73
|
-
// Set the token in local storage
|
|
74
|
-
await page.evaluate((token) => {
|
|
75
|
-
localStorage.setItem("print-token", token);
|
|
76
|
-
}, token);
|
|
77
|
-
|
|
78
|
-
await page.goto(domain + urlPath, { waitUntil: "networkidle2" });
|
|
79
|
-
|
|
80
|
-
await page.waitForSelector(".pdf-render-complete");
|
|
81
|
-
|
|
82
|
-
const pdf = await page.pdf({ format: "A4", printBackground: true });
|
|
83
|
-
|
|
84
|
-
await page.evaluate(() => {
|
|
85
|
-
localStorage.setItem("print-token", "");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await browser.close();
|
|
89
|
-
|
|
90
|
-
const fileName = `download-${uuidv4()}.pdf`;
|
|
91
|
-
await uploadBuffer(Buffer.from(pdf), fileName);
|
|
92
|
-
|
|
93
|
-
console.log(`File uploaded successfully. File Name: ${fileName}`);
|
|
94
|
-
|
|
95
|
-
const signedUrl = await generateSignedUrl(path.basename(fileName));
|
|
96
|
-
console.log(`Signed URL: ${signedUrl}`);
|
|
97
|
-
|
|
98
|
-
return signedUrl;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
export default {
|
|
102
|
-
generatePdfFromUrl,
|
|
103
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
};
|