From 591d687550bba829cb3f5ade03b0c39f5017bb55 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Mon, 11 Nov 2024 15:44:44 +0100
Subject: [PATCH 01/14] add email without button
---
backend/authStrategies/magiclogin.ts | 2 +-
backend/mail/mail.ts | 32 ++++++++++++++--------------
backend/retentionpolicy.ts | 2 +-
backend/templates/mail.ejs | 2 ++
4 files changed, 20 insertions(+), 18 deletions(-)
diff --git a/backend/authStrategies/magiclogin.ts b/backend/authStrategies/magiclogin.ts
index 94c00e4d..1dbf335f 100644
--- a/backend/authStrategies/magiclogin.ts
+++ b/backend/authStrategies/magiclogin.ts
@@ -21,7 +21,7 @@ export default new MagicLoginStrategy.default({
i18n.t('mail.magiclogin.subject', { lng: user.settings.language }),
i18n.t('mail.magiclogin.paragraph', { lng: user.settings.language }),
{ text: i18n.t('mail.magiclogin.buttonText', { lng: user.settings.language }), link: href },
- '',
+ undefined,
false
)
} else {
diff --git a/backend/mail/mail.ts b/backend/mail/mail.ts
index 87a9b66d..c0192b9c 100644
--- a/backend/mail/mail.ts
+++ b/backend/mail/mail.ts
@@ -30,20 +30,23 @@ export async function sendMail(
recipients: IUser[],
subject: string,
paragraph: string,
- button: { text: string; link: string },
- lastParagraph: string,
+ button?: { text: string; link: string },
+ lastParagraph?: string,
authenticateLink = true
) {
for (let i = 0; i < recipients.length; i++) {
const language = recipients[i].settings.language
- const recipientButton = { ...button }
- if (authenticateLink && recipients[i].fk.magiclogin && recipientButton.link.startsWith(process.env.VITE_FRONTEND_URL)) {
- recipientButton.link = await genAuthenticatedLink({
- destination: recipients[i].fk.magiclogin!,
- redirect: recipientButton.link.substring(process.env.VITE_FRONTEND_URL.length)
- })
+ let recipientButton: { text: string; link: string } | undefined = undefined
+ if (button) {
+ recipientButton = { ...button }
+ if (authenticateLink && recipients[i].fk.magiclogin && recipientButton.link.startsWith(process.env.VITE_FRONTEND_URL)) {
+ recipientButton.link = await genAuthenticatedLink({
+ destination: recipients[i].fk.magiclogin!,
+ redirect: recipientButton.link.substring(process.env.VITE_FRONTEND_URL.length)
+ })
+ }
}
- _sendMail(recipients[i], subject, paragraph, recipientButton, lastParagraph, language)
+ _sendMail(recipients[i], subject, paragraph, language, recipientButton, lastParagraph)
}
}
@@ -51,9 +54,9 @@ async function _sendMail(
recipient: IUser,
subject: string,
paragraph: string,
- button: { text: string; link: string },
- lastParagraph: string,
- language: Locale
+ language: Locale,
+ button?: { text: string; link: string },
+ lastParagraph?: string
) {
const mailClient = await getClient()
const salutation = i18n.t('mail.hiX', { lng: language, X: recipient.name.givenName })
@@ -77,10 +80,7 @@ async function _sendMail(
'\n\n' +
paragraph +
'\n\n' +
- button.text +
- ': ' +
- button.link +
- '\n\n' +
+ (button ? button.text + ': ' + button.link + '\n\n' : '') +
lastParagraph +
'\n\n' +
regards +
diff --git a/backend/retentionpolicy.ts b/backend/retentionpolicy.ts
index 0306cd09..34ec9e0f 100644
--- a/backend/retentionpolicy.ts
+++ b/backend/retentionpolicy.ts
@@ -134,7 +134,7 @@ async function sendNotificationMails(report: ITravel | IExpenseReport | IHealthC
const subject = i18n.t(`mail.${reportType}.${report.state}DeletedSoon.subject`, interpolation)
const paragraph = i18n.t(`mail.${reportType}.${report.state}DeletedSoon.paragraph`, interpolation)
- await sendMail(recipients, subject, paragraph, button, '')
+ await sendMail(recipients, subject, paragraph, button)
}
}
}
diff --git a/backend/templates/mail.ejs b/backend/templates/mail.ejs
index 06ddc8c6..ba9186f3 100644
--- a/backend/templates/mail.ejs
+++ b/backend/templates/mail.ejs
@@ -350,6 +350,7 @@
<%= salutation %>
<%= paragraph %>
+ <% if (button) { %>
+ <% } %>
<% if (lastParagraph) { %>
<%= lastParagraph %>
<% } %>
From 059f9eb73ba9c056a2448c889bc26885f29fc638 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Mon, 11 Nov 2024 15:45:09 +0100
Subject: [PATCH 02/14] add send report via email settings
---
backend/models/connectionSettings.ts | 1 +
backend/models/organisation.ts | 5 +++--
common/locales/de.json | 2 ++
common/locales/en.json | 2 ++
common/types.ts | 3 +++
5 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/backend/models/connectionSettings.ts b/backend/models/connectionSettings.ts
index 98ce2477..58a80998 100644
--- a/backend/models/connectionSettings.ts
+++ b/backend/models/connectionSettings.ts
@@ -7,6 +7,7 @@ function requiredIf(ifPath: string) {
}
export const connectionSettingsSchema = new Schema({
+ sendPDFReportsToOrganisationEmail: { type: Boolean, default: false, required: true },
smtp: {
type: {
host: { type: String, trim: true, required: true, label: 'Host', rules: requiredIf('smtp.user') },
diff --git a/backend/models/organisation.ts b/backend/models/organisation.ts
index 9b9c2d6b..f8c3f493 100644
--- a/backend/models/organisation.ts
+++ b/backend/models/organisation.ts
@@ -1,9 +1,10 @@
-import { Document, Query, Schema, model } from 'mongoose'
-import { Organisation } from '../../common/types.js'
+import { Document, model, Query, Schema } from 'mongoose'
+import { emailRegex, Organisation } from '../../common/types.js'
export const organisationSchema = new Schema({
name: { type: String, trim: true, required: true },
subfolderPath: { type: String, trim: true, default: '' },
+ reportEmail: { type: String, validate: emailRegex },
bankDetails: { type: String },
companyNumber: { type: String, trim: true },
logo: { type: Schema.Types.ObjectId, ref: 'DocumentFile' },
diff --git a/common/locales/de.json b/common/locales/de.json
index 481c06ac..3f1aec93 100755
--- a/common/locales/de.json
+++ b/common/locales/de.json
@@ -97,6 +97,8 @@
"vehicleRegistration": "Lade hier den Fahrzeugschein deines Autos hoch."
},
"labels": {
+ "reportEmail": "Email Adresse für Berichte",
+ "sendPDFReportsToOrganisationEmail": "PDF Berichte an Organisations-Email senden",
"access": "Zugriffsrechte",
"accessIcons": "Icons für Zugriffsrechte",
"add": "Hinzufügen",
diff --git a/common/locales/en.json b/common/locales/en.json
index db5222fd..c804b7e0 100755
--- a/common/locales/en.json
+++ b/common/locales/en.json
@@ -97,6 +97,8 @@
"vehicleRegistration": "Upload the vehicle registration of your car here."
},
"labels": {
+ "reportEmail": "Email Address for Reports",
+ "sendPDFReportsToOrganisationEmail": "Send PDF reports to organisation email",
"access": "Access",
"accessIcons": "Icons for access rights",
"add": "Add",
diff --git a/common/types.ts b/common/types.ts
index 4576fc23..57ed47c8 100644
--- a/common/types.ts
+++ b/common/types.ts
@@ -74,11 +74,13 @@ export interface microsoftSettings {
}
export interface ConnectionSettings {
+ sendPDFReportsToOrganisationEmail: boolean
auth: {
microsoft?: microsoftSettings | null
ldapauth?: ldapauthSettings | null
}
smtp?: smtpSettings | null
+
_id: _id
}
@@ -211,6 +213,7 @@ export interface ProjectWithUsers extends Project, ProjectUsers {}
export interface Organisation extends OrganisationSimple {
subfolderPath: string
+ reportEmail?: string | null
bankDetails?: string | null
companyNumber?: string | null
logo?: DocumentFile | null
From 7149b3003a27d362ced8dedc410f125065a4872a Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Mon, 11 Nov 2024 16:00:39 +0100
Subject: [PATCH 03/14] use fs/promises
---
backend/controller/uploadController.ts | 2 +-
backend/mail/mail.ts | 4 ++--
backend/pdf/advance.ts | 4 ++--
backend/pdf/expenseReport.ts | 4 ++--
backend/pdf/healthCareCost.ts | 4 ++--
backend/pdf/travel.ts | 4 ++--
6 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/backend/controller/uploadController.ts b/backend/controller/uploadController.ts
index c95e2a88..5d0488ab 100644
--- a/backend/controller/uploadController.ts
+++ b/backend/controller/uploadController.ts
@@ -1,6 +1,6 @@
import ejs from 'ejs'
import { Request as ExRequest, Response as ExResponse, NextFunction } from 'express'
-import fs from 'node:fs/promises'
+import fs from 'fs/promises'
import { Body, Consumes, Controller, Get, Middlewares, Post, Produces, Query, Request, Route, SuccessResponse } from 'tsoa'
import { _id } from '../../common/types.js'
import { getSettings } from '../db.js'
diff --git a/backend/mail/mail.ts b/backend/mail/mail.ts
index c0192b9c..f8602597 100644
--- a/backend/mail/mail.ts
+++ b/backend/mail/mail.ts
@@ -1,5 +1,5 @@
import ejs from 'ejs'
-import fs from 'fs'
+import fs from 'fs/promises'
import nodemailer from 'nodemailer'
import {
ExpenseReportSimple,
@@ -66,7 +66,7 @@ async function _sendMail(
url: process.env.VITE_FRONTEND_URL
}
- const template = fs.readFileSync('./templates/mail.ejs', { encoding: 'utf-8' })
+ const template = await fs.readFile('./templates/mail.ejs', { encoding: 'utf-8' })
const renderedHTML = ejs.render(template, {
salutation,
paragraph,
diff --git a/backend/pdf/advance.ts b/backend/pdf/advance.ts
index 42b6c56c..4d47d034 100644
--- a/backend/pdf/advance.ts
+++ b/backend/pdf/advance.ts
@@ -1,4 +1,4 @@
-import fs from 'fs'
+import fs from 'fs/promises'
import pdf_fontkit from 'pdf-fontkit'
import pdf_lib from 'pdf-lib'
import { Locale, Money, TravelSimple } from '../../common/types.js'
@@ -9,7 +9,7 @@ export async function generateAdvanceReport(travel: TravelSimple, language: Loca
formatter.setLocale(language)
const pdfDoc = await pdf_lib.PDFDocument.create()
pdfDoc.registerFontkit(pdf_fontkit)
- const fontBytes = fs.readFileSync('../common/fonts/NotoSans-Regular.ttf')
+ const fontBytes = await fs.readFile('../common/fonts/NotoSans-Regular.ttf')
const font = await pdfDoc.embedFont(fontBytes, { subset: true })
const edge = 36
const fontSize = 11
diff --git a/backend/pdf/expenseReport.ts b/backend/pdf/expenseReport.ts
index 683fdf84..5e85a766 100644
--- a/backend/pdf/expenseReport.ts
+++ b/backend/pdf/expenseReport.ts
@@ -1,4 +1,4 @@
-import fs from 'fs'
+import fs from 'fs/promises'
import pdf_fontkit from 'pdf-fontkit'
import pdf_lib from 'pdf-lib'
import { addUp } from '../../common/scripts.js'
@@ -21,7 +21,7 @@ export async function generateExpenseReportReport(expenseReport: ExpenseReport,
formatter.setLocale(language)
const pdfDoc = await pdf_lib.PDFDocument.create()
pdfDoc.registerFontkit(pdf_fontkit)
- const fontBytes = fs.readFileSync('../common/fonts/NotoSans-Regular.ttf')
+ const fontBytes = await fs.readFile('../common/fonts/NotoSans-Regular.ttf')
const font = await pdfDoc.embedFont(fontBytes, { subset: true })
const edge = 36
const fontSize = 11
diff --git a/backend/pdf/healthCareCost.ts b/backend/pdf/healthCareCost.ts
index 2a437996..05b04d3b 100644
--- a/backend/pdf/healthCareCost.ts
+++ b/backend/pdf/healthCareCost.ts
@@ -1,4 +1,4 @@
-import fs from 'fs'
+import fs from 'fs/promises'
import pdf_fontkit from 'pdf-fontkit'
import pdf_lib from 'pdf-lib'
import { addUp } from '../../common/scripts.js'
@@ -21,7 +21,7 @@ export async function generateHealthCareCostReport(healthCareCost: HealthCareCos
formatter.setLocale(language)
const pdfDoc = await pdf_lib.PDFDocument.create()
pdfDoc.registerFontkit(pdf_fontkit)
- const fontBytes = fs.readFileSync('../common/fonts/NotoSans-Regular.ttf')
+ const fontBytes = await fs.readFile('../common/fonts/NotoSans-Regular.ttf')
const font = await pdfDoc.embedFont(fontBytes, { subset: true })
const edge = 36
const fontSize = 11
diff --git a/backend/pdf/travel.ts b/backend/pdf/travel.ts
index 8472c97b..a9b619f9 100644
--- a/backend/pdf/travel.ts
+++ b/backend/pdf/travel.ts
@@ -1,4 +1,4 @@
-import fs from 'fs'
+import fs from 'fs/promises'
import pdf_fontkit from 'pdf-fontkit'
import pdf_lib from 'pdf-lib'
import { addUp } from '../../common/scripts.js'
@@ -39,7 +39,7 @@ export async function generateTravelReport(travel: Travel, language: Locale) {
formatter.setLocale(language)
const pdfDoc = await pdf_lib.PDFDocument.create()
pdfDoc.registerFontkit(pdf_fontkit)
- const fontBytes = fs.readFileSync('../common/fonts/NotoSans-Regular.ttf')
+ const fontBytes = await fs.readFile('../common/fonts/NotoSans-Regular.ttf')
const font = await pdfDoc.embedFont(fontBytes, { subset: true })
const edge = 36
const fontSize = 11
From 750df77bd8d4f0230dcd15d922376f998de255bb Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Mon, 11 Nov 2024 16:09:51 +0100
Subject: [PATCH 04/14] make sendMail truly awaitable
---
backend/mail/mail.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/backend/mail/mail.ts b/backend/mail/mail.ts
index f8602597..b1f06019 100644
--- a/backend/mail/mail.ts
+++ b/backend/mail/mail.ts
@@ -34,6 +34,7 @@ export async function sendMail(
lastParagraph?: string,
authenticateLink = true
) {
+ const mailPromises = []
for (let i = 0; i < recipients.length; i++) {
const language = recipients[i].settings.language
let recipientButton: { text: string; link: string } | undefined = undefined
@@ -46,8 +47,9 @@ export async function sendMail(
})
}
}
- _sendMail(recipients[i], subject, paragraph, language, recipientButton, lastParagraph)
+ mailPromises.push(_sendMail(recipients[i], subject, paragraph, language, recipientButton, lastParagraph))
}
+ return await Promise.allSettled(mailPromises)
}
async function _sendMail(
@@ -89,7 +91,7 @@ async function _sendMail(
': ' +
app.url
- mailClient.sendMail({
+ return await mailClient.sendMail({
from: '"' + app.name + '" <' + mailClient.options.from + '>', // sender address
to: recipient.email, // list of receivers
subject: subject, // Subject line
From a336fa2534ab9b5d79ff428909e759d88d96a605 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Mon, 11 Nov 2024 17:33:30 +0100
Subject: [PATCH 05/14] send via mail
---
backend/controller/expenseReportController.ts | 3 +-
.../controller/healthCareCostController.ts | 4 +-
backend/controller/travelController.ts | 14 ++---
backend/mail/mail.ts | 2 +-
backend/models/connectionSettings.ts | 13 ++++-
backend/models/displaySettings.ts | 3 +-
backend/pdf/helper.ts | 57 +++++++++++++++++++
common/locales/de.json | 1 +
common/locales/en.json | 1 +
common/types.ts | 5 +-
10 files changed, 87 insertions(+), 16 deletions(-)
diff --git a/backend/controller/expenseReportController.ts b/backend/controller/expenseReportController.ts
index 62ab1b5e..072cca11 100644
--- a/backend/controller/expenseReportController.ts
+++ b/backend/controller/expenseReportController.ts
@@ -8,7 +8,7 @@ import { sendNotificationMail } from '../mail/mail.js'
import ExpenseReport, { ExpenseReportDoc } from '../models/expenseReport.js'
import User from '../models/user.js'
import { generateExpenseReportReport } from '../pdf/expenseReport.js'
-import { writeToDiskFilePath } from '../pdf/helper.js'
+import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
import { AuthorizationError, NotFoundError } from './error.js'
import { IdDocument, MoneyPost } from './types.js'
@@ -273,6 +273,7 @@ export class ExpenseReportExamineController extends Controller {
const cb = async (expenseReport: IExpenseReport) => {
sendNotificationMail(expenseReport)
+ sendViaMail(expenseReport)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
await writeToDiskFilePath(expenseReport),
diff --git a/backend/controller/healthCareCostController.ts b/backend/controller/healthCareCostController.ts
index 468f349d..65be9d04 100644
--- a/backend/controller/healthCareCostController.ts
+++ b/backend/controller/healthCareCostController.ts
@@ -16,7 +16,7 @@ import HealthCareCost, { HealthCareCostDoc } from '../models/healthCareCost.js'
import Organisation from '../models/organisation.js'
import User from '../models/user.js'
import { generateHealthCareCostReport } from '../pdf/healthCareCost.js'
-import { writeToDiskFilePath } from '../pdf/helper.js'
+import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
import { AuthorizationError, NotAllowedError } from './error.js'
import { IdDocument, MoneyPlusPost } from './types.js'
@@ -251,6 +251,7 @@ export class HealthCareCostExamineController extends Controller {
const cb = async (healthCareCost: IHealthCareCost) => {
sendNotificationMail(healthCareCost)
+ sendViaMail(healthCareCost)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
await writeToDiskFilePath(healthCareCost),
@@ -371,6 +372,7 @@ export class HealthCareCostConfirmController extends Controller {
const cb = async (healthCareCost: IHealthCareCost) => {
sendNotificationMail(healthCareCost)
+ sendViaMail(healthCareCost)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(
await writeToDiskFilePath(healthCareCost),
diff --git a/backend/controller/travelController.ts b/backend/controller/travelController.ts
index e20a8b00..8cc7e02b 100644
--- a/backend/controller/travelController.ts
+++ b/backend/controller/travelController.ts
@@ -8,7 +8,7 @@ import { sendNotificationMail } from '../mail/mail.js'
import Travel, { TravelDoc } from '../models/travel.js'
import User from '../models/user.js'
import { generateAdvanceReport } from '../pdf/advance.js'
-import { writeToDiskFilePath } from '../pdf/helper.js'
+import { sendViaMail, writeToDiskFilePath } from '../pdf/helper.js'
import { generateTravelReport } from '../pdf/travel.js'
import { Controller, GetterQuery, SetterBody } from './controller.js'
import { AuthorizationError, NotAllowedError } from './error.js'
@@ -278,12 +278,11 @@ export class TravelApproveController extends Controller {
}
const cb = async (travel: ITravel) => {
sendNotificationMail(travel)
- if (
- travel.advance.amount !== null &&
- travel.advance.amount > 0 &&
- process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true'
- ) {
- await writeToDisk(await writeToDiskFilePath(travel), await generateAdvanceReport(travel, i18n.language as Locale))
+ if (travel.advance.amount !== null && travel.advance.amount > 0) {
+ sendViaMail(travel)
+ if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
+ await writeToDisk(await writeToDiskFilePath(travel), await generateAdvanceReport(travel, i18n.language as Locale))
+ }
}
}
@@ -448,6 +447,7 @@ export class TravelExamineController extends Controller {
const cb = async (travel: ITravel) => {
sendNotificationMail(travel)
+ sendViaMail(travel)
if (process.env.BACKEND_SAVE_REPORTS_ON_DISK.toLowerCase() === 'true') {
await writeToDisk(await writeToDiskFilePath(travel), await generateTravelReport(travel, i18n.language as Locale))
}
diff --git a/backend/mail/mail.ts b/backend/mail/mail.ts
index b1f06019..c060fe7b 100644
--- a/backend/mail/mail.ts
+++ b/backend/mail/mail.ts
@@ -17,7 +17,7 @@ import i18n, { formatter } from '../i18n.js'
import User from '../models/user.js'
import { mapSmtpConfig } from '../settingsValidator.js'
-async function getClient() {
+export async function getClient() {
const connectionSettings = await getConnectionSettings()
if (connectionSettings.smtp?.host) {
return nodemailer.createTransport(mapSmtpConfig(connectionSettings.smtp))
diff --git a/backend/models/connectionSettings.ts b/backend/models/connectionSettings.ts
index 58a80998..54ffc040 100644
--- a/backend/models/connectionSettings.ts
+++ b/backend/models/connectionSettings.ts
@@ -1,13 +1,20 @@
import { HydratedDocument, model, Schema } from 'mongoose'
-import { ConnectionSettings, emailRegex } from '../../common/types.js'
+import { ConnectionSettings, emailRegex, locales } from '../../common/types.js'
import { verifyLdapauthConfig, verifySmtpConfig } from '../settingsValidator.js'
function requiredIf(ifPath: string) {
- return [{ required: [ifPath, 'not_in', [null, '']] }, { nullable: [ifPath, 'in', [null, '']] }]
+ return [{ required: [ifPath, 'not_in', [null, '', false]] }, { nullable: [ifPath, 'in', [null, '', false]] }]
}
export const connectionSettingsSchema = new Schema({
- sendPDFReportsToOrganisationEmail: { type: Boolean, default: false, required: true },
+ PDFReportsViaEmail: {
+ type: {
+ sendPDFReportsToOrganisationEmail: { type: Boolean, default: false, required: true },
+ locale: { type: String, enum: locales, required: true }
+ },
+ required: true,
+ label: 'PDF via Email'
+ },
smtp: {
type: {
host: { type: String, trim: true, required: true, label: 'Host', rules: requiredIf('smtp.user') },
diff --git a/backend/models/displaySettings.ts b/backend/models/displaySettings.ts
index f1b09b81..5dc3a414 100644
--- a/backend/models/displaySettings.ts
+++ b/backend/models/displaySettings.ts
@@ -30,8 +30,7 @@ export const displaySettingsSchema = new Schema(
fallback: { type: String, enum: locales, required: true },
overwrite: { type: overwrites, required: true, description: 'description.keyFullIdentifier' }
},
- required: true,
- label: 'Sprache'
+ required: true
}
},
{ minimize: false, toObject: { minimize: false }, toJSON: { minimize: false } }
diff --git a/backend/pdf/helper.ts b/backend/pdf/helper.ts
index d0e2a153..e5e2c69b 100644
--- a/backend/pdf/helper.ts
+++ b/backend/pdf/helper.ts
@@ -14,9 +14,15 @@ import {
reportIsHealthCareCost,
reportIsTravel
} from '../../common/types.js'
+import { getConnectionSettings } from '../db.js'
import i18n, { formatter } from '../i18n.js'
+import { getClient } from '../mail/mail.js'
import DocumentFile from '../models/documentFile.js'
import Organisation from '../models/organisation.js'
+import { generateAdvanceReport } from './advance.js'
+import { generateExpenseReportReport } from './expenseReport.js'
+import { generateHealthCareCostReport } from './healthCareCost.js'
+import { generateTravelReport } from './travel.js'
export async function writeToDiskFilePath(report: Travel | ExpenseReport | HealthCareCost): Promise {
let path = '/reports/'
@@ -43,6 +49,57 @@ export async function writeToDiskFilePath(report: Travel | ExpenseReport | Healt
return path
}
+export async function sendViaMail(report: Travel | ExpenseReport | HealthCareCost) {
+ const connectionSettings = await getConnectionSettings()
+ if (connectionSettings.PDFReportsViaEmail.sendPDFReportsToOrganisationEmail) {
+ const org = await Organisation.findOne({ _id: report.project.organisation._id })
+ if (org?.reportEmail) {
+ const mailClient = await getClient()
+ const lng = connectionSettings.PDFReportsViaEmail.locale
+ let subject = '🧾 '
+ let pdf: Uint8Array
+ if (reportIsTravel(report)) {
+ if (report.state == 'refunded') {
+ subject = subject + i18n.t('labels.travel', { lng })
+ pdf = await generateTravelReport(report, lng)
+ } else {
+ subject = subject + i18n.t('labels.advance', { lng })
+ pdf = await generateAdvanceReport(report, lng)
+ }
+ } else if (reportIsHealthCareCost(report)) {
+ subject = subject + i18n.t('labels.healthCareCost', { lng })
+ pdf = await generateHealthCareCostReport(report, lng)
+ } else {
+ subject = subject + i18n.t('labels.expenseReport', { lng })
+ pdf = await generateExpenseReportReport(report, lng)
+ }
+ const appName = i18n.t('headlines.title', { lng }) + ' ' + i18n.t('headlines.emoji', { lng })
+ formatter.setLocale(i18n.language as Locale)
+ const totalSum = formatter.money(addUp(report).total)
+
+ const text =
+ `${i18n.t('labels.project', { lng })}: ${report.project.identifier}\n` +
+ `${i18n.t('labels.name', { lng })}: ${report.name}\n` +
+ `${i18n.t('labels.owner', { lng })}: ${report.owner.name.givenName} ${report.owner.name.familyName}\n` +
+ `${i18n.t('labels.total', { lng })}: ${totalSum}\n`
+
+ return await mailClient.sendMail({
+ from: '"' + appName + '" <' + mailClient.options.from + '>', // sender address
+ to: org?.reportEmail, // list of receivers
+ subject: subject, // Subject line
+ text,
+ attachments: [
+ {
+ content: Buffer.from(pdf),
+ contentType: 'application/pdf',
+ filename: 'report.pdf'
+ }
+ ]
+ })
+ }
+ }
+}
+
export interface Options {
font: pdf_lib.PDFFont
fontSize: number
diff --git a/common/locales/de.json b/common/locales/de.json
index 3f1aec93..57a4ddaf 100755
--- a/common/locales/de.json
+++ b/common/locales/de.json
@@ -97,6 +97,7 @@
"vehicleRegistration": "Lade hier den Fahrzeugschein deines Autos hoch."
},
"labels": {
+ "locale": "Sprache",
"reportEmail": "Email Adresse für Berichte",
"sendPDFReportsToOrganisationEmail": "PDF Berichte an Organisations-Email senden",
"access": "Zugriffsrechte",
diff --git a/common/locales/en.json b/common/locales/en.json
index c804b7e0..11f91d9d 100755
--- a/common/locales/en.json
+++ b/common/locales/en.json
@@ -97,6 +97,7 @@
"vehicleRegistration": "Upload the vehicle registration of your car here."
},
"labels": {
+ "locale": "Language",
"reportEmail": "Email Address for Reports",
"sendPDFReportsToOrganisationEmail": "Send PDF reports to organisation email",
"access": "Access",
diff --git a/common/types.ts b/common/types.ts
index 57ed47c8..17d49611 100644
--- a/common/types.ts
+++ b/common/types.ts
@@ -74,7 +74,10 @@ export interface microsoftSettings {
}
export interface ConnectionSettings {
- sendPDFReportsToOrganisationEmail: boolean
+ PDFReportsViaEmail: {
+ sendPDFReportsToOrganisationEmail: boolean
+ locale: Locale
+ }
auth: {
microsoft?: microsoftSettings | null
ldapauth?: ldapauthSettings | null
From a2bd9e68605d15b41f97800d638f8de9440641ce Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Wed, 20 Nov 2024 14:09:42 +0100
Subject: [PATCH 06/14] add migration
---
backend/data/connectionSettings.development.json | 4 ++++
backend/db.ts | 6 +++---
backend/migrations.ts | 10 ++++++++++
3 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/backend/data/connectionSettings.development.json b/backend/data/connectionSettings.development.json
index 4427a543..2fc98f45 100644
--- a/backend/data/connectionSettings.development.json
+++ b/backend/data/connectionSettings.development.json
@@ -22,5 +22,9 @@
"user": "username",
"password": "password",
"senderAddress": "info@abrechnung.com"
+ },
+ "PDFReportsViaEmail": {
+ "sendPDFReportsToOrganisationEmail": false,
+ "locale": "de"
}
}
\ No newline at end of file
diff --git a/backend/db.ts b/backend/db.ts
index 2c42e60e..5059daa9 100644
--- a/backend/db.ts
+++ b/backend/db.ts
@@ -56,12 +56,12 @@ export async function initDB() {
}
if (process.env.NODE_ENV === 'development') {
- await initer(ConnectionSettings, 'connectionSettings', [connectionSettingsDevelopment], true)
+ await initer(ConnectionSettings, 'connectionSettings', [connectionSettingsDevelopment as Partial], true)
} else {
- const emtpyConnectionSettings: Partial = {
+ const emptyConnectionSettings: Partial = {
auth: {}
}
- await initer(ConnectionSettings, 'connectionSettings', [emtpyConnectionSettings])
+ await initer(ConnectionSettings, 'connectionSettings', [emptyConnectionSettings])
}
await initer(DisplaySettings, 'displaySettings', [displaySettings as Partial])
diff --git a/backend/migrations.ts b/backend/migrations.ts
index 2312e298..e4b42f95 100644
--- a/backend/migrations.ts
+++ b/backend/migrations.ts
@@ -124,6 +124,16 @@ export async function checkForMigrations() {
}
await mongoose.connection.collection('displaysettings').updateOne({}, { $set: displaySettingsFromEnv })
}
+ if (semver.lte(migrateFrom, '1.4.1')) {
+ console.log('Apply migration from v1.4.1: Add PDFReportsViaEmail Settings')
+ const displaySettings = await mongoose.connection.collection('displaysettings').findOne({})
+ await mongoose.connection
+ .collection('connectionsettings')
+ .updateOne(
+ {},
+ { $set: { PDFReportsViaEmail: { sendPDFReportsToOrganisationEmail: false, locale: displaySettings?.locale?.default || 'de' } } }
+ )
+ }
if (settings) {
settings.migrateFrom = undefined
await settings.save()
From db7e9b13fb9f614c7179304896920dbd88d99400 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Wed, 20 Nov 2024 17:08:16 +0100
Subject: [PATCH 07/14] ctrl + s => save settings
---
.../elements/ConnectionSettingsForm.vue | 12 +++++++++++
.../settings/elements/DisplaySettingsForm.vue | 21 ++++++++++++++++++-
.../settings/elements/SettingsForm.vue | 12 +++++++++++
3 files changed, 44 insertions(+), 1 deletion(-)
diff --git a/frontend/src/components/settings/elements/ConnectionSettingsForm.vue b/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
index 58d7c036..557cc457 100644
--- a/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
+++ b/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
@@ -30,9 +30,21 @@ export default defineComponent({
this.connectionSettings = result.ok
;(this.$refs.form$ as any).load(this.connectionSettings)
}
+ },
+ ctrlS(event: KeyboardEvent) {
+ if (event.ctrlKey && event.key === 's') {
+ event.preventDefault()
+ if (!event.repeat && this.$refs.form$) {
+ this.postConnectionSettings((this.$refs.form$ as any).data)
+ }
+ }
}
},
+ beforeDestroy() {
+ document.removeEventListener('keydown', this.ctrlS)
+ },
async mounted() {
+ document.addEventListener('keydown', this.ctrlS)
await this.$root.load()
this.schema = Object.assign({}, (await this.$root.getter('admin/connectionSettings/form')).ok?.data, {
buttons: {
diff --git a/frontend/src/components/settings/elements/DisplaySettingsForm.vue b/frontend/src/components/settings/elements/DisplaySettingsForm.vue
index 95451842..30eb7b31 100644
--- a/frontend/src/components/settings/elements/DisplaySettingsForm.vue
+++ b/frontend/src/components/settings/elements/DisplaySettingsForm.vue
@@ -21,15 +21,34 @@ export default defineComponent({
this.displaySettings = result.ok
;(this.$refs.form$ as any).load(this.displaySettings)
}
+ },
+ ctrlS(event: KeyboardEvent) {
+ if (event.ctrlKey && event.key === 's') {
+ event.preventDefault()
+ if (!event.repeat && this.$refs.form$) {
+ this.postDisplaySettings((this.$refs.form$ as any).data)
+ }
+ }
}
},
+ beforeDestroy() {
+ document.removeEventListener('keydown', this.ctrlS)
+ },
async mounted() {
+ document.addEventListener('keydown', this.ctrlS)
await this.$root.load()
this.schema = Object.assign({}, (await this.$root.getter('admin/displaySettings/form')).ok?.data, {
buttons: {
type: 'group',
schema: {
- submit: { type: 'button', submits: true, buttonLabel: this.$t('labels.save'), full: true, columns: { container: 6 } }
+ submit: {
+ type: 'button',
+ submits: true,
+ buttonLabel: this.$t('labels.save'),
+ full: true,
+ columns: { container: 6 },
+ id: 'submit-button'
+ }
}
},
_id: { type: 'hidden', meta: true }
diff --git a/frontend/src/components/settings/elements/SettingsForm.vue b/frontend/src/components/settings/elements/SettingsForm.vue
index 50146004..1a0688f6 100644
--- a/frontend/src/components/settings/elements/SettingsForm.vue
+++ b/frontend/src/components/settings/elements/SettingsForm.vue
@@ -20,9 +20,21 @@ export default defineComponent({
this.$root.settings = result.ok
;(this.$refs.form$ as any).load(this.$root.settings)
}
+ },
+ ctrlS(event: KeyboardEvent) {
+ if (event.ctrlKey && event.key === 's') {
+ event.preventDefault()
+ if (!event.repeat && this.$refs.form$) {
+ this.postSettings((this.$refs.form$ as any).data)
+ }
+ }
}
},
+ beforeDestroy() {
+ document.removeEventListener('keydown', this.ctrlS)
+ },
async mounted() {
+ document.addEventListener('keydown', this.ctrlS)
await this.$root.load()
this.schema = Object.assign({}, (await this.$root.getter('admin/settings/form')).ok?.data, {
buttons: {
From 3de1e832ff713902dffaf84f386520e1d2b634f3 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Wed, 20 Nov 2024 17:13:30 +0100
Subject: [PATCH 08/14] organize organisations
---
backend/models/organisation.ts | 4 ++--
.../src/components/settings/elements/OrganisationList.vue | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/backend/models/organisation.ts b/backend/models/organisation.ts
index f8c3f493..e144200d 100644
--- a/backend/models/organisation.ts
+++ b/backend/models/organisation.ts
@@ -3,12 +3,12 @@ import { emailRegex, Organisation } from '../../common/types.js'
export const organisationSchema = new Schema({
name: { type: String, trim: true, required: true },
- subfolderPath: { type: String, trim: true, default: '' },
reportEmail: { type: String, validate: emailRegex },
+ website: { type: String },
bankDetails: { type: String },
companyNumber: { type: String, trim: true },
logo: { type: Schema.Types.ObjectId, ref: 'DocumentFile' },
- website: { type: String }
+ subfolderPath: { type: String, trim: true, default: '' }
})
function populate(doc: Document) {
diff --git a/frontend/src/components/settings/elements/OrganisationList.vue b/frontend/src/components/settings/elements/OrganisationList.vue
index 6b281b90..15a6201a 100644
--- a/frontend/src/components/settings/elements/OrganisationList.vue
+++ b/frontend/src/components/settings/elements/OrganisationList.vue
@@ -15,7 +15,7 @@
]"
:headers="[
{ text: $t('labels.name'), value: 'name' },
- { text: $t('labels.subfolderPath'), value: 'subfolderPath' },
+ { text: $t('labels.reportEmail'), value: 'reportEmail' },
{ text: $t('labels.website'), value: 'website' },
{ value: 'buttons' }
]">
From 56bd3d30f150ecf2f6edc968ab9318a3bbcb2a52 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Wed, 20 Nov 2024 17:38:15 +0100
Subject: [PATCH 09/14] adding default locale
---
backend/db.ts | 5 +----
backend/models/connectionSettings.ts | 8 +++++---
backend/models/displaySettings.ts | 6 +++---
common/types.ts | 1 +
frontend/src/formatter.ts | 3 ++-
frontend/src/i18n.ts | 16 +++++++++-------
frontend/src/vueform.config.ts | 3 ++-
frontend/vue.config.ts | 11 -----------
8 files changed, 23 insertions(+), 30 deletions(-)
diff --git a/backend/db.ts b/backend/db.ts
index 5059daa9..df91d729 100644
--- a/backend/db.ts
+++ b/backend/db.ts
@@ -58,10 +58,7 @@ export async function initDB() {
if (process.env.NODE_ENV === 'development') {
await initer(ConnectionSettings, 'connectionSettings', [connectionSettingsDevelopment as Partial], true)
} else {
- const emptyConnectionSettings: Partial = {
- auth: {}
- }
- await initer(ConnectionSettings, 'connectionSettings', [emptyConnectionSettings])
+ await initer(ConnectionSettings, 'connectionSettings', [{} as Partial])
}
await initer(DisplaySettings, 'displaySettings', [displaySettings as Partial])
diff --git a/backend/models/connectionSettings.ts b/backend/models/connectionSettings.ts
index 54ffc040..6d27458f 100644
--- a/backend/models/connectionSettings.ts
+++ b/backend/models/connectionSettings.ts
@@ -1,5 +1,5 @@
import { HydratedDocument, model, Schema } from 'mongoose'
-import { ConnectionSettings, emailRegex, locales } from '../../common/types.js'
+import { ConnectionSettings, defaultLocale, emailRegex, locales } from '../../common/types.js'
import { verifyLdapauthConfig, verifySmtpConfig } from '../settingsValidator.js'
function requiredIf(ifPath: string) {
@@ -10,9 +10,10 @@ export const connectionSettingsSchema = new Schema({
PDFReportsViaEmail: {
type: {
sendPDFReportsToOrganisationEmail: { type: Boolean, default: false, required: true },
- locale: { type: String, enum: locales, required: true }
+ locale: { type: String, enum: locales, required: true, default: defaultLocale }
},
required: true,
+ default: () => ({}),
label: 'PDF via Email'
},
smtp: {
@@ -101,7 +102,8 @@ export const connectionSettingsSchema = new Schema({
label: 'LDAP'
}
},
- required: true
+ required: true,
+ default: () => ({})
}
})
diff --git a/backend/models/displaySettings.ts b/backend/models/displaySettings.ts
index 5dc3a414..b591a6f2 100644
--- a/backend/models/displaySettings.ts
+++ b/backend/models/displaySettings.ts
@@ -1,5 +1,5 @@
import { model, Schema } from 'mongoose'
-import { DisplaySettings, Locale, locales } from '../../common/types.js'
+import { defaultLocale, DisplaySettings, Locale, locales } from '../../common/types.js'
const overwrites = {} as { [key in Locale]: { type: typeof Schema.Types.Mixed; required: true; default: () => object } }
for (const locale of locales) {
@@ -26,8 +26,8 @@ export const displaySettingsSchema = new Schema(
},
locale: {
type: {
- default: { type: String, enum: locales, required: true },
- fallback: { type: String, enum: locales, required: true },
+ default: { type: String, enum: locales, required: true, default: defaultLocale },
+ fallback: { type: String, enum: locales, required: true, default: defaultLocale },
overwrite: { type: overwrites, required: true, description: 'description.keyFullIdentifier' }
},
required: true
diff --git a/common/types.ts b/common/types.ts
index 17d49611..43903b75 100644
--- a/common/types.ts
+++ b/common/types.ts
@@ -541,3 +541,4 @@ export const baseCurrency: Currency = {
subunit: 'Cent',
symbol: '€'
}
+export const defaultLocale: Locale = 'de'
diff --git a/frontend/src/formatter.ts b/frontend/src/formatter.ts
index 6045c3eb..22db7a36 100644
--- a/frontend/src/formatter.ts
+++ b/frontend/src/formatter.ts
@@ -1,9 +1,10 @@
import { App, Plugin } from 'vue'
import Formatter from '../../common/formatter'
+import { defaultLocale } from '../../common/types'
const FormatterPlugin: Plugin = {
install(app: App, options: any) {
- app.config.globalProperties.$formatter = new Formatter('de')
+ app.config.globalProperties.$formatter = new Formatter(defaultLocale)
}
}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index f2379085..f2f2294b 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -1,15 +1,17 @@
import { createI18n } from 'vue-i18n'
-import { Locale, locales } from '../../common/types.js'
+import de from '../../common/locales/de.json' with { type: 'json' }
+import en from '../../common/locales/en.json' with { type: 'json' }
+import { defaultLocale, Locale } from '../../common/types.js'
-const emptyMessages = {} as { [key in Locale]: {} }
-for (const locale of locales) {
- emptyMessages[locale] = {}
+const defaultMessages = {
+ de,
+ en
}
export default createI18n({
legacy: false,
- locale: 'de',
- fallbackLocale: 'de',
- messages: emptyMessages,
+ locale: defaultLocale,
+ fallbackLocale: defaultLocale,
+ messages: defaultMessages,
globalInjection: true
})
diff --git a/frontend/src/vueform.config.ts b/frontend/src/vueform.config.ts
index 7352662e..6a0620f7 100644
--- a/frontend/src/vueform.config.ts
+++ b/frontend/src/vueform.config.ts
@@ -3,6 +3,7 @@ import vueform from '@vueform/vueform/dist/vueform'
import de from '@vueform/vueform/locales/de'
import en from '@vueform/vueform/locales/en'
+import { defaultLocale } from '../../common/types'
import CountryElement from './components/elements/vueform/CountryElement.vue'
import CurrencyElement from './components/elements/vueform/CurrencyElement.vue'
import DocumentfileElement from './components/elements/vueform/DocumentfileElement.vue'
@@ -38,7 +39,7 @@ export default defineConfig({
MixedElement
],
locales: { de, en },
- locale: 'de',
+ locale: defaultLocale,
env: import.meta.env.MODE,
displayErrors: false,
displayMessages: false,
diff --git a/frontend/vue.config.ts b/frontend/vue.config.ts
index c1a2fe1b..8ff0ff82 100644
--- a/frontend/vue.config.ts
+++ b/frontend/vue.config.ts
@@ -4,16 +4,5 @@ export default {
client: {
webSocketURL: 'auto://0.0.0.0:0/ws'
}
- },
- pluginOptions: {
- i18n: {
- locale: 'en',
- fallbackLocale: 'en',
- localeDir: 'locales',
- enableLegacy: false,
- runtimeOnly: false,
- compositionOnly: false,
- fullInstall: true
- }
}
}
From 7da4905c43eff5452146afec689305caa1e6f429 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Wed, 20 Nov 2024 17:42:03 +0100
Subject: [PATCH 10/14] deprecate BACKEND_SAVE_REPORTS_ON_DISK
---
.env.example | 1 +
deploy-compose.yml | 5 -----
docker-compose.yml | 4 ----
3 files changed, 1 insertion(+), 9 deletions(-)
diff --git a/.env.example b/.env.example
index ed60181b..40b54492 100644
--- a/.env.example
+++ b/.env.example
@@ -35,5 +35,6 @@ RATE_LIMIT=
# TRUE | number | string (https://expressjs.com/en/guide/behind-proxies.html) use BACKEND_URL/ip to validate
TRUST_PROXY=
+# ⚠️Deprecated⚠️ Use send via mail under connection settings
# If set to 'TRUE', all reports will be saved to `/reports` in the backend container. Uncomment the corresponding backend volume in `docker-compose.yml` to get reports on host maschine
BACKEND_SAVE_REPORTS_ON_DISK=FALSE
diff --git a/deploy-compose.yml b/deploy-compose.yml
index cb9bf30f..c02e66fa 100644
--- a/deploy-compose.yml
+++ b/deploy-compose.yml
@@ -4,11 +4,6 @@ services:
restart: always
depends_on:
- db
- # volumes:
- # - ./reports/travel:/reports/travel #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/expenseReport:/reports/expenseReport #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/advance:/reports/advance #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/healthCareCost:/reports/healthCareCost #BACKEND_SAVE_REPORTS_ON_DISK
ports:
- ${BACKEND_PORT}:${BACKEND_PORT}
env_file:
diff --git a/docker-compose.yml b/docker-compose.yml
index 1279ebba..913f258c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,10 +9,6 @@ services:
volumes:
- ./backend:/app
- ./common:/common
- # - ./reports/travel:/reports/travel #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/expenseReport:/reports/expenseReport #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/advance:/reports/advance #BACKEND_SAVE_REPORTS_ON_DISK
- # - ./reports/healthCareCost:/reports/healthCareCost #BACKEND_SAVE_REPORTS_ON_DISK
ports:
- ${BACKEND_PORT}:${BACKEND_PORT}
- ${BACKEND_DEBUG_PORT}:${BACKEND_DEBUG_PORT}
From aed64db16f7e36c8ae50ef807a883cf3184ba7fa Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Thu, 21 Nov 2024 09:38:23 +0100
Subject: [PATCH 11/14] add caching for templates
---
backend/controller/uploadController.ts | 4 ++--
backend/mail/mail.ts | 4 ++--
backend/templates/cache.ts | 18 ++++++++++++++++++
3 files changed, 22 insertions(+), 4 deletions(-)
create mode 100644 backend/templates/cache.ts
diff --git a/backend/controller/uploadController.ts b/backend/controller/uploadController.ts
index 5d0488ab..7bff1ba0 100644
--- a/backend/controller/uploadController.ts
+++ b/backend/controller/uploadController.ts
@@ -1,6 +1,5 @@
import ejs from 'ejs'
import { Request as ExRequest, Response as ExResponse, NextFunction } from 'express'
-import fs from 'fs/promises'
import { Body, Consumes, Controller, Get, Middlewares, Post, Produces, Query, Request, Route, SuccessResponse } from 'tsoa'
import { _id } from '../../common/types.js'
import { getSettings } from '../db.js'
@@ -8,6 +7,7 @@ import { documentFileHandler, fileHandler } from '../helper.js'
import i18n from '../i18n.js'
import Token from '../models/token.js'
import User from '../models/user.js'
+import { getUploadTemplate } from '../templates/cache.js'
import { AuthorizationError, NotFoundError } from './error.js'
import { File } from './types.js'
@@ -33,7 +33,7 @@ export class UploadController extends Controller {
): Promise {
const settings = await getSettings()
const user = await User.findOne({ _id: userId }).lean()
- const template = await fs.readFile('./templates/upload.ejs', { encoding: 'utf-8' })
+ const template = await getUploadTemplate()
const url = new URL(process.env.VITE_BACKEND_URL + '/upload/new')
url.searchParams.append('userId', userId)
url.searchParams.append('tokenId', tokenId)
diff --git a/backend/mail/mail.ts b/backend/mail/mail.ts
index c060fe7b..3d595edf 100644
--- a/backend/mail/mail.ts
+++ b/backend/mail/mail.ts
@@ -1,5 +1,4 @@
import ejs from 'ejs'
-import fs from 'fs/promises'
import nodemailer from 'nodemailer'
import {
ExpenseReportSimple,
@@ -16,6 +15,7 @@ import { genAuthenticatedLink } from '../helper.js'
import i18n, { formatter } from '../i18n.js'
import User from '../models/user.js'
import { mapSmtpConfig } from '../settingsValidator.js'
+import { getMailTemplate } from '../templates/cache.js'
export async function getClient() {
const connectionSettings = await getConnectionSettings()
@@ -68,7 +68,7 @@ async function _sendMail(
url: process.env.VITE_FRONTEND_URL
}
- const template = await fs.readFile('./templates/mail.ejs', { encoding: 'utf-8' })
+ const template = await getMailTemplate()
const renderedHTML = ejs.render(template, {
salutation,
paragraph,
diff --git a/backend/templates/cache.ts b/backend/templates/cache.ts
new file mode 100644
index 00000000..8b883331
--- /dev/null
+++ b/backend/templates/cache.ts
@@ -0,0 +1,18 @@
+import fs from 'fs/promises'
+
+let mail: string | null = null
+let upload: string | null = null
+
+export async function getMailTemplate() {
+ if (!mail) {
+ mail = await fs.readFile('./templates/mail.ejs', { encoding: 'utf-8' })
+ }
+ return mail
+}
+
+export async function getUploadTemplate() {
+ if (!upload) {
+ upload = await fs.readFile('./templates/upload.ejs', { encoding: 'utf-8' })
+ }
+ return upload
+}
From 7f3984b83a3339bbcde765f8b1ba8e11e0c72a7f Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Thu, 21 Nov 2024 09:42:30 +0100
Subject: [PATCH 12/14] use consistent language
---
backend/pdf/helper.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/pdf/helper.ts b/backend/pdf/helper.ts
index e5e2c69b..d96cff92 100644
--- a/backend/pdf/helper.ts
+++ b/backend/pdf/helper.ts
@@ -74,7 +74,7 @@ export async function sendViaMail(report: Travel | ExpenseReport | HealthCareCos
pdf = await generateExpenseReportReport(report, lng)
}
const appName = i18n.t('headlines.title', { lng }) + ' ' + i18n.t('headlines.emoji', { lng })
- formatter.setLocale(i18n.language as Locale)
+ formatter.setLocale(lng)
const totalSum = formatter.money(addUp(report).total)
const text =
From 295bc729cdf24bcddb1d2ee82f39c36a601764b8 Mon Sep 17 00:00:00 2001
From: david-loe <56305409+david-loe@users.noreply.github.com>
Date: Thu, 21 Nov 2024 10:05:42 +0100
Subject: [PATCH 13/14] refactor keydown
---
.../elements/ConnectionSettingsForm.vue | 20 +++++++------------
.../settings/elements/DisplaySettingsForm.vue | 19 ++++++------------
.../settings/elements/SettingsForm.vue | 19 ++++++------------
3 files changed, 19 insertions(+), 39 deletions(-)
diff --git a/frontend/src/components/settings/elements/ConnectionSettingsForm.vue b/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
index 557cc457..85aaf41d 100644
--- a/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
+++ b/frontend/src/components/settings/elements/ConnectionSettingsForm.vue
@@ -1,5 +1,10 @@
- postConnectionSettings(form$.data)">
+ postConnectionSettings(form$.data)"
+ @keydown.ctrl.s.prevent="() => postConnectionSettings(($refs.form$ as any).data)">
|