diff --git a/.env.connector b/.env.connector index b8d6c1b..a3bfe2f 100644 --- a/.env.connector +++ b/.env.connector @@ -1 +1 @@ -CONNECTOR_VERSION=6.26.2 +CONNECTOR_VERSION=6.27.1 diff --git a/Taskfile.yml b/Taskfile.yml index c0dc773..63c36e5 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -51,7 +51,17 @@ tasks: create-onboarding-pdf-with-custom-png-logo: desc: Gets the onboarding document as PDF cmds: - - 'echo -n ''{"schoolLogo": "{{.LOGO_BASE64}}"}'' | http -b POST localhost:8099/students/{{ .STUDENT_ID }}/onboarding Accept:application/pdf ''X-API-KEY: This_is_a_test_APIKEY_with_30_chars+'' > {{ .STUDENT_ID }}_onboarding.pdf' + - 'echo -n ''{"logo": {"bytes": "{{.LOGO_BASE64}}"}}'' | http -b POST localhost:8099/students/{{ .STUDENT_ID }}/onboarding Accept:application/pdf ''X-API-KEY: This_is_a_test_APIKEY_with_30_chars+'' > {{ .STUDENT_ID }}_onboarding.pdf' + requires: + vars: [STUDENT_ID] + vars: + LOGO_BASE64: + sh: 'cat assets/Logo_GM.png | {{if eq OS "darwin"}}base64{{else}}base64 -w 0{{end}}' + + create-onboarding-pdf-with-custom-values: + desc: Gets the onboarding document as PDF + cmds: + - 'echo -n ''{ "logo": { "bytes": "{{.LOGO_BASE64}}", "x": 15, "y": 15, "maxWidth": 50, "maxHeight": 50 }, "fields": { "salutation": "Moin {{"{{"}}student.givenname{{"}}"}}," } }'' | http -b POST localhost:8099/students/{{ .STUDENT_ID }}/onboarding Accept:application/pdf ''X-API-KEY: This_is_a_test_APIKEY_with_30_chars+'' > {{ .STUDENT_ID }}_onboarding.pdf' requires: vars: [STUDENT_ID] vars: @@ -61,7 +71,7 @@ tasks: create-onboarding-pdf-with-custom-jpg-logo: desc: Gets the onboarding document as PDF cmds: - - 'echo -n ''{"schoolLogo": "{{.LOGO_BASE64}}"}'' | http -b POST localhost:8099/students/{{ .STUDENT_ID }}/onboarding Accept:application/pdf ''X-API-KEY: This_is_a_test_APIKEY_with_30_chars+'' > {{ .STUDENT_ID }}_onboarding.pdf' + - 'echo -n ''{"logo": {"bytes": "{{.LOGO_BASE64}}"}}'' | http -b POST localhost:8099/students/{{ .STUDENT_ID }}/onboarding Accept:application/pdf ''X-API-KEY: This_is_a_test_APIKEY_with_30_chars+'' > {{ .STUDENT_ID }}_onboarding.pdf' requires: vars: [STUDENT_ID] vars: diff --git a/assets/template_onboarding.pdf b/assets/template_onboarding.pdf index 5f33fd7..e1acc36 100644 Binary files a/assets/template_onboarding.pdf and b/assets/template_onboarding.pdf differ diff --git a/package-lock.json b/package-lock.json index 2909dbc..3a44f09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "mustache": "^4.2.0", "pdf-lib": "1.17.1", "qrcode": "1.5.4", - "zod": "^3.24.4", + "zod": "^3.25.7", "zod-validation-error": "^3.4.1" }, "devDependencies": { @@ -17,9 +17,9 @@ "@js-soft/eslint-config-ts": "^1.6.14", "@js-soft/license-check": "^1.0.9", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/connector-types": "^6.26.2", - "@nmshd/typescript-rest": "^3.1.3", - "@types/express": "^5.0.1", + "@nmshd/connector-types": "^6.27.1", + "@nmshd/typescript-rest": "^3.1.4", + "@types/express": "^5.0.2", "@types/luxon": "^3.6.2", "@types/mustache": "^4.2.6", "@types/qrcode": "^1.5.5", @@ -348,9 +348,9 @@ } }, "node_modules/@nmshd/connector-types": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/@nmshd/connector-types/-/connector-types-6.26.2.tgz", - "integrity": "sha512-r92f9rRkjHqxSLcW918VuagJbB95Emv0vXoT86kGgKb15tw7lxxiz+85aiURA9Kuz6mOG+PQySz4bTLY2A+URw==", + "version": "6.27.1", + "resolved": "https://registry.npmjs.org/@nmshd/connector-types/-/connector-types-6.27.1.tgz", + "integrity": "sha512-0u2vwmQU+jbhE06e214zsqm+1mUYkjxuP+k701F1qUAsHNa6f0hnto7feUVlfx3F4RWuQG20nvHse+47YQU+1g==", "dev": true, "license": "MIT", "dependencies": { @@ -359,15 +359,15 @@ "peerDependencies": { "@js-soft/docdb-access-abstractions": "^1.1.0", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/runtime": "^6.45.3", + "@nmshd/runtime": "^6.46.2", "@nmshd/typescript-rest": "^3.1.3", "express": "^5.1.0" } }, "node_modules/@nmshd/consumption": { - "version": "6.45.3", - "resolved": "https://registry.npmjs.org/@nmshd/consumption/-/consumption-6.45.3.tgz", - "integrity": "sha512-OkfAdhVZQRqSEJi1hH/cZgnI7VVOBvJoDhDyR/u0rZ4qn+UxUYRSZQCnU7wfbK4S3lqJ+qnIS5JBWyWE3rTUZA==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@nmshd/consumption/-/consumption-6.47.0.tgz", + "integrity": "sha512-aLrIKV9c9Z1P2h5pGVGCV1PHwlCJzSigrKMy4z0dNJ+au6lU5ZCNXdx8MRBRkF8pb6eaQatX8DIgfX4qukuA8w==", "dev": true, "license": "MIT", "peer": true, @@ -375,33 +375,33 @@ "@js-soft/docdb-querytranslator": "^1.1.5", "@js-soft/ts-serval": "2.0.12", "@js-soft/ts-utils": "2.3.3", - "@nmshd/content": "6.45.3", - "@nmshd/core-types": "6.45.3", + "@nmshd/content": "6.47.0", + "@nmshd/core-types": "6.47.0", "@nmshd/iql": "^1.0.3", - "@nmshd/transport": "6.45.3", + "@nmshd/transport": "6.47.0", "lodash": "^4.17.21", "ts-simple-nameof": "^1.3.1" } }, "node_modules/@nmshd/content": { - "version": "6.45.3", - "resolved": "https://registry.npmjs.org/@nmshd/content/-/content-6.45.3.tgz", - "integrity": "sha512-qStDIXZL1xjqD7QQ09Ue6Om+z6JfkclisCAY3zk8uMmzCkOX4RNRtcLBvUNzTNorkapaf4r0i7sl3uuJkAT8Cw==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@nmshd/content/-/content-6.47.0.tgz", + "integrity": "sha512-ywVmH/kdKBSP+SegTt0lU7bVEXSz9E+lU0e7oMnblJdFI2YUG+0JcyHJ0EwaYPL9v94NqbnEuubss8F2YF2mHg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@js-soft/ts-serval": "2.0.12", - "@nmshd/core-types": "6.45.3", + "@nmshd/core-types": "6.47.0", "@nmshd/iql": "^1.0.3", "luxon": "^3.6.1", "ts-simple-nameof": "^1.3.1" } }, "node_modules/@nmshd/core-types": { - "version": "6.45.3", - "resolved": "https://registry.npmjs.org/@nmshd/core-types/-/core-types-6.45.3.tgz", - "integrity": "sha512-HbctJ/TIGThPsgHpWx6EHuiaVBdBxLSJZt6v+YctuPaxyV46Chr3v1urtj7IYjt8Nbpd+mO2NU4xntKm5gy/qg==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@nmshd/core-types/-/core-types-6.47.0.tgz", + "integrity": "sha512-rZxONGL1P4Y/jrl8EddBfWS1tpC2mVK5P0qzYRE+JxYpDUMEbG4mmD2FlOwjynAkn25EZjn0cMeg4RygnZ8kaA==", "dev": true, "license": "MIT", "peer": true, @@ -434,9 +434,9 @@ "peer": true }, "node_modules/@nmshd/runtime": { - "version": "6.45.3", - "resolved": "https://registry.npmjs.org/@nmshd/runtime/-/runtime-6.45.3.tgz", - "integrity": "sha512-uEvbIGNWNGwmf3PZILNHkiNGyIHqYfxGNvIcSGKYj5Alck7nXvhHRnL95/FsCQe5fK/0mh6fnN0IudttU+XBPQ==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@nmshd/runtime/-/runtime-6.47.0.tgz", + "integrity": "sha512-a5E4VUGAHZBJlUkgz0AlctTch4X0E7/acimRkB752r6w2mpFvRTKJbMu+vTkQ5b/DDd1fU+WsZYKXjMZ6rvbFg==", "dev": true, "license": "MIT", "peer": true, @@ -445,12 +445,12 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/ts-serval": "2.0.12", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/consumption": "6.45.3", - "@nmshd/content": "6.45.3", - "@nmshd/core-types": "6.45.3", + "@nmshd/consumption": "6.47.0", + "@nmshd/content": "6.47.0", + "@nmshd/core-types": "6.47.0", "@nmshd/crypto": "2.1.1", "@nmshd/iql": "^1.0.3", - "@nmshd/transport": "6.45.3", + "@nmshd/transport": "6.47.0", "@nmshd/typescript-ioc": "3.2.4", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", @@ -471,9 +471,9 @@ "peer": true }, "node_modules/@nmshd/transport": { - "version": "6.45.3", - "resolved": "https://registry.npmjs.org/@nmshd/transport/-/transport-6.45.3.tgz", - "integrity": "sha512-IKzSeRr1GDBn3VpdYU2o1pJ9nbiUzNvppF1VD7itUPSS61/Q7ibNKUGPez7Mc1pHCCLsBAgLh4N1FLFYEc+/gA==", + "version": "6.47.0", + "resolved": "https://registry.npmjs.org/@nmshd/transport/-/transport-6.47.0.tgz", + "integrity": "sha512-6taUJg9K7tlUE2i9uEE/WOFDm5/iMKPs4kMmMAKMtZbQim6gN2LX7bmnveWwhRalsshJGryANAdI8NfXg0AdyQ==", "dev": true, "license": "MIT", "peer": true, @@ -482,7 +482,7 @@ "@js-soft/logging-abstractions": "^1.0.1", "@js-soft/simple-logger": "1.0.5", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/core-types": "6.45.3", + "@nmshd/core-types": "6.47.0", "@nmshd/crypto": "2.1.1", "axios": "^1.9.0", "fast-json-patch": "^3.1.1", @@ -541,22 +541,22 @@ "peer": true }, "node_modules/@nmshd/typescript-rest": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@nmshd/typescript-rest/-/typescript-rest-3.1.3.tgz", - "integrity": "sha512-xQilRBZ2UJafvlRSM6fXh30fJD7WdKJNuKCLoRNj7CLhQ4W/JtU6C+uUlslHEMcp3Gwiji90htVk7ykz7DMaew==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@nmshd/typescript-rest/-/typescript-rest-3.1.4.tgz", + "integrity": "sha512-4dDE1AR4aMACqfCGSvEissAFs0tQqRQsJuTe7ZoFPXnLbPzL5x7rlj0/Nb4cdQxtCBbry7adXq8af7kRaNbwlw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "1.19.5", "@types/cookie-parser": "^1.4.8", - "@types/express": "^5.0.1", + "@types/express": "^5.0.2", "@types/multer": "1.4.12", "body-parser": "^2.2.0", "cookie-parser": "^1.4.7", "express": "^5.1.0", "fs-extra": "^11.3.0", "lodash": "^4.17.21", - "multer": "^1.4.5-lts.2", + "multer": "^2.0.0", "reflect-metadata": "^0.2.2", "require-glob": "^4.1.0", "swagger-ui-express": "^5.0.1", @@ -669,9 +669,9 @@ } }, "node_modules/@types/express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", - "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.2.tgz", + "integrity": "sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g==", "dev": true, "license": "MIT", "dependencies": { @@ -3322,9 +3322,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "1.4.5-lts.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", - "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==", "dev": true, "license": "MIT", "dependencies": { @@ -3337,7 +3337,7 @@ "xtend": "^4.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" } }, "node_modules/multer/node_modules/media-typer": { @@ -4892,9 +4892,9 @@ } }, "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "version": "3.25.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.7.tgz", + "integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 2f2f8b6..6e552c9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "mustache": "^4.2.0", "pdf-lib": "1.17.1", "qrcode": "1.5.4", - "zod": "^3.24.4", + "zod": "^3.25.7", "zod-validation-error": "^3.4.1" }, "devDependencies": { @@ -26,9 +26,9 @@ "@js-soft/eslint-config-ts": "^1.6.14", "@js-soft/license-check": "^1.0.9", "@js-soft/ts-utils": "^2.3.3", - "@nmshd/connector-types": "^6.26.2", - "@nmshd/typescript-rest": "^3.1.3", - "@types/express": "^5.0.1", + "@nmshd/connector-types": "^6.27.1", + "@nmshd/typescript-rest": "^3.1.4", + "@types/express": "^5.0.2", "@types/luxon": "^3.6.2", "@types/mustache": "^4.2.6", "@types/qrcode": "^1.5.5", diff --git a/src/StudentsController.ts b/src/StudentsController.ts index b0cfddb..f9b06da 100644 --- a/src/StudentsController.ts +++ b/src/StudentsController.ts @@ -21,7 +21,7 @@ import fs from "node:fs"; import path from "path"; import { PDFDocument, PDFImage } from "pdf-lib"; import qrCodeLib from "qrcode"; -import { SchoolFileDTO, Student, StudentAuditLog, StudentAuditLogEntry, StudentDTO, StudentStatus } from "./types"; +import { PDFLogoSettings, PDFSettings, SchoolFileDTO, Student, StudentAuditLog, StudentAuditLogEntry, StudentDTO, StudentStatus } from "./types"; import { getFileTypeForBuffer } from "./utils/getFileTypeForBuffer"; export class StudentsController { @@ -246,7 +246,7 @@ export class StudentsController { return entries; } - public async getOnboardingDataForStudent(student: Student, schoolLogo?: string): Promise> { + public async getOnboardingDataForStudent(student: Student, pdfSettings: PDFSettings): Promise> { if (!student.correspondingRelationshipTemplateId || !student.givenname || !student.surname) { throw new ApplicationError("error.schoolModule.studentAlreadyDeleted", "The student seems to be already deleted."); } @@ -257,28 +257,12 @@ export class StudentsController { const link = template.value.reference.url; const pngAsBuffer = await qrCodeLib.toBuffer(link, { type: "png" }); - const onboardingPdf = await this.createOnboardingPDF( - { - organizationDisplayName: (this.displayName.content.value as DisplayNameJSON).value, - givenname: student.givenname, - surname: student.surname, - schoolLogo: schoolLogo - }, - pngAsBuffer - ); + const onboardingPdf = await this.createOnboardingPDF(student, pdfSettings, pngAsBuffer); return Result.ok({ link: link, png: pngAsBuffer, pdf: onboardingPdf }); } - private async createOnboardingPDF( - data: { - organizationDisplayName: string; - givenname: string; - surname: string; - schoolLogo?: string; - }, - pngAsBuffer: Buffer - ) { + private async createOnboardingPDF(student: Student, settings: PDFSettings, pngAsBuffer: Buffer) { const templateName = "template_onboarding.pdf"; const pathToPdf = path.resolve(path.join(this.assetsLocation, templateName)); @@ -295,13 +279,27 @@ export class StudentsController { const qrImage = await pdfDoc.embedPng(pngAsBuffer); const form = pdfDoc.getForm(); - form.getTextField("Vorname_Nachname").setText(`${data.givenname} ${data.surname}`); - form.getTextField("Schulname_01").setText(data.organizationDisplayName); - form.getTextField("Schulname_02").setText(data.organizationDisplayName); - form.getTextField("Ort_Datum").setText(""); - form.getTextField("QR_Code_Schueler").setImage(qrImage); + const fields = { + schoolname: "{{organization.displayName}}", + salutation: `Guten Tag {{student.givenname}} {{student.surname}},`, + greeting: "{{organization.displayName}}", + // eslint-disable-next-line @typescript-eslint/naming-convention + place_date: "", + ...settings.fields + }; + + const formFields = form.getFields(); + + for (const [key, value] of Object.entries(fields)) { + if (!formFields.some((field) => field.getName() === key)) continue; - await this.embedImage(pdfDoc, data.schoolLogo); + const templatedValue = await this.fillTemplateStringWithStudentAndOrganizationData(student, value); + form.getTextField(key).setText(templatedValue); + } + + form.getTextField("qr_code").setImage(qrImage); + + await this.embedImage(pdfDoc, settings.logo); try { form.flatten(); @@ -320,8 +318,8 @@ export class StudentsController { return Buffer.from(pdfBytes); } - private async embedImage(pdfDoc: PDFDocument, schoolLogoBase64?: string) { - const image = await this.getImage(pdfDoc, schoolLogoBase64); + private async embedImage(pdfDoc: PDFDocument, logo: PDFLogoSettings = {}) { + const image = await this.getImage(pdfDoc, logo.bytes); if (!image) return; const page = pdfDoc.getPage(0); @@ -329,17 +327,25 @@ export class StudentsController { // 25.5mm / 72DPI const pointsPerMillimeter = 0.353; - const pagePaddingInMillimeter = 15; - const pagePaddingInPoints = pagePaddingInMillimeter / pointsPerMillimeter; + const yInMillis = logo.y ?? 15; + const yInPoints = yInMillis / pointsPerMillimeter; + + const xInMillis = logo.x ?? 15; + const xInPoints = xInMillis / pointsPerMillimeter; + + const userDefinedMaxWidth = logo.maxWidth !== undefined ? logo.maxWidth / pointsPerMillimeter : undefined; + const userDefinedMaxHeight = logo.maxHeight !== undefined ? logo.maxHeight / pointsPerMillimeter : undefined; - const availableHorizontalSpace = page.getWidth() - pagePaddingInPoints * 2; - const maxWidth = (availableHorizontalSpace / 5) * 2; - const maxHeight = 80; + const availableHorizontalSpace = page.getWidth() - yInPoints * 2; + const calulatedMaxWidth = (availableHorizontalSpace / 5) * 2; + + const maxWidth = userDefinedMaxWidth ?? calulatedMaxWidth; + const maxHeight = userDefinedMaxHeight ?? 80; const scale = image.scaleToFit(maxWidth, maxHeight); page.drawImage(image, { - x: pagePaddingInPoints, - y: page.getHeight() - scale.height - pagePaddingInPoints, + x: xInPoints, + y: page.getHeight() - scale.height - yInPoints, height: scale.height, width: scale.width }); @@ -493,9 +499,10 @@ export class StudentsController { public async sendMail(student: Student, rawSubject: string, rawBody: string, additionalData: any = {}): Promise { if (!student.correspondingRelationshipId) throw new ApplicationError("error.schoolModule.noRelationship", "The student has no relationship."); + if (!student.correspondingRelationshipTemplateId) throw new ApplicationError("error.schoolModule.studentAlreadyDeleted", "The student seems to be already deleted."); - const subject = await this.fillMailTemplateWithStudentData(student, rawSubject, additionalData); - const body = await this.fillMailTemplateWithStudentData(student, rawBody, additionalData); + const subject = await this.fillTemplateStringWithStudentAndOrganizationData(student, rawSubject, additionalData); + const body = await this.fillTemplateStringWithStudentAndOrganizationData(student, rawBody, additionalData); const relationship = await this.services.transportServices.relationships.getRelationship({ id: student.correspondingRelationshipId.toString() }); @@ -507,26 +514,17 @@ export class StudentsController { return result.value; } - private async fillMailTemplateWithStudentData(student: Student, template: string, additionalData: any = {}): Promise { - if (!student.correspondingRelationshipTemplateId) { - throw new ApplicationError("error.schoolModule.studentAlreadyDeleted", "The student seems to be already deleted."); - } - - if (!student.correspondingRelationshipId) { - throw new ApplicationError("error.schoolModule.noRelationship", "The student has no relationship."); - } - - const relationship = await this.getRelationship(student.correspondingRelationshipId); - if (relationship.status !== RelationshipStatus.Active) { - throw new ApplicationError("error.schoolModule.noActiveRelationship", "The relationship to the student is not active, so sending a mail is not possible."); - } - - const contact = await this.services.dataViewExpander.expandAddress(relationship.peer); + private async fillTemplateStringWithStudentAndOrganizationData(student: Student, template: string, additionalData: any = {}): Promise { + const relationship = student.correspondingRelationshipId ? await this.getRelationship(student.correspondingRelationshipId) : undefined; + const contact = relationship && relationship.status !== RelationshipStatus.Active ? await this.services.dataViewExpander.expandAddress(relationship.peer) : undefined; const data = { student: { - givenname: contact.relationship?.nameMap["GivenName"] ?? student.givenname, - surname: contact.relationship?.nameMap["Surname"] ?? student.surname + givenname: contact?.relationship?.nameMap["GivenName"] ?? student.givenname, + surname: contact?.relationship?.nameMap["Surname"] ?? student.surname + }, + organization: { + displayName: (this.displayName.content.value as DisplayNameJSON).value }, requestBody: additionalData }; diff --git a/src/controllers/StudentsRESTController.ts b/src/controllers/StudentsRESTController.ts index 8cdb7b0..7f1d314 100644 --- a/src/controllers/StudentsRESTController.ts +++ b/src/controllers/StudentsRESTController.ts @@ -89,7 +89,7 @@ export class StudentsRESTController extends BaseController { const student = await this.studentsController.getStudent(id); if (!student) throw RuntimeErrors.general.recordNotFound(Student); - const result = await this.studentsController.getOnboardingDataForStudent(student, data.schoolLogo); + const result = await this.studentsController.getOnboardingDataForStudent(student, data); return this.file( result, (r) => r.value.pdf, @@ -107,7 +107,7 @@ export class StudentsRESTController extends BaseController { const student = await this.studentsController.getStudent(id); if (!student) throw RuntimeErrors.general.recordNotFound(Student); - const result = await this.studentsController.getOnboardingDataForStudent(student); + const result = await this.studentsController.getOnboardingDataForStudent(student, {}); switch (accept) { case "application/pdf": diff --git a/src/controllers/schemas.ts b/src/controllers/schemas.ts index 431baa0..57f30f0 100644 --- a/src/controllers/schemas.ts +++ b/src/controllers/schemas.ts @@ -22,7 +22,16 @@ export const createStudentRequestSchema = z.object({ }); export const createStudentOnboardingPDFSchema = z.object({ - schoolLogo: z.string().base64() + logo: z + .object({ + bytes: z.string().base64().optional(), + x: z.number().min(0).optional(), + y: z.number().min(0).optional(), + maxWidth: z.number().min(0).optional(), + maxHeight: z.number().min(0).optional() + }) + .optional(), + fields: z.record(z.string()).optional() }); export const sendMailRequestSchema = z.object({ diff --git a/src/types/PDFSettings.ts b/src/types/PDFSettings.ts new file mode 100644 index 0000000..dbffe11 --- /dev/null +++ b/src/types/PDFSettings.ts @@ -0,0 +1,12 @@ +export interface PDFSettings { + logo?: PDFLogoSettings; + fields?: Record; +} + +export interface PDFLogoSettings { + bytes?: string; + x?: number; + y?: number; + maxWidth?: number; + maxHeight?: number; +} diff --git a/src/types/index.ts b/src/types/index.ts index 27c9619..8167754 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ +export * from "./PDFSettings"; export * from "./SchoolFileDTO"; export * from "./Student"; export * from "./StudentAuditLog";