Merge branch 'master' into 3573-feature-introduce-distributed-semaphore-base-on-redis

This commit is contained in:
einhornimmond 2025-11-27 19:10:36 +01:00
commit 6f95ab1337
11 changed files with 67 additions and 79 deletions

View File

@ -14,7 +14,6 @@ import { GRADIDO_REALM, LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { AppDatabase } from 'database' import { AppDatabase } from 'database'
import { context as serverContext } from './context' import { context as serverContext } from './context'
import { cors } from './cors' import { cors } from './cors'
import { i18n } from './localization'
import { plugins } from './plugins' import { plugins } from './plugins'
import { jwks, openidConfiguration } from '@/openIDConnect' import { jwks, openidConfiguration } from '@/openIDConnect'
// TODO implement // TODO implement
@ -74,9 +73,6 @@ export const createServer = async (
app.use(json()) app.use(json())
// bodyparser urlencoded for elopage // bodyparser urlencoded for elopage
app.use(urlencoded({ extended: true })) app.use(urlencoded({ extended: true }))
// i18n
app.use(i18n.init)
// Elopage Webhook // Elopage Webhook

View File

@ -1,33 +0,0 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import i18n from 'i18n'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.localization`)
i18n.configure({
locales: ['en', 'de'],
defaultLocale: 'en',
retryInDefaultLocale: false,
staticCatalog: {
en: { general: { decimalSeparator: "." } },
de: { general: { decimalSeparator: "," } },
},
// autoReload: true, // if this is activated the seeding hangs at the very end
updateFiles: false,
objectNotation: true,
logDebugFn: (msg) => logger.debug(msg),
logWarnFn: (msg) => logger.info(msg),
logErrorFn: (msg) => logger.error(msg),
// this api is needed for email-template pug files
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn
},
register: global,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
})
export { i18n }

View File

@ -22,8 +22,8 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"lint": "biome check --error-on-warnings .", "lint": "biome check --error-on-warnings .",
"lint:fix": "biome check --error-on-warnings . --write", "lint:fix": "biome check --error-on-warnings . --write",
"locales": "scripts/sort.sh src/emails/locales", "locales": "scripts/sort.sh src/locales",
"locales:fix": "scripts/sort.sh src/emails/locales --fix", "locales:fix": "scripts/sort.sh src/locales --fix",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo" "clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
}, },
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
import { createTransport } from 'nodemailer' import { createTransport } from 'nodemailer'
import { CONFIG } from '../config' import { CONFIG } from '../config'
import { i18n } from './localization' import { i18n } from '../locales/localization'
import { getLogger } from '../../../config-schema/test/testSetup.bun' import { getLogger } from '../../../config-schema/test/testSetup.bun'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { sendEmailTranslated } from './sendEmailTranslated' import { sendEmailTranslated } from './sendEmailTranslated'

View File

@ -1,6 +1,6 @@
import path from 'path' import path from 'path'
import Email from 'email-templates' import Email from 'email-templates'
import { i18n } from './localization' import { i18n } from '../locales/localization'
import { createTransport } from 'nodemailer' import { createTransport } from 'nodemailer'
import { CONFIG } from '../config' import { CONFIG } from '../config'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
@ -113,7 +113,6 @@ export const sendEmailTranslated = async ({
}) })
.catch((error: unknown) => { .catch((error: unknown) => {
logger.error('Error sending notification email', error) logger.error('Error sending notification email', error)
return error
}) })
return resultSend return resultSend

View File

@ -48,6 +48,10 @@ describe('sendEmailVariants', () => {
const contributionFrontendLink = const contributionFrontendLink =
'https://gradido.net/contributions/own-contributions/1#contributionListItem-1' 'https://gradido.net/contributions/own-contributions/1#contributionListItem-1'
afterEach(() => {
sendEmailTranslatedSpy.mockClear()
})
describe('sendAddedContributionMessageEmail', () => { describe('sendAddedContributionMessageEmail', () => {
beforeAll(async () => { beforeAll(async () => {
result = await sendAddedContributionMessageEmail({ result = await sendAddedContributionMessageEmail({
@ -163,7 +167,7 @@ describe('sendEmailVariants', () => {
}) })
}) })
/*
describe('sendAccountMultiRegistrationEmail', () => { describe('sendAccountMultiRegistrationEmail', () => {
beforeAll(async () => { beforeAll(async () => {
@ -182,20 +186,22 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'accountMultiRegistration', template: 'accountMultiRegistration',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD, resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL, communityURL: CONFIG.COMMUNITY_URL,
}, }),
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -236,24 +242,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'contributionConfirmed', template: 'contributionConfirmed',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.', contributionMemo: 'My contribution.',
contributionAmount: '23.54', contributionAmount: '23.54',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
contributionFrontendLink, contributionFrontendLink,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -293,24 +301,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'contributionChangedByModerator', template: 'contributionChangedByModerator',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.', contributionMemo: 'My contribution.',
contributionMemoUpdated: 'This is a better contribution memo.', contributionMemoUpdated: 'This is a better contribution memo.',
contributionFrontendLink, contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -349,23 +359,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'contributionDenied', template: 'contributionDenied',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.', contributionMemo: 'My contribution.',
contributionFrontendLink, contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('has expected result', () => { it('has expected result', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -404,23 +416,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'contributionDeleted', template: 'contributionDeleted',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.', contributionMemo: 'My contribution.',
contributionFrontendLink, contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -457,23 +471,25 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'resetPassword', template: 'resetPassword',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
resetLink: 'http://localhost/reset-password/3762660021544901417', resetLink: 'http://localhost/reset-password/3762660021544901417',
timeDurationObject: { hours: 23, minutes: 30 }, timeDurationObject: { hours: 23, minutes: 30 },
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD, resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL, communityURL: CONFIG.COMMUNITY_URL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -513,10 +529,10 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'transactionLinkRedeemed', template: 'transactionLinkRedeemed',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
senderEmail: 'bibi@bloxberg.de', senderEmail: 'bibi@bloxberg.de',
@ -524,14 +540,16 @@ describe('sendEmailVariants', () => {
transactionAmount: '17.65', transactionAmount: '17.65',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL, communityURL: CONFIG.COMMUNITY_URL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -571,10 +589,10 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'transactionReceived', template: 'transactionReceived',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
memo: 'Du bist schon lustiger ;)', memo: 'Du bist schon lustiger ;)',
senderFirstName: 'Bibi', senderFirstName: 'Bibi',
senderLastName: 'Bloxberg', senderLastName: 'Bloxberg',
@ -582,14 +600,16 @@ describe('sendEmailVariants', () => {
transactionAmount: '37.40', transactionAmount: '37.40',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL, communityURL: CONFIG.COMMUNITY_URL,
}, }),
}) })
}) })
}) })
describe('result', () => { describe('result', () => {
it('is the expected object', () => { it('is the expected object', () => {
expect(result).toMatchObject({ // bun testrunner bug, toMatchObject mess with 'result'
const resultClone = JSON.parse(JSON.stringify(result))
expect(resultClone).toMatchObject({
originalMessage: expect.objectContaining({ originalMessage: expect.objectContaining({
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>', from: 'Gradido (emails.general.doNotAnswer) <info@gradido.net>',
@ -606,5 +626,4 @@ describe('sendEmailVariants', () => {
}) })
}) })
}) })
*/
}) })

View File

@ -1,6 +1,10 @@
import en from './locales/en.json' import en from './en.json'
import de from './locales/de.json' import de from './de.json'
import { I18n } from 'i18n' import { I18n } from 'i18n'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.localization`)
function flatten(obj: any, prefix: string = ''): any { function flatten(obj: any, prefix: string = ''): any {
const result: any = {} const result: any = {}
@ -18,6 +22,9 @@ export const i18n = new I18n({
locales: ['en', 'de'], locales: ['en', 'de'],
defaultLocale: 'en', defaultLocale: 'en',
staticCatalog: { en: flatten(en), de: flatten(de) }, staticCatalog: { en: flatten(en), de: flatten(de) },
logDebugFn: (msg) => logger.debug(msg),
logWarnFn: (msg) => logger.info(msg),
logErrorFn: (msg) => logger.error(msg),
api: { api: {
__: 't', // now req.__ becomes req.t __: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn __n: 'tn', // and req.__n can be called as req.tn

View File

@ -1,7 +1,7 @@
import { promisify } from 'util' import { promisify } from 'util'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import i18n from 'i18n' import { i18n } from '../locales/localization'
export const objectValuesToArray = (obj: Record<string, string>): string[] => export const objectValuesToArray = (obj: Record<string, string>): string[] =>
Object.keys(obj).map((key) => obj[key]) Object.keys(obj).map((key) => obj[key])