Merge branch 'dlt_inspector_as_submodule' into dlt_export_existing_transactions_drizzleOrm

This commit is contained in:
einhornimmond 2025-11-26 14:22:42 +01:00
commit 5fa6ef9d8c
147 changed files with 1226 additions and 1322 deletions

View File

@ -92,19 +92,4 @@ jobs:
bun install --global --no-save turbo@^2 bun install --global --no-save turbo@^2
- name: Backend | Typecheck - name: Backend | Typecheck
run: turbo backend#typecheck backend#build run: turbo backend#typecheck backend#build
locales:
if: needs.files-changed.outputs.backend == 'true'
name: Locales - Backend
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: install bun
uses: oven-sh/setup-bun@v2
- name: Backend | Locales
run: cd backend && bun locales

View File

@ -43,3 +43,18 @@ jobs:
- name: typecheck && unit test - name: typecheck && unit test
run: turbo core#test core#typecheck run: turbo core#test core#typecheck
locales:
if: needs.files-changed.outputs.core == 'true'
name: Locales - Core
needs: files-changed
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: install bun
uses: oven-sh/setup-bun@v2
- name: Core | Locales
run: cd core && bun locales

View File

@ -4,9 +4,18 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v2.7.0](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.0) #### [v2.7.1](https://github.com/gradido/gradido/compare/v2.7.0...v2.7.1)
- fixes [`414ff8a`](https://github.com/gradido/gradido/commit/414ff8ac5a7477109f80123ccca5c4c8ed4511b2) - feat(frontend): new startpage images [`#3576`](https://github.com/gradido/gradido/pull/3576)
- feat(frontend): update login subtitle [`#3574`](https://github.com/gradido/gradido/pull/3574)
- feat(frontend): update copy symbol and change link order [`#3575`](https://github.com/gradido/gradido/pull/3575)
- fix(backend): correct seeding [`#3572`](https://github.com/gradido/gradido/pull/3572)
- feat(dlt): dlt-connector takes care of gradido node [`#3568`](https://github.com/gradido/gradido/pull/3568)
- refactor(dlt): upgrade dlt for using gradido blockchain lib and hiero [`#3551`](https://github.com/gradido/gradido/pull/3551)
- chore(release): v2.7.0 [`#3557`](https://github.com/gradido/gradido/pull/3557)
- feat(federation): use own table for handshake state [`#3555`](https://github.com/gradido/gradido/pull/3555)
- fix(workflow): use bun instead of yarn [`#3550`](https://github.com/gradido/gradido/pull/3550)
- feat(workflow): adjust for new bun version [`#3554`](https://github.com/gradido/gradido/pull/3554)
#### [v2.7.0](https://github.com/gradido/gradido/compare/2.6.1...v2.7.0) #### [v2.7.0](https://github.com/gradido/gradido/compare/2.6.1...v2.7.0)

View File

@ -189,11 +189,11 @@ describe('test', () => {
```ts ```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup' import { clearLogs, printLogs } from 'config-schema/test/testSetup'
``` ```
- vitest (frontend, admin, database): - vitest (frontend, admin):
```ts ```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.vitest' import { clearLogs, printLogs } from 'config-schema/test/testSetup.vitest'
``` ```
- bun (shared, core): - bun (shared, core, database):
```ts ```ts
import { clearLogs, printLogs } from 'config-schema/test/testSetup.bun' import { clearLogs, printLogs } from 'config-schema/test/testSetup.bun'
``` ```

View File

@ -3,7 +3,7 @@
"description": "Administration Interface for Gradido", "description": "Administration Interface for Gradido",
"main": "index.js", "main": "index.js",
"author": "Gradido Academy - https://www.gradido.net", "author": "Gradido Academy - https://www.gradido.net",
"version": "2.7.0", "version": "2.7.1",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@ -22,7 +22,7 @@ KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# DltConnector # DltConnector
DLT_CONNECTOR=true DLT_ACTIVE=false
DLT_CONNECTOR_URL=http://localhost:6010 DLT_CONNECTOR_URL=http://localhost:6010
# Community # Community

View File

@ -1,77 +0,0 @@
# Server
PORT=4000
JWT_SECRET=secret123
JWT_EXPIRES_IN=10m
GRAPHIQL=false
GDT_API_URL=https://gdt.gradido.net
# Database
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
TYPEORM_LOGGING_RELATIVE_PATH=typeorm.backend.log
# Klicktipp
KLICKTIPP=false
KLICKTTIPP_API_URL=https://api.klicktipp.com
KLICKTIPP_USER=gradido_test
KLICKTIPP_PASSWORD=secret321
KLICKTIPP_APIKEY_DE=SomeFakeKeyDE
KLICKTIPP_APIKEY_EN=SomeFakeKeyEN
# DltConnector
DLT_CONNECTOR=true
DLT_CONNECTOR_URL=http://localhost:6010
# Community
COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost
COMMUNITY_REGISTER_PATH=/register
COMMUNITY_REDEEM_PATH=/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_PATH=/redeem/CL-{code}
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# Login Server
LOGIN_APP_SECRET=21ffbbc616fe
LOGIN_SERVER_KEY=a51ef8ac7ef1abf162fb7a65261acd7a
# EMail
EMAIL=false
EMAIL_TEST_MODUS=false
EMAIL_TEST_RECEIVER=stage1@gradido.net
EMAIL_USERNAME=gradido_email
EMAIL_SENDER=info@gradido.net
EMAIL_PASSWORD=xxx
EMAIL_SMTP_HOST=gmail.com
EMAIL_SMTP_PORT=587
EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code}
EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin}
EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password
EMAIL_LINK_OVERVIEW_PATH=/overview
EMAIL_CODE_VALID_TIME=1440
EMAIL_CODE_REQUEST_TIME=10
# Webhook
WEBHOOK_ELOPAGE_SECRET=secret
# SET LOG LEVEL AS NEEDED IN YOUR .ENV
# POSSIBLE VALUES: all | trace | debug | info | warn | error | fatal
LOG_LEVEL=INFO
# Federation
FEDERATION_VALIDATE_COMMUNITY_TIMER=60000
# GMS
# GMS_ACTIVE=true
# Coordinates of Illuminz test instance
#GMS_API_URL=http://54.176.169.179:3071
GMS_API_URL=http://localhost:4044
GMS_DASHBOARD_URL=http://localhost:8080
# HUMHUB
HUMHUB_ACTIVE=true
HUMHUB_API_URL=https://community-test.gradido.net
HUMHUB_JWT_KEY=GwdkIKi-rkRS0mXC4Cg3MYc3ktZh89VFmntDpNKET_dUfcIdjL_957F3nCv3brNtDfbbV81NViKaktUsfExrkH

View File

@ -24,7 +24,7 @@ KLICKTIPP_APIKEY_DE=$KLICKTIPP_APIKEY_DE
KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN KLICKTIPP_APIKEY_EN=$KLICKTIPP_APIKEY_EN
# DltConnector # DltConnector
DLT_CONNECTOR=$DLT_CONNECTOR DLT_ACTIVE=$DLT_ACTIVE
DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT DLT_CONNECTOR_PORT=$DLT_CONNECTOR_PORT
# Community # Community

View File

@ -114,8 +114,7 @@ COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/build/worker.js ./wo
# add node_modules from production_node_modules # add node_modules from production_node_modules
COPY --chown=app:app --from=production-node-modules ${DOCKER_WORKDIR}/node_modules ./node_modules COPY --chown=app:app --from=production-node-modules ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy locales COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/core/build/templates ./templates
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/locales ./locales
# Run command # Run command
CMD ["node", "index.js"] CMD ["node", "index.js"]

View File

@ -1,6 +1,6 @@
{ {
"name": "backend", "name": "backend",
"version": "2.7.0", "version": "2.7.1",
"private": false, "private": false,
"description": "Gradido unified backend providing an API-Service for Gradido Transactions", "description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"repository": "https://github.com/gradido/gradido/backend", "repository": "https://github.com/gradido/gradido/backend",
@ -8,7 +8,7 @@
"author": "Gradido Academy - https://www.gradido.net", "author": "Gradido Academy - https://www.gradido.net",
"main": "src/index.ts", "main": "src/index.ts",
"scripts": { "scripts": {
"build": "ts-node ./esbuild.config.ts && mkdirp build/templates/ && ncp src/emails/templates build/templates && mkdirp locales/ && ncp src/locales locales", "build": "ts-node ./esbuild.config.ts && mkdirp build/templates/ && ncp ../core/build/templates build/templates",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css -r tsconfig-paths/register src/index.ts", "dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css -r tsconfig-paths/register src/index.ts",
"test": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --runInBand --forceExit --detectOpenHandles", "test": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --runInBand --forceExit --detectOpenHandles",
"test:coverage": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --coverage --runInBand --forceExit --detectOpenHandles", "test:coverage": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --coverage --runInBand --forceExit --detectOpenHandles",
@ -19,8 +19,6 @@
"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",
"lint:fix:unsafe": "biome check --fix --unsafe", "lint:fix:unsafe": "biome check --fix --unsafe",
"locales": "scripts/sort.sh",
"locales:fix": "scripts/sort.sh --fix",
"start": "cross-env TZ=UTC node build/index.js", "start": "cross-env TZ=UTC node build/index.js",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo" "clear": "rm -rf node_modules && rm -rf build && rm -rf .turbo"
@ -49,7 +47,6 @@
"@types/jest": "27.0.2", "@types/jest": "27.0.2",
"@types/lodash.clonedeep": "^4.5.6", "@types/lodash.clonedeep": "^4.5.6",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/nodemailer": "^6.4.4",
"@types/sodium-native": "^2.3.5", "@types/sodium-native": "^2.3.5",
"@types/source-map-support": "^0.5.10", "@types/source-map-support": "^0.5.10",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
@ -83,11 +80,9 @@
"log4js": "^6.7.1", "log4js": "^6.7.1",
"mkdirp": "^3.0.1", "mkdirp": "^3.0.1",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nodemailer": "^6.6.5",
"nodemon": "^2.0.7", "nodemon": "^2.0.7",
"openai": "^4.87.3", "openai": "^4.87.3",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"pug": "^3.0.2",
"random-bigint": "^0.0.1", "random-bigint": "^0.0.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.14.1", "regenerator-runtime": "^0.14.1",

View File

@ -1,13 +0,0 @@
def walk(f):
. as $in
| if type == "object" then
reduce keys_unsorted[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
def keys_sort_by(f):
to_entries | sort_by(.key|f ) | from_entries;
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)

View File

@ -5,17 +5,17 @@ import { DltConnectorClient } from './DltConnectorClient'
describe('undefined DltConnectorClient', () => { describe('undefined DltConnectorClient', () => {
it('invalid url', () => { it('invalid url', () => {
CONFIG.DLT_CONNECTOR_URL = '' CONFIG.DLT_CONNECTOR_URL = ''
CONFIG.DLT_CONNECTOR = true CONFIG.DLT_ACTIVE = true
const result = DltConnectorClient.getInstance() const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined() expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR_URL = 'http://dlt-connector:6010' CONFIG.DLT_CONNECTOR_URL = 'http://dlt-connector:6010'
}) })
it('DLT_CONNECTOR is false', () => { it('DLT_ACTIVE is false', () => {
CONFIG.DLT_CONNECTOR = false CONFIG.DLT_ACTIVE = false
const result = DltConnectorClient.getInstance() const result = DltConnectorClient.getInstance()
expect(result).toBeUndefined() expect(result).toBeUndefined()
CONFIG.DLT_CONNECTOR = true CONFIG.DLT_ACTIVE = true
}) })
}) })

View File

@ -31,7 +31,7 @@ export class DltConnectorClient {
* just one instance of each subclass around. * just one instance of each subclass around.
*/ */
public static getInstance(): DltConnectorClient | undefined { public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) { if (!CONFIG.DLT_ACTIVE || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`) logger.info(`dlt-connector are disabled via config...`)
return return
} }

View File

@ -30,7 +30,7 @@ export class DltConnectorClient {
* just one instance of each subclass around. * just one instance of each subclass around.
*/ */
public static getInstance(): DltConnectorClient | undefined { public static getInstance(): DltConnectorClient | undefined {
if (!CONFIG.DLT_CONNECTOR || !CONFIG.DLT_CONNECTOR_URL) { if (!CONFIG.DLT_ACTIVE || !CONFIG.DLT_CONNECTOR_URL) {
logger.info(`dlt-connector are disabled via config...`) logger.info(`dlt-connector are disabled via config...`)
return return
} }

View File

@ -66,7 +66,7 @@ async function executeDltTransaction(draft: TransactionDraft | null, typeId: Dlt
* and update dltTransactionId of transaction in db with hiero transaction id * and update dltTransactionId of transaction in db with hiero transaction id
*/ */
export async function registerAddressTransaction(user: DbUser, community: DbCommunity): Promise<DbDltTransaction | null> { export async function registerAddressTransaction(user: DbUser, community: DbCommunity): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) { if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null) return Promise.resolve(null)
} }
if (!user.id) { if (!user.id) {
@ -90,7 +90,7 @@ export async function contributionTransaction(
signingUser: DbUser, signingUser: DbUser,
createdAt: Date, createdAt: Date,
): Promise<DbDltTransaction | null> { ): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) { if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null) return Promise.resolve(null)
} }
const homeCommunity = await getHomeCommunity() const homeCommunity = await getHomeCommunity()
@ -109,7 +109,7 @@ export async function transferTransaction(
memo: string, memo: string,
createdAt: Date createdAt: Date
): Promise<DbDltTransaction | null> { ): Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) { if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null) return Promise.resolve(null)
} }
// load community if not already loaded, maybe they are remote communities // load community if not already loaded, maybe they are remote communities
@ -127,7 +127,7 @@ export async function transferTransaction(
export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink) export async function deferredTransferTransaction(senderUser: DbUser, transactionLink: DbTransactionLink)
: Promise<DbDltTransaction | null> { : Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) { if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null) return Promise.resolve(null)
} }
// load community if not already loaded // load community if not already loaded
@ -140,7 +140,7 @@ export async function deferredTransferTransaction(senderUser: DbUser, transactio
export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser) export async function redeemDeferredTransferTransaction(transactionLink: DbTransactionLink, amount: string, createdAt: Date, recipientUser: DbUser)
: Promise<DbDltTransaction | null> { : Promise<DbDltTransaction | null> {
if (!CONFIG.DLT_CONNECTOR) { if (!CONFIG.DLT_ACTIVE) {
return Promise.resolve(null) return Promise.resolve(null)
} }
// load user and communities if not already loaded // load user and communities if not already loaded

View File

@ -4,7 +4,7 @@ import { User as DbUser } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
// import { createGmsUser } from '@/apis/gms/GmsClient' // import { createGmsUser } from '@/apis/gms/GmsClient'
// import { GmsUser } from '@/apis/gms/model/GmsUser' // import { GmsUser } from '@/apis/gms/model/GmsUser'
import { CONFIG } from '@/config' import { CONFIG as CORE_CONFIG } from 'core'
import { sendUserToGms } from '@/graphql/resolver/util/sendUserToGms' import { sendUserToGms } from '@/graphql/resolver/util/sendUserToGms'
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
import { initLogging } from '@/server/logger' import { initLogging } from '@/server/logger'
@ -13,7 +13,7 @@ import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.gms.ExportUsers`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apis.gms.ExportUsers`)
CONFIG.EMAIL = false CORE_CONFIG.EMAIL = false
// use force to copy over all user even if gmsRegistered is set to true // use force to copy over all user even if gmsRegistered is set to true
const forceMode = process.argv.includes('--force') const forceMode = process.argv.includes('--force')

View File

@ -92,6 +92,7 @@ export class OpenaiClient {
if (openaiThreadEntity.updatedAt < new Date(Date.now() - OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS * 24 * 60 * 60 * 1000)) { if (openaiThreadEntity.updatedAt < new Date(Date.now() - OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS * 24 * 60 * 60 * 1000)) {
logger.info(`Openai thread for user: ${user.id} is older than ${OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS} days, deleting...`) logger.info(`Openai thread for user: ${user.id} is older than ${OPENAI_AI_THREAD_DEFAULT_TIMEOUT_DAYS} days, deleting...`)
// let run async, because it could need some time, but we don't need to wait, because we create a new one nevertheless // let run async, because it could need some time, but we don't need to wait, because we create a new one nevertheless
// biome-ignore lint/complexity/noVoid: start it intentionally async without waiting for result
void this.deleteThread(openaiThreadEntity.id) void this.deleteThread(openaiThreadEntity.id)
return [] return []
} }

View File

@ -17,6 +17,7 @@ const logging = {
const server = { const server = {
BACKEND_PORT: process.env.BACKEND_PORT ?? 4000, BACKEND_PORT: process.env.BACKEND_PORT ?? 4000,
DLT_ACTIVE: process.env.DLT_ACTIVE === 'true' || false,
JWT_SECRET: process.env.JWT_SECRET ?? 'secret123', JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '10m', JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '10m',
REDEEM_JWT_TOKEN_EXPIRATION: process.env.REDEEM_JWT_TOKEN_EXPIRATION ?? '10m', REDEEM_JWT_TOKEN_EXPIRATION: process.env.REDEEM_JWT_TOKEN_EXPIRATION ?? '10m',
@ -41,9 +42,7 @@ const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNIT
const DLT_CONNECTOR_PORT = process.env.DLT_CONNECTOR_PORT ?? 6010 const DLT_CONNECTOR_PORT = process.env.DLT_CONNECTOR_PORT ?? 6010
const dltConnector = { const dltConnector = {
DLT_CONNECTOR: process.env.DLT_CONNECTOR === 'true' || false,
DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? `${COMMUNITY_URL}:${DLT_CONNECTOR_PORT}`, DLT_CONNECTOR_URL: process.env.DLT_CONNECTOR_URL ?? `${COMMUNITY_URL}:${DLT_CONNECTOR_PORT}`,
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: process.env.DLT_GRADIDO_NODE_SERVER_HOME_FOLDER ?? '~/.gradido',
} }
const community = { const community = {
@ -64,22 +63,10 @@ const loginServer = {
} }
const email = { const email = {
EMAIL: process.env.EMAIL === 'true',
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true',
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD ?? '',
EMAIL_SMTP_HOST: process.env.EMAIL_SMTP_HOST ?? 'mailserver',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
EMAIL_TLS: process.env.EMAIL_TLS !== 'false',
EMAIL_LINK_VERIFICATION: EMAIL_LINK_VERIFICATION:
COMMUNITY_URL + (process.env.EMAIL_LINK_VERIFICATION_PATH ?? '/checkEmail/'), COMMUNITY_URL + (process.env.EMAIL_LINK_VERIFICATION_PATH ?? '/checkEmail/'),
EMAIL_LINK_SETPASSWORD: EMAIL_LINK_SETPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_SETPASSWORD_PATH ?? '/reset-password/'), COMMUNITY_URL + (process.env.EMAIL_LINK_SETPASSWORD_PATH ?? '/reset-password/'),
EMAIL_LINK_FORGOTPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_FORGOTPASSWORD_PATH ?? '/forgot-password'),
EMAIL_LINK_OVERVIEW: COMMUNITY_URL + (process.env.EMAIL_LINK_OVERVIEW_PATH ?? '/overview'), EMAIL_LINK_OVERVIEW: COMMUNITY_URL + (process.env.EMAIL_LINK_OVERVIEW_PATH ?? '/overview'),
// time in minutes a optin code is valid // time in minutes a optin code is valid
EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME

View File

@ -4,6 +4,7 @@ import {
COMMUNITY_SUPPORT_MAIL, COMMUNITY_SUPPORT_MAIL,
COMMUNITY_URL, COMMUNITY_URL,
DECAY_START_TIME, DECAY_START_TIME,
DLT_ACTIVE,
GDT_ACTIVE, GDT_ACTIVE,
GDT_API_URL, GDT_API_URL,
GMS_ACTIVE, GMS_ACTIVE,
@ -27,6 +28,7 @@ export const schema = Joi.object({
COMMUNITY_DESCRIPTION, COMMUNITY_DESCRIPTION,
COMMUNITY_SUPPORT_MAIL, COMMUNITY_SUPPORT_MAIL,
DECAY_START_TIME, DECAY_START_TIME,
DLT_ACTIVE,
GDT_API_URL, GDT_API_URL,
GDT_ACTIVE, GDT_ACTIVE,
GMS_ACTIVE, GMS_ACTIVE,
@ -68,90 +70,11 @@ export const schema = Joi.object({
.default('http://0.0.0.0/redeem/CL-') .default('http://0.0.0.0/redeem/CL-')
.required(), .required(),
DLT_CONNECTOR: Joi.boolean()
.description('Flag to indicate if DLT-Connector is used. (Still in development)')
.default(false)
.required(),
DLT_CONNECTOR_URL: Joi.string() DLT_CONNECTOR_URL: Joi.string()
.uri({ scheme: ['http', 'https'] }) .uri({ scheme: ['http', 'https'] })
.default('http://localhost:6010') .default('http://localhost:6010')
.when('DLT_CONNECTOR', { is: true, then: Joi.required() }) .when('DLT_ACTIVE', { is: true, then: Joi.required() })
.description('The URL for GDT API endpoint'), .description('The URL for DLT connector'),
DLT_GRADIDO_NODE_SERVER_HOME_FOLDER: Joi.string()
.default('~/.gradido')
.description('The home folder for the gradido dlt node server'),
EMAIL: Joi.boolean()
.default(false)
.description('Enable or disable email functionality')
.required(),
EMAIL_TEST_MODUS: Joi.boolean()
.default(false)
.description('When enabled, all emails are sended to EMAIL_TEST_RECEIVER')
.optional(),
EMAIL_TEST_RECEIVER: Joi.string()
.email()
.default('stage1@gradido.net')
.when('EMAIL_TEST_MODUS', { is: true, then: Joi.required() })
.description('Email address used in test mode'),
EMAIL_USERNAME: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Username for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
.description('Valid SMTP username required in production')
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SENDER: Joi.string()
.email()
.when('EMAIL', { is: true, then: Joi.required() })
.default('info@gradido.net')
.description('Email address used as sender'),
EMAIL_PASSWORD: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Password for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#]).{8,}$/)
.description(
'Password must be at least 8 characters long, include uppercase and lowercase letters, a number, and a special character',
)
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SMTP_HOST: Joi.string()
.hostname()
.when('EMAIL', { is: true, then: Joi.required() })
.default('mailserver')
.description('SMTP server hostname'),
EMAIL_SMTP_PORT: Joi.number()
.integer()
.positive()
.when('EMAIL', { is: true, then: Joi.required() })
.default(1025)
.description('SMTP server port'),
EMAIL_TLS: Joi.boolean().default(true).description('Enable or disable TLS for SMTP').optional(),
EMAIL_LINK_VERIFICATION: Joi.string() EMAIL_LINK_VERIFICATION: Joi.string()
.uri({ scheme: ['http', 'https'] }) .uri({ scheme: ['http', 'https'] })
@ -175,17 +98,6 @@ export const schema = Joi.object({
.description('Email Verification link for set initial Password.') .description('Email Verification link for set initial Password.')
.required(), .required(),
EMAIL_LINK_FORGOTPASSWORD: Joi.string()
.uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
if (!value.startsWith(helpers.state.ancestors[0].COMMUNITY_URL)) {
return helpers.error('string.pattern.base', { value, communityUrl: COMMUNITY_URL })
}
return value
})
.description('Email Verification link for set new Password, when old Password was forgotten.')
.required(),
EMAIL_LINK_OVERVIEW: Joi.string() EMAIL_LINK_OVERVIEW: Joi.string()
.uri({ scheme: ['http', 'https'] }) .uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => { .custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
@ -213,7 +125,7 @@ export const schema = Joi.object({
.description('Time in minutes before a new code can be requested') .description('Time in minutes before a new code can be requested')
.required(), .required(),
FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number() FEDERATION_VALIDATE_COMMUNITY_TIMER: Joi.number()
.integer() .integer()
.min(1000) .min(1000)
.default(60000) .default(60000)

View File

@ -1,225 +0,0 @@
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '@/config'
import { decimalSeparatorByLanguage } from 'core'
import { sendEmailTranslated } from './sendEmailTranslated'
export interface ContributionEmailCommonData {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionFrontendLink: string
}
function toContributionEmailLocales(data: ContributionEmailCommonData): Record<string, unknown> {
return {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
contributionFrontendLink: data.contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}
}
export const sendAddedContributionMessageEmail = (
data: ContributionEmailCommonData & {
message: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
},
template: 'addedContributionMessage',
locals: {
...toContributionEmailLocales(data),
message: data.message,
},
})
}
export const sendAccountActivationEmail = (data: {
firstName: string
lastName: string
email: string
language: string
activationLink: string
timeDurationObject: Record<string, unknown>
logoUrl?: string | null
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountActivation',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
activationLink: data.activationLink,
timeDurationObject: data.timeDurationObject,
logoUrl: data.logoUrl,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendAccountMultiRegistrationEmail = (data: {
firstName: string
lastName: string
email: string
language: string
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountMultiRegistration',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionConfirmedEmail = (
data: ContributionEmailCommonData & {
contributionAmount: Decimal
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
locals: {
...toContributionEmailLocales(data),
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
},
})
}
export const sendContributionChangedByModeratorEmail = (
data: ContributionEmailCommonData & {
contributionMemoUpdated: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionChangedByModerator',
locals: {
...toContributionEmailLocales(data),
contributionMemoUpdated: data.contributionMemoUpdated,
},
})
}
export const sendContributionDeletedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: toContributionEmailLocales(data),
})
}
export const sendContributionDeniedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
locals: toContributionEmailLocales(data),
})
}
export const sendResetPasswordEmail = (data: {
firstName: string
lastName: string
email: string
language: string
resetLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'resetPassword',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
resetLink: data.resetLink,
timeDurationObject: data.timeDurationObject,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendTransactionLinkRedeemedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
senderEmail: string
transactionMemo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionLinkRedeemed',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
senderEmail: data.senderEmail,
transactionMemo: data.transactionMemo,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendTransactionReceivedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
senderEmail: string
memo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionReceived',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
memo: data.memo,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
senderEmail: data.senderEmail,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}

View File

@ -5,7 +5,6 @@ import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers' import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { userFactory } from '@/seeds/factory/user' import { userFactory } from '@/seeds/factory/user'
import { login, updateHomeCommunityQuery } from '@/seeds/graphql/mutations' import { login, updateHomeCommunityQuery } from '@/seeds/graphql/mutations'
@ -43,7 +42,7 @@ const peterLoginData = {
} }
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization) testEnv = await testEnvironment(getLogger('apollo'))
mutate = testEnv.mutate mutate = testEnv.mutate
query = testEnv.query query = testEnv.query
con = testEnv.con con = testEnv.con

View File

@ -5,10 +5,9 @@ import { DataSource } from 'typeorm'
import { ContributionStatus } from '@enum/ContributionStatus' import { ContributionStatus } from '@enum/ContributionStatus'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants' import { sendAddedContributionMessageEmail } from 'core'
import { EventType } from '@/event/Events' import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user' import { userFactory } from '@/seeds/factory/user'
import { import {
@ -30,14 +29,12 @@ const interactionLogger = getLogger(
) )
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
jest.mock('@/emails/sendEmailVariants', () => { jest.mock('core', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants') const originalModule = jest.requireActual('core')
return { return {
__esModule: true, __esModule: true,
...originalModule, ...originalModule,
sendAddedContributionMessageEmail: jest.fn((a) => sendAddedContributionMessageEmail: jest.fn(),
originalModule.sendAddedContributionMessageEmail(a),
),
} }
}) })
@ -51,7 +48,7 @@ let testEnv: {
let result: any let result: any
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment(logger, localization) testEnv = await testEnvironment(logger)
mutate = testEnv.mutate mutate = testEnv.mutate
con = testEnv.con con = testEnv.con
await cleanDB() await cleanDB()

View File

@ -14,7 +14,7 @@ import { Order } from '@enum/Order'
import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage' import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants' import { sendAddedContributionMessageEmail } from 'core'
import { import {
EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE, EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE,
EVENT_CONTRIBUTION_MESSAGE_CREATE, EVENT_CONTRIBUTION_MESSAGE_CREATE,

View File

@ -14,14 +14,13 @@ import {
resetToken, resetToken,
testEnvironment, testEnvironment,
} from '@test/helpers' } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { import {
sendContributionConfirmedEmail, sendContributionConfirmedEmail,
sendContributionDeletedEmail, sendContributionDeletedEmail,
sendContributionDeniedEmail, sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants' } from 'core'
import { EventType } from '@/event/Events' import { EventType } from '@/event/Events'
import { creations } from '@/seeds/creation/index' import { creations } from '@/seeds/creation/index'
import { creationFactory } from '@/seeds/factory/creation' import { creationFactory } from '@/seeds/factory/creation'
@ -54,7 +53,17 @@ import { getFirstDayOfPreviousNMonth } from 'core'
import { getLogger } from 'config-schema/test/testSetup' import { getLogger } from 'config-schema/test/testSetup'
import { getLogger as originalGetLogger } from 'log4js' import { getLogger as originalGetLogger } from 'log4js'
jest.mock('@/emails/sendEmailVariants') jest.mock('core', () => {
const originalModule = jest.requireActual('core')
return {
__esModule: true,
...originalModule,
sendContributionDeniedEmail: jest.fn(),
sendContributionConfirmedEmail: jest.fn(),
sendContributionDeletedEmail: jest.fn(),
sendEmailTranslated: jest.fn(),
}
})
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
@ -77,7 +86,7 @@ let contributionToDelete: any
let bibiCreatedContribution: Contribution let bibiCreatedContribution: Contribution
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment(originalGetLogger('apollo'), localization) testEnv = await testEnvironment(originalGetLogger('apollo'))
mutate = testEnv.mutate mutate = testEnv.mutate
query = testEnv.query query = testEnv.query
con = testEnv.con con = testEnv.con

View File

@ -21,15 +21,15 @@ import { AdminUpdateContribution } from '@model/AdminUpdateContribution'
import { Contribution, ContributionListResult } from '@model/Contribution' import { Contribution, ContributionListResult } from '@model/Contribution'
import { OpenCreation } from '@model/OpenCreation' import { OpenCreation } from '@model/OpenCreation'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { TransactionTypeId } from 'core'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { import {
fullName,
sendContributionChangedByModeratorEmail, sendContributionChangedByModeratorEmail,
sendContributionConfirmedEmail, sendContributionConfirmedEmail,
sendContributionDeletedEmail, sendContributionDeletedEmail,
sendContributionDeniedEmail, sendContributionDeniedEmail,
} from '@/emails/sendEmailVariants' TransactionTypeId
} from 'core'
import { import {
EVENT_ADMIN_CONTRIBUTION_CONFIRM, EVENT_ADMIN_CONTRIBUTION_CONFIRM,
EVENT_ADMIN_CONTRIBUTION_CREATE, EVENT_ADMIN_CONTRIBUTION_CREATE,
@ -44,7 +44,6 @@ import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUncon
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context' import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { TRANSACTIONS_LOCK } from 'database' import { TRANSACTIONS_LOCK } from 'database'
import { fullName } from 'core'
import { calculateDecay, Decay } from 'shared' import { calculateDecay, Decay } from 'shared'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'

View File

@ -5,6 +5,7 @@ import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers' import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG as CORE_CONFIG } from 'core'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { writeHomeCommunityEntry } from '@/seeds/community' import { writeHomeCommunityEntry } from '@/seeds/community'
import { createUser, forgotPassword, setPassword } from '@/seeds/graphql/mutations' import { createUser, forgotPassword, setPassword } from '@/seeds/graphql/mutations'
@ -21,7 +22,7 @@ let testEnv: {
CONFIG.EMAIL_CODE_VALID_TIME = 1440 CONFIG.EMAIL_CODE_VALID_TIME = 1440
CONFIG.EMAIL_CODE_REQUEST_TIME = 10 CONFIG.EMAIL_CODE_REQUEST_TIME = 10
CONFIG.EMAIL = false CORE_CONFIG.EMAIL = false
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment() testEnv = await testEnvironment()

View File

@ -2,7 +2,6 @@ import { Event as DbEvent, UserContact } from 'database'
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup' import { getLogger } from 'config-schema/test/testSetup'
import { EventType } from '@/event/Events' import { EventType } from '@/event/Events'
@ -20,7 +19,7 @@ let mutate: any
let con: any let con: any
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment(logger, localization) testEnv = await testEnvironment(logger)
mutate = testEnv.mutate mutate = testEnv.mutate
con = testEnv.con con = testEnv.con
await cleanDB() await cleanDB()

View File

@ -43,7 +43,7 @@ const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false CONFIG.DLT_ACTIVE = false
// mock semaphore to allow use fake timers // mock semaphore to allow use fake timers
jest.mock('database/src/util/TRANSACTIONS_LOCK') jest.mock('database/src/util/TRANSACTIONS_LOCK')

View File

@ -34,12 +34,13 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking' import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getLogger } from 'config-schema/test/testSetup' import { getLogger } from 'config-schema/test/testSetup'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG} from 'core'
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
CONFIG.DLT_CONNECTOR = false CONFIG.DLT_ACTIVE = false
CONFIG.EMAIL = false CORE_CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate'] let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query'] let query: ApolloServerTestClient['query']
@ -72,7 +73,6 @@ let peter: User
let homeCom: DbCommunity let homeCom: DbCommunity
let foreignCom: DbCommunity let foreignCom: DbCommunity
let fedForeignCom: DbFederatedCommunity
describe('send coins', () => { describe('send coins', () => {
beforeAll(async () => { beforeAll(async () => {

View File

@ -1,14 +1,11 @@
import { import {
AppDatabase, AppDatabase,
countOpenPendingTransactions, countOpenPendingTransactions,
Community as DbCommunity,
DltTransaction as DbDltTransaction, DltTransaction as DbDltTransaction,
Transaction as dbTransaction, Transaction as dbTransaction,
TransactionLink as dbTransactionLink, TransactionLink as dbTransactionLink,
User as dbUser, User as dbUser,
findUserByIdentifier, findUserByIdentifier,
TransactionLoggingView,
UserLoggingView
} from 'database' } from 'database'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql' import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
@ -20,21 +17,22 @@ import { Order } from '@enum/Order'
import { Transaction } from '@model/Transaction' import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList' import { TransactionList } from '@model/TransactionList'
import { User } from '@model/User' import { User } from '@model/User'
import { processXComCompleteTransaction, TransactionTypeId } from 'core' import {
fullName,
processXComCompleteTransaction,
sendTransactionLinkRedeemedEmail,
sendTransactionReceivedEmail,
TransactionTypeId
} from 'core'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { import {
sendTransactionLinkRedeemedEmail, EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
sendTransactionReceivedEmail,
} from '@/emails/sendEmailVariants'
import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Events'
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context' import { Context, getUser } from '@/server/context'
import { communityUser } from '@/util/communityUser' import { communityUser } from '@/util/communityUser'
import { calculateBalance } from '@/util/validate' import { calculateBalance } from '@/util/validate'
import { virtualDecayTransaction, virtualLinkTransaction } from '@/util/virtualTransactions' import { virtualDecayTransaction, virtualLinkTransaction } from '@/util/virtualTransactions'
import { fullName } from 'core'
import { TRANSACTIONS_LOCK } from 'database' import { TRANSACTIONS_LOCK } from 'database'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'

View File

@ -20,7 +20,6 @@ import { UserContactType } from '@enum/UserContactType'
import { ContributionLink } from '@model/ContributionLink' import { ContributionLink } from '@model/ContributionLink'
import { Location } from '@model/Location' import { Location } from '@model/Location'
import { cleanDB, headerPushMock, resetToken, testEnvironment } from '@test/helpers' import { cleanDB, headerPushMock, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController' import { subscribe } from '@/apis/KlicktippController'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
@ -28,7 +27,7 @@ import {
sendAccountActivationEmail, sendAccountActivationEmail,
sendAccountMultiRegistrationEmail, sendAccountMultiRegistrationEmail,
sendResetPasswordEmail, sendResetPasswordEmail,
} from '@/emails/sendEmailVariants' } from 'core'
import { EventType } from '@/event/Events' import { EventType } from '@/event/Events'
import { PublishNameType } from '@/graphql/enum/PublishNameType' import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils' import { SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
@ -74,16 +73,15 @@ import { Location2Point } from './util/Location2Point'
jest.mock('@/apis/humhub/HumHubClient') jest.mock('@/apis/humhub/HumHubClient')
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
jest.mock('@/emails/sendEmailVariants', () => { jest.mock('core', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants') const originalModule = jest.requireActual('core')
return { return {
__esModule: true, __esModule: true,
...originalModule, ...originalModule,
sendAccountActivationEmail: jest.fn((a) => originalModule.sendAccountActivationEmail(a)), sendAccountActivationEmail: jest.fn(),
sendAccountMultiRegistrationEmail: jest.fn((a) => sendAccountMultiRegistrationEmail: jest.fn(),
originalModule.sendAccountMultiRegistrationEmail(a), sendResetPasswordEmail: jest.fn(),
), sendEmailTranslated: jest.fn(),
sendResetPasswordEmail: jest.fn((a) => originalModule.sendResetPasswordEmail(a)),
} }
}) })
@ -112,12 +110,12 @@ let testEnv: {
} }
beforeAll(async () => { beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization) testEnv = await testEnvironment(getLogger('apollo'))
mutate = testEnv.mutate mutate = testEnv.mutate
query = testEnv.query query = testEnv.query
con = testEnv.con con = testEnv.con
CONFIG.HUMHUB_ACTIVE = false CONFIG.HUMHUB_ACTIVE = false
CONFIG.DLT_CONNECTOR = false CONFIG.DLT_ACTIVE = false
await cleanDB() await cleanDB()
}) })
@ -155,6 +153,7 @@ describe('UserResolver', () => {
expect(result).toEqual( expect(result).toEqual(
expect.objectContaining({ data: { createUser: { id: expect.any(Number) } } }), expect.objectContaining({ data: { createUser: { id: expect.any(Number) } } }),
) )
}) })
describe('valid input data', () => { describe('valid input data', () => {

View File

@ -10,7 +10,6 @@ import {
findUserByIdentifier findUserByIdentifier
} from 'database' } from 'database'
import { GraphQLResolveInfo } from 'graphql' import { GraphQLResolveInfo } from 'graphql'
import i18n from 'i18n'
import { import {
Arg, Arg,
Args, Args,
@ -57,11 +56,6 @@ import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { PublishNameLogic } from '@/data/PublishName.logic' import { PublishNameLogic } from '@/data/PublishName.logic'
import {
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from '@/emails/sendEmailVariants'
import { import {
EVENT_ADMIN_USER_DELETE, EVENT_ADMIN_USER_DELETE,
EVENT_ADMIN_USER_ROLE_SET, EVENT_ADMIN_USER_ROLE_SET,
@ -85,8 +79,12 @@ import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { communityDbUser } from '@/util/communityUser' import { communityDbUser } from '@/util/communityUser'
import { hasElopageBuys } from '@/util/hasElopageBuys' import { hasElopageBuys } from '@/util/hasElopageBuys'
import { durationInMinutesFromDates, getTimeDurationObject, printTimeDuration } from '@/util/time' import { durationInMinutesFromDates, getTimeDurationObject, printTimeDuration } from '@/util/time'
import { delay } from 'core' import {
delay,
sendAccountActivationEmail,
sendAccountMultiRegistrationEmail,
sendResetPasswordEmail,
} from 'core'
import random from 'random-bigint' import random from 'random-bigint'
import { randombytes_random } from 'sodium-native' import { randombytes_random } from 'sodium-native'
@ -233,7 +231,6 @@ export class UserResolver {
logger.debug('validation of login credentials successful...') logger.debug('validation of login credentials successful...')
const user = new User(dbUser) const user = new User(dbUser)
i18n.setLocale(user.language)
// Elopage Status & Stored PublisherId // Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage({ ...context, user: dbUser }) user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
@ -322,7 +319,6 @@ export class UserResolver {
if (!language || !isLanguage(language)) { if (!language || !isLanguage(language)) {
language = DEFAULT_LANGUAGE language = DEFAULT_LANGUAGE
} }
i18n.setLocale(language)
// check if user with email still exists? // check if user with email still exists?
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
@ -764,7 +760,6 @@ export class UserResolver {
throw new LogError('Given language is not a valid language or not supported') throw new LogError('Given language is not a valid language or not supported')
} }
user.language = language user.language = language
i18n.setLocale(language)
updated = true updated = true
} }

View File

@ -22,12 +22,13 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig' import { peterLustig } from '@/seeds/users/peter-lustig'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { TRANSACTIONS_LOCK } from 'database' import { TRANSACTIONS_LOCK } from 'database'
jest.mock('@/password/EncryptorUtils') jest.mock('@/password/EncryptorUtils')
CONFIG.DLT_CONNECTOR = false CONFIG.DLT_ACTIVE = false
CONFIG.EMAIL = false CORE_CONFIG.EMAIL = false
let mutate: ApolloServerTestClient['mutate'] let mutate: ApolloServerTestClient['mutate']
let con: DataSource let con: DataSource

View File

@ -18,7 +18,6 @@ export const userFactory = async (
// console.log('call createUser with', JSON.stringify(user, null, 2)) // console.log('call createUser with', JSON.stringify(user, null, 2))
const response = await mutate({ mutation: createUser, variables: user }) const response = await mutate({ mutation: createUser, variables: user })
if (!response?.data?.createUser) { if (!response?.data?.createUser) {
// biome-ignore lint/suspicious/noConsole: will be used in tests where logging is mocked
// console.log(JSON.stringify(response, null, 2)) // console.log(JSON.stringify(response, null, 2))
throw new Error('createUser mutation returned unexpected response') throw new Error('createUser mutation returned unexpected response')
} }

View File

@ -3,6 +3,7 @@ import { entities } from 'database'
import { datatype, internet, name } from 'faker' import { datatype, internet, name } from 'faker'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { createServer } from '@/server/createServer' import { createServer } from '@/server/createServer'
import { initLogging } from '@/server/logger' import { initLogging } from '@/server/logger'
@ -17,7 +18,7 @@ import { userFactory } from './factory/user'
import { transactionLinks } from './transactionLink/index' import { transactionLinks } from './transactionLink/index'
import { users } from './users/index' import { users } from './users/index'
CONFIG.EMAIL = false CORE_CONFIG.EMAIL = false
const logger = getLogger('seed') const logger = getLogger('seed')
const context = { const context = {

View File

@ -1,4 +1,5 @@
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { CONFIG as CORE_CONFIG } from 'core'
import { schema } from '@/graphql/schema' import { schema } from '@/graphql/schema'
import { elopageWebhook } from '@/webhook/elopage' import { elopageWebhook } from '@/webhook/elopage'
import { gmsWebhook } from '@/webhook/gms' import { gmsWebhook } from '@/webhook/gms'
@ -28,7 +29,6 @@ interface ServerDef {
export const createServer = async ( export const createServer = async (
apolloLogger: Logger, apolloLogger: Logger,
context: any = serverContext, context: any = serverContext,
localization: i18n.I18n = i18n,
): Promise<ServerDef> => { ): Promise<ServerDef> => {
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.createServer`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.createServer`)
logger.debug('createServer...') logger.debug('createServer...')
@ -73,9 +73,9 @@ 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 // i18n
app.use(localization.init) app.use(i18n.init)
// Elopage Webhook // Elopage Webhook
@ -100,7 +100,7 @@ export const createServer = async (
}) })
apollo.applyMiddleware({ app, path: '/' }) apollo.applyMiddleware({ app, path: '/' })
logger.info( logger.info(
`running with PRODUCTION=${CONFIG.PRODUCTION}, sending EMAIL enabled=${CONFIG.EMAIL} and EMAIL_TEST_MODUS=${CONFIG.EMAIL_TEST_MODUS} ...`, `running with PRODUCTION=${CONFIG.PRODUCTION}, sending EMAIL enabled=${CORE_CONFIG.EMAIL} and EMAIL_TEST_MODUS=${CORE_CONFIG.EMAIL_TEST_MODUS} ...`,
) )
logger.debug('createServer...successful') logger.debug('createServer...successful')

View File

@ -1,4 +1,3 @@
import path from 'node:path'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import i18n from 'i18n' import i18n from 'i18n'
import { getLogger } from 'log4js' import { getLogger } from 'log4js'
@ -9,7 +8,10 @@ i18n.configure({
locales: ['en', 'de'], locales: ['en', 'de'],
defaultLocale: 'en', defaultLocale: 'en',
retryInDefaultLocale: false, retryInDefaultLocale: false,
directory: path.join(__dirname, '..', 'locales'), staticCatalog: {
en: { general: { decimalSeparator: "." } },
de: { general: { decimalSeparator: "," } },
},
// autoReload: true, // if this is activated the seeding hangs at the very end // autoReload: true, // if this is activated the seeding hangs at the very end
updateFiles: false, updateFiles: false,
objectNotation: true, objectNotation: true,

View File

@ -3,8 +3,6 @@ import { entities } from 'database'
import { createServer } from '@/server/createServer' import { createServer } from '@/server/createServer'
import { i18n } from './testSetup'
import { getLogger } from 'log4js' import { getLogger } from 'log4js'
export const headerPushMock = jest.fn((t) => { export const headerPushMock = jest.fn((t) => {
@ -29,8 +27,8 @@ export const cleanDB = async () => {
} }
} }
export const testEnvironment = async (testLogger = getLogger('apollo'), testI18n = i18n) => { export const testEnvironment = async (testLogger = getLogger('apollo')) => {
const server = await createServer( testLogger, context, testI18n) const server = await createServer( testLogger, context)
const con = server.con const con = server.con
const testClient = createTestClient(server.apollo) const testClient = createTestClient(server.apollo)
const mutate = testClient.mutate const mutate = testClient.mutate

View File

@ -1,27 +1,13 @@
import 'openai/shims/node' import 'openai/shims/node'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { i18n } from '@/server/localization' import { CONFIG as CORE_CONFIG } from 'core'
import { getLogger, printLogs, clearLogs } from 'config-schema/test/testSetup' import { getLogger, printLogs, clearLogs } from 'config-schema/test/testSetup'
CONFIG.EMAIL = true CORE_CONFIG.EMAIL = false
CONFIG.EMAIL_TEST_MODUS = false CORE_CONFIG.EMAIL_TEST_MODUS = false
CONFIG.HUMHUB_ACTIVE = false CONFIG.HUMHUB_ACTIVE = false
CONFIG.GMS_ACTIVE = false CONFIG.GMS_ACTIVE = false
jest.setTimeout(1000000) jest.setTimeout(1000000)
jest.mock('@/server/localization', () => { export { getLogger, printLogs, clearLogs as cleanLogs }
const originalModule = jest.requireActual<typeof i18n>('@/server/localization')
return {
__esModule: true,
...originalModule,
i18n: {
init: jest.fn(),
// configure: jest.fn(),
// __: jest.fn(),
// setLocale: jest.fn(),
},
}
})
export { i18n, getLogger, printLogs, clearLogs as cleanLogs }

View File

@ -63,7 +63,7 @@
"typeRoots": [ /* List of folders to include type definitions from. */ "typeRoots": [ /* List of folders to include type definitions from. */
"@types", "@types",
"node_modules/@types", "node_modules/@types",
"../node_modules/@types" "../node_modules/@types",
], ],
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
@ -86,6 +86,9 @@
"skipLibCheck": true, /* Skip type checking of declaration files. */ "skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
}, },
"include": [
"../core/src/types"
],
"ts-node": { "ts-node": {
"swc": true "swc": true
} }

View File

@ -8,10 +8,8 @@
"locales": {}, "locales": {},
"locales:fix": {}, "locales:fix": {},
"lint": { "lint": {
"dependsOn": ["locales"]
}, },
"lint:fix": { "lint:fix": {
"dependsOn": ["locales:fix"]
}, },
"test": { "test": {
"dependsOn": ["database#up:backend_test", "^build"] "dependsOn": ["database#up:backend_test", "^build"]

416
bun.lock

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "config-schema", "name": "config-schema",
"version": "2.7.0", "version": "2.7.1",
"description": "Gradido Config for validate config", "description": "Gradido Config for validate config",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",

View File

@ -35,6 +35,11 @@ export const COMMUNITY_URL = Joi.string()
.default('http://0.0.0.0') .default('http://0.0.0.0')
.required() .required()
export const DLT_ACTIVE = Joi.boolean()
.description('Flag to indicate if the DLT (Decentralized Ledger Technology) service is used.')
.default(false)
.required()
export const GRAPHQL_URI = Joi.string() export const GRAPHQL_URI = Joi.string()
.uri({ scheme: ['http', 'https'] }) .uri({ scheme: ['http', 'https'] })
.description( .description(

View File

@ -17,17 +17,23 @@ export function validate(schema: ObjectSchema, data: any) {
throw new Error('missing key in config validation with joi: ' + details) throw new Error('missing key in config validation with joi: ' + details)
} }
const value = err.context.value const value = err.context.value
const description = schemaJson.keys[key] try {
? schema.describe().keys[key].flags.description const description = schemaJson.keys[key]
: 'No description available' ? schema.describe().keys[key].flags.description
if (data[key] === undefined) { : 'No description available'
throw new Error( if (data[key] === undefined) {
`Environment Variable '${key}' is missing. ${description}, details: ${details}`, throw new Error(
) `Environment Variable '${key}' is missing. ${description}, details: ${details}`,
} else { )
throw new Error( } else {
`Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`, throw new Error(
) `Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`,
)
}
} catch (e) {
// biome-ignore lint/suspicious/noConsole: schema validation may be run before logger is initialized
console.error('Error getting description for key ' + key + ': ' + e)
throw e
} }
}) })
} }

View File

@ -91,7 +91,8 @@ const getLoggerMocked = mock().mockImplementation((param: any) => {
}) })
mock.module('log4js', () => ({ mock.module('log4js', () => ({
getLogger: getLoggerMocked getLogger: getLoggerMocked,
addLayout: jest.fn()
})) }))
export function getLogger(name: string) { export function getLogger(name: string) {

16
core/esbuild.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { build } from 'esbuild'
build({
entryPoints: ['src/index.ts'],
outdir: 'build',
platform: 'node',
target: 'node18.20.7',
loader: {
'.png': 'binary',
'.jpeg': 'binary',
'.jpg': 'binary',
},
bundle: true,
sourcemap: true,
packages: 'external',
})

View File

@ -1,6 +1,6 @@
{ {
"name": "core", "name": "core",
"version": "2.7.0", "version": "2.7.1",
"description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules", "description": "Gradido Core Code, High-Level Shared Code, with dependencies on other modules",
"main": "./build/index.js", "main": "./build/index.js",
"types": "./src/index.ts", "types": "./src/index.ts",
@ -15,37 +15,46 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "esbuild src/index.ts --outdir=build --platform=node --target=node18.20.7 --bundle --packages=external", "build": "bun esbuild.config.ts && mkdirp build/templates/ && ncp src/emails/templates build/templates",
"build:bun": "bun build src/index.ts --outdir=build --target=bun --packages=external", "build:bun": "bun build src/index.ts --outdir=build --target=bun --packages=external",
"test": "bun test", "test": "bun test",
"test:debug": "bun test --inspect-brk", "test:debug": "bun test --inspect-brk",
"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:fix": "scripts/sort.sh src/emails/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": {
"database": "*", "database": "*",
"email-templates": "^10.0.1",
"esbuild": "^0.25.2", "esbuild": "^0.25.2",
"i18n": "^0.15.1", "i18n": "^0.15.1",
"joi": "^17.13.3", "joi": "^17.13.3",
"jose": "^4.14.4", "jose": "^4.14.4",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"nodemailer": "^6.6.5",
"pug": "^3.0.2",
"shared": "*", "shared": "*",
"sodium-native": "^3.4.1", "sodium-native": "^3.4.1",
"zod": "^3.25.61" "zod": "^3.25.61"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.0.0", "@biomejs/biome": "2.0.0",
"@types/email-templates": "^10.0.4",
"@types/i18n": "^0.13.4", "@types/i18n": "^0.13.4",
"@types/minimatch": "6.0.0", "@types/minimatch": "6.0.0",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/nodemailer": "^6.4.4",
"@types/sodium-native": "^2.3.5", "@types/sodium-native": "^2.3.5",
"config-schema": "*", "config-schema": "*",
"decimal.js-light": "^2.5.1", "decimal.js-light": "^2.5.1",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"graphql-request": "5.0.0", "graphql-request": "5.0.0",
"jest": "27.2.4", "jest": "27.2.4",
"mkdirp": "^3.0.1",
"ncp": "^2.0.0",
"type-graphql": "^1.1.1", "type-graphql": "^1.1.1",
"typescript": "^4.9.5" "typescript": "^4.9.5"
}, },

View File

@ -4,11 +4,11 @@ ROOT_DIR=$(dirname "$0")/..
exit_code=0 exit_code=0
for locale_file in $ROOT_DIR/src/locales/*.json for locale_file in $ROOT_DIR/"$1"/*.json
do do
jq -M 'to_entries | sort_by(.key) | from_entries' "$locale_file" > tmp.json jq -M 'to_entries | sort_by(.key) | from_entries' "$locale_file" > tmp.json
if [ "$*" == "--fix" ] if [ "$2" == "--fix" ]
then then
mv tmp.json "$locale_file" mv tmp.json "$locale_file"
else else

View File

@ -20,8 +20,34 @@ const federation = {
), ),
} }
const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost'
const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http'
const COMMUNITY_URL = process.env.COMMUNITY_URL ?? `${URL_PROTOCOL}://${COMMUNITY_HOST}`
const community = {
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL ?? 'support@supportmail.com',
COMMUNITY_URL,
}
const email = {
EMAIL: process.env.EMAIL === 'true',
EMAIL_LINK_FORGOTPASSWORD:
COMMUNITY_URL + (process.env.EMAIL_LINK_FORGOTPASSWORD_PATH ?? '/forgot-password'),
EMAIL_TLS: process.env.EMAIL_TLS !== 'false',
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true',
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD ?? '',
EMAIL_SMTP_HOST: process.env.EMAIL_SMTP_HOST ?? 'mailserver',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
}
export const CONFIG = { export const CONFIG = {
...federation, ...federation,
...community,
...email,
} }
validate(schema, CONFIG) validate(schema, CONFIG)

View File

@ -1,6 +1,93 @@
import Joi from 'joi' import Joi from 'joi'
import { COMMUNITY_SUPPORT_MAIL, COMMUNITY_URL, NODE_ENV } from 'config-schema'
export const schema = Joi.object({ export const schema = Joi.object({
COMMUNITY_SUPPORT_MAIL,
COMMUNITY_URL,
NODE_ENV,
EMAIL: Joi.boolean()
.default(false)
.description('Enable or disable email functionality')
.required(),
EMAIL_LINK_FORGOTPASSWORD: Joi.string()
.uri({ scheme: ['http', 'https'] })
.custom((value: string, helpers: Joi.CustomHelpers<string>): string | Joi.ErrorReport => {
if (!value.startsWith(helpers.state.ancestors[0].COMMUNITY_URL)) {
return helpers.error('string.pattern.base', { value, communityUrl: COMMUNITY_URL })
}
return value
})
.description('Email Verification link for set new Password, when old Password was forgotten.')
.required(),
EMAIL_TEST_MODUS: Joi.boolean()
.default(false)
.description('When enabled, all emails are sended to EMAIL_TEST_RECEIVER')
.optional(),
EMAIL_TEST_RECEIVER: Joi.string()
.email()
.default('stage1@gradido.net')
.when('EMAIL_TEST_MODUS', { is: true, then: Joi.required() })
.description('Email address used in test mode'),
EMAIL_USERNAME: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Username for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.pattern(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)
.description('Valid SMTP username required in production')
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SENDER: Joi.string()
.email()
.when('EMAIL', { is: true, then: Joi.required() })
.default('info@gradido.net')
.description('Email address used as sender'),
EMAIL_PASSWORD: Joi.alternatives().conditional(Joi.ref('EMAIL'), {
is: true,
then: Joi.alternatives().conditional(Joi.ref('NODE_ENV'), {
is: 'development',
then: Joi.string()
.allow('')
.description('Password for SMTP authentication (optional in development)'),
otherwise: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#]).{8,}$/)
.description(
'Password must be at least 8 characters long, include uppercase and lowercase letters, a number, and a special character',
)
.required(),
}),
otherwise: Joi.string().allow('').optional(),
}),
EMAIL_SMTP_HOST: Joi.string()
.hostname()
.when('EMAIL', { is: true, then: Joi.required() })
.default('mailserver')
.description('SMTP server hostname'),
EMAIL_SMTP_PORT: Joi.number()
.integer()
.positive()
.when('EMAIL', { is: true, then: Joi.required() })
.default(1025)
.description('SMTP server port'),
EMAIL_TLS: Joi.boolean().default(true).description('Enable or disable TLS for SMTP').optional(),
FEDERATION_BACKEND_SEND_ON_API: Joi.string() FEDERATION_BACKEND_SEND_ON_API: Joi.string()
.pattern(/^\d+_\d+$/) .pattern(/^\d+_\d+$/)
.default('1_0') .default('1_0')

View File

@ -2,12 +2,12 @@
exports[`sendEmailVariants sendAccountActivationEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendAccountActivationEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -133,41 +133,41 @@ exports[`sendEmailVariants sendAccountActivationEmail result has the correct htm
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Email Verification</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Email Verification</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Your email address has just been registered with Gradido.</p> <p>Your email address has just been registered with Gradido.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Complete registration</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Complete registration</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please click here to complete the registration and activate your Gradido account.</div><a class=\\"button-3\\" href=\\"http://localhost/checkEmail/6627633878930542284\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">Activate account</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please click here to complete the registration and activate your Gradido account.</div><a class=\"button-3\" href=\"http://localhost/checkEmail/6627633878930542284\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">Activate account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/checkEmail/6627633878930542284\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/checkEmail/6627633878930542284</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/checkEmail/6627633878930542284\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/checkEmail/6627633878930542284</a>
<requestnewlink> <requestnewlink>
<h2 style=\\"margin-top: 15px; color: #383838;\\">Request new valid link</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Request new valid link</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">The link has a validity of 23 hours and 30 minutes. <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\\"button-4\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\\">New link</a> If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\"button-4\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\">New link</a>
</requestnewlink> </requestnewlink>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -178,12 +178,12 @@ If the validity of the link has already expired, you can have a new link sent to
exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTranslated" result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTranslated" result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -309,39 +309,39 @@ exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTra
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Try To Register Again With Your Email</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Try To Register Again With Your Email</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Your email address has just been used again to register an account with Gradido.<br>However, an account already exists for your email address. <p>Your email address has just been used again to register an account with Gradido.<br>However, an account already exists for your email address.
</p> </p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If you have forgotten your password, please click here.</div><a class=\\"button-3\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">reset</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If you have forgotten your password, please click here.</div><a class=\"button-3\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">reset</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/forgot-password\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/forgot-password</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/forgot-password\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/forgot-password</a>
<h2 style=\\"margin-top: 15px; color: red;\\">Contact support</h2> <h2 style=\"margin-top: 15px; color: red;\">Contact support</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If you did not try to register again, please contact our support:</div><a class=\\"clink\\" href=\\"mailto:support@supportmail.com\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">support@supportmail.com</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If you did not try to register again, please contact our support:</div><a class=\"clink\" href=\"mailto:support@supportmail.com\" style=\"line-break: anywhere; margin-bottom: 40px;\">support@supportmail.com</a>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -352,12 +352,12 @@ exports[`sendEmailVariants sendAccountMultiRegistrationEmail calls "sendEmailTra
exports[`sendEmailVariants sendAddedContributionMessageEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendAddedContributionMessageEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -483,39 +483,39 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Message about your common good contribution</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Message about your common good contribution</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>You have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.</p> <p>You have received a message from Bibi Bloxberg regarding your common good contribution “My contribution.”.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Read and reply to message</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Read and reply to message</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\"> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">
<p>„My message.“</p> <p>„My message.“</p>
<p>To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</p> <p>To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</p>
</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> </div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -526,12 +526,12 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -657,37 +657,37 @@ exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has th
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution has been changed</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution has been changed</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>your common good contribution 'My contribution.' has just been changed by Bibi Bloxberg and now reads as 'This is a better contribution memo.'</p> <p>your common good contribution 'My contribution.' has just been changed by Bibi Bloxberg and now reads as 'This is a better contribution memo.'</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -698,12 +698,12 @@ exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has th
exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -829,37 +829,37 @@ exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your contribution to the common good was confirmed</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Your contribution to the common good was confirmed</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” has just been approved by Bibi Bloxberg. Your Gradido account has been credited with 23.54 GDD.</p> <p>Your common good contribution “My contribution.” has just been approved by Bibi Bloxberg. Your Gradido account has been credited with 23.54 GDD.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -870,12 +870,12 @@ exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct
exports[`sendEmailVariants sendContributionDeletedEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendContributionDeletedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -1001,37 +1001,37 @@ exports[`sendEmailVariants sendContributionDeletedEmail result has the correct h
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution was deleted</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution was deleted</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” was deleted by Bibi Bloxberg.</p> <p>Your common good contribution “My contribution.” was deleted by Bibi Bloxberg.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -1042,12 +1042,12 @@ exports[`sendEmailVariants sendContributionDeletedEmail result has the correct h
exports[`sendEmailVariants sendContributionDeniedEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendContributionDeniedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -1173,37 +1173,37 @@ exports[`sendEmailVariants sendContributionDeniedEmail result has the correct ht
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Your common good contribution was rejected</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Your common good contribution was rejected</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Your common good contribution “My contribution.” was rejected by Bibi Bloxberg.</p> <p>Your common good contribution “My contribution.” was rejected by Bibi Bloxberg.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\"button-3\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\" style=\"line-break: anywhere; margin-bottom: 40px;\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -1214,12 +1214,12 @@ exports[`sendEmailVariants sendContributionDeniedEmail result has the correct ht
exports[`sendEmailVariants sendResetPasswordEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendResetPasswordEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -1345,41 +1345,41 @@ exports[`sendEmailVariants sendResetPasswordEmail result has the correct html as
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>You, or someone else, requested a password reset for this account.</p> <p>You, or someone else, requested a password reset for this account.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Reset password</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Reset password</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">If it was you, please click here.</div><a class=\\"button-3\\" href=\\"http://localhost/reset-password/3762660021544901417\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">reset</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">If it was you, please click here.</div><a class=\"button-3\" href=\"http://localhost/reset-password/3762660021544901417\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">reset</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/reset-password/3762660021544901417\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/reset-password/3762660021544901417</a> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Or copy the link into your browser window.</div><a class=\"clink\" href=\"http://localhost/reset-password/3762660021544901417\" style=\"line-break: anywhere; margin-bottom: 40px;\">http://localhost/reset-password/3762660021544901417</a>
<requestnewlink> <requestnewlink>
<h2 style=\\"margin-top: 15px; color: #383838;\\">Request new valid link</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Request new valid link</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">The link has a validity of 23 hours and 30 minutes. <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">The link has a validity of 23 hours and 30 minutes.
If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\\"button-4\\" href=\\"http://localhost/forgot-password\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\\">New link</a> If the validity of the link has already expired, you can have a new link sent to you here.</div><a class=\"button-4\" href=\"http://localhost/forgot-password\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%; background-image: radial-gradient(circle farthest-corner at 0% 0%, #616161, #c2c2c2);\">New link</a>
</requestnewlink> </requestnewlink>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -1390,12 +1390,12 @@ If the validity of the link has already expired, you can have a new link sent to
exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -1521,37 +1521,37 @@ exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the corre
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Bibi Bloxberg has redeemed your Gradido link</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Bibi Bloxberg has redeemed your Gradido link</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p>Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.</p> <p>Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.</p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Transaction details</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Transaction details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Amount: 17.65 GDD<br>Message: You deserve it! 🙏🏼<br>You can find transaction details in your Gradido account. <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Amount: 17.65 GDD<br>Message: You deserve it! 🙏🏼<br>You can find transaction details in your Gradido account.
</div><a class=\\"button-3\\" href=\\"http://localhost/transactions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> </div><a class=\"button-3\" href=\"http://localhost/transactions\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Please do not reply to this email.</div>
</div> </div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>
@ -1562,12 +1562,12 @@ exports[`sendEmailVariants sendTransactionLinkRedeemedEmail result has the corre
exports[`sendEmailVariants sendTransactionReceivedEmail result has the correct html as snapshot 1`] = ` exports[`sendEmailVariants sendTransactionReceivedEmail result has the correct html as snapshot 1`] = `
"<!DOCTYPE html> "<!DOCTYPE html>
<html lang=\\"en\\"> <html lang=\"en\">
<head> <head>
<meta content=\\"multipart/html; charset=UTF-8\\" http-equiv=\\"content-type\\"> <meta content=\"multipart/html; charset=UTF-8\" http-equiv=\"content-type\">
<meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1\\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style> <style>
.wf-force-outline-none[tabindex=\\"-1\\"]:focus { .wf-force-outline-none[tabindex=\"-1\"]:focus {
outline: none; outline: none;
} }
</style> </style>
@ -1693,40 +1693,40 @@ exports[`sendEmailVariants sendTransactionReceivedEmail result has the correct h
} }
</style> </style>
</head> </head>
<body style=\\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\\"> <body style=\"display: block; font-family: 'Work Sans', sans-serif; font-size: 17px; text-align: center; text-align: -webkit-center; justify-content: center; padding: 0px; margin: 0px;\">
<div class=\\"container\\" style=\\"max-width: 680px; margin: 0 auto; display: block;\\"> <div class=\"container\" style=\"max-width: 680px; margin: 0 auto; display: block;\">
<header> <header>
<div class=\\"head\\"><img class=\\"head-logo\\" alt=\\"Gradido Logo\\" loading=\\"lazy\\" src=\\"cid:gradidoheader\\" style=\\"width: 100%; height: auto;\\"></div> <div class=\"head\"><img class=\"head-logo\" alt=\"Gradido Logo\" loading=\"lazy\" src=\"cid:gradidoheader\" style=\"width: 100%; height: auto;\"></div>
</header> </header>
<div class=\\"wrapper\\"> <div class=\"wrapper\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Bibi Bloxberg has sent you 37.40 Gradido</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Bibi Bloxberg has sent you 37.40 Gradido</h2>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Hello Peter Lustig,</p> <p>Hello Peter Lustig,</p>
<p> You have just received 37.40 GDD from Bibi Bloxberg (<a href=\\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\\">bibi@bloxberg.de</a>). <p> You have just received 37.40 GDD from Bibi Bloxberg (<a href=\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\">bibi@bloxberg.de</a>).
</p> </p>
</div> </div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\"> <div class=\"content\" style=\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Message</h2> <h2 style=\"margin-top: 15px; color: #383838;\">Message</h2>
<div class=\\"child-left\\" style=\\"text-align: left;\\"> <div class=\"child-left\" style=\"text-align: left;\">
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Du bist schon lustiger ;)</div> <div class=\"p_content\" style=\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\">Du bist schon lustiger ;)</div>
</div> </div>
<div class=\\"child-right\\" style=\\"text-align: right;\\"><a class=\\"button-5\\" href=\\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\\" style=\\"display: inline-block; padding: 9px 15px; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); margin: 25px 0 25px 0; background: linear-gradient(135deg, #53900c, #6e6e6e); font-size: 20px; font-weight: 600; color: #f5f5f5; width: auto; box-shadow: 20px 20px 25px; transition: all 0.3s ease;\\"><span class=\\"chatbox-wrapper\\" style=\\"margin-right: 8px;\\"><img class=\\"bi-chatbox\\" alt=\\"chatbox\\" loading=\\"lazy\\" src=\\"cid:chatboxicon\\" style=\\"margin-bottom: -5px;\\"></span><span>Reply</span></a> <div class=\"child-right\" style=\"text-align: right;\"><a class=\"button-5\" href=\"mailto:bibi@bloxberg.de?subject=RE%3A%20Bibi%20Bloxberg%20has%20sent%20you%2037.40%20Gradido\" style=\"display: inline-block; padding: 9px 15px; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); margin: 25px 0 25px 0; background: linear-gradient(135deg, #53900c, #6e6e6e); font-size: 20px; font-weight: 600; color: #f5f5f5; width: auto; box-shadow: 20px 20px 25px; transition: all 0.3s ease;\"><span class=\"chatbox-wrapper\" style=\"margin-right: 8px;\"><img class=\"bi-chatbox\" alt=\"chatbox\" loading=\"lazy\" src=\"cid:chatboxicon\" style=\"margin-bottom: -5px;\"></span><span>Reply</span></a>
</div> </div>
</div><a class=\\"button-3\\" href=\\"http://localhost/transactions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a> </div><a class=\"button-3\" href=\"http://localhost/transactions\" style=\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\">To account</a>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\"> <div class=\"text-block\" style=\"margin-top: 20px; color: #9ca0a8;\">
<p>Kind regards,<br>your Gradido team <p>Kind regards,<br>your Gradido team
</p> </p>
</div> </div>
</div> </div>
<footer> <footer>
<div class=\\"w-container footer_01\\"> <div class=\"w-container footer_01\">
<div class=\\"socialmedia\\" style=\\"display: flex; margin-top: 40px; max-width: 600px;\\"><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.facebook.com/groups/Gradido/\\" style=\\"width: 150px;\\"><img class=\\"bi-facebook\\" alt=\\"facebook\\" loading=\\"lazy\\" src=\\"cid:facebookicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://t.me/GradidoGruppe\\" style=\\"width: 150px;\\"><img class=\\"bi-telegram\\" alt=\\"Telegram\\" loading=\\"lazy\\" src=\\"cid:telegramicon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://twitter.com/gradido\\" style=\\"width: 150px;\\"><img class=\\"bi-twitter\\" alt=\\"Twitter\\" loading=\\"lazy\\" src=\\"cid:twittericon\\"></a><a class=\\"slink\\" target=\\"_blank\\" href=\\"https://www.youtube.com/c/GradidoNet\\" style=\\"width: 150px;\\"><img class=\\"bi-youtube\\" alt=\\"youtube\\" loading=\\"lazy\\" src=\\"cid:youtubeicon\\"></a></div> <div class=\"socialmedia\" style=\"display: flex; margin-top: 40px; max-width: 600px;\"><a class=\"slink\" target=\"_blank\" href=\"https://www.facebook.com/groups/Gradido/\" style=\"width: 150px;\"><img class=\"bi-facebook\" alt=\"facebook\" loading=\"lazy\" src=\"cid:facebookicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://t.me/GradidoGruppe\" style=\"width: 150px;\"><img class=\"bi-telegram\" alt=\"Telegram\" loading=\"lazy\" src=\"cid:telegramicon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://twitter.com/gradido\" style=\"width: 150px;\"><img class=\"bi-twitter\" alt=\"Twitter\" loading=\"lazy\" src=\"cid:twittericon\"></a><a class=\"slink\" target=\"_blank\" href=\"https://www.youtube.com/c/GradidoNet\" style=\"width: 150px;\"><img class=\"bi-youtube\" alt=\"youtube\" loading=\"lazy\" src=\"cid:youtubeicon\"></a></div>
<div class=\\"line\\" style=\\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\\"></div> <div class=\"line\" style=\"width: 100%; height: 13px; margin-top: 40px; background-image: linear-gradient(90deg, #c58d38, #f3cd7c 40%, #dbb056 55%, #eec05f 71%, #cc9d3d);\"></div>
<div class=\\"footer\\" style=\\"padding-bottom: 20px;\\"> <div class=\"footer\" style=\"padding-bottom: 20px;\">
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">If you have any further questions, please contact our support.</div><a class=\\"footer_p2\\" href=\\"mailto:support@gradido.net\\" style=\\"color: #383838; font-weight: bold;\\">support@gradido.net</a> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">If you have any further questions, please contact our support.</div><a class=\"footer_p2\" href=\"mailto:support@gradido.net\" style=\"color: #383838; font-weight: bold;\">support@gradido.net</a>
<div> <img class=\\"image\\" alt=\\"Gradido Logo\\" src=\\"https://gdd.gradido.net/img/brand/green.png\\" style=\\"width: 200px; margin-top: 30px; margin-bottom: 30px;\\" width=\\"200\\"></div> <div> <img class=\"image\" alt=\"Gradido Logo\" src=\"https://gdd.gradido.net/img/brand/green.png\" style=\"width: 200px; margin-top: 30px; margin-bottom: 30px;\" width=\"200\"></div>
<div><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/impressum/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Impressum</a></div><br><a class=\\"terms_of_use\\" href=\\"https://gradido.net/de/datenschutz/\\" target=\\"_blank\\" style=\\"color: #9ca0a8;\\">Privacy Policy</a> <div><a class=\"terms_of_use\" href=\"https://gradido.net/de/impressum/\" target=\"_blank\" style=\"color: #9ca0a8;\">Impressum</a></div><br><a class=\"terms_of_use\" href=\"https://gradido.net/de/datenschutz/\" target=\"_blank\" style=\"color: #9ca0a8;\">Privacy Policy</a>
<div class=\\"footer_p1\\" style=\\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div> <div class=\"footer_p1\" style=\"margin-top: 30px; color: #9ca0a8; margin-bottom: 30px;\">Gradido-Akademie<br>Institut für Wirtschaftsbionik<br>Pfarrweg 2<br>74653 Künzelsau<br>Deutschland<br><br><br></div>
</div> </div>
</div> </div>
</footer> </footer>

1
core/src/emails/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './sendEmailVariants'

View File

@ -0,0 +1,7 @@
import { i18n } from './localization'
describe('localization', () => {
it('translate emails.accountMultiRegistration.contactSupport with Contact support', () => {
expect(i18n.__('emails.accountMultiRegistration.contactSupport')).toBe('Contact support')
})
})

View File

@ -0,0 +1,31 @@
import en from './locales/en.json'
import de from './locales/de.json'
import { I18n } from 'i18n'
function flatten(obj: any, prefix: string = ''): any {
const result: any = {}
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
Object.assign(result, flatten(obj[key], prefix + key + '.'))
} else {
result[prefix + key] = obj[key]
}
}
return result
}
export const i18n = new I18n({
locales: ['en', 'de'],
defaultLocale: 'en',
staticCatalog: { en: flatten(en), de: flatten(de) },
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn', // and req.__n can be called as req.tn
},
register: global,
mustacheConfig: {
tags: ['{', '}'],
disable: false,
},
})

View File

@ -1,12 +1,10 @@
import { createTransport } from 'nodemailer' import { createTransport } from 'nodemailer'
import { CONFIG } from '../config'
import { i18n } from '@test/testSetup' import { i18n } from './localization'
import { getLogger } from '../../../config-schema/test/testSetup.bun'
import { CONFIG } from '@/config' import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendEmailTranslated } from './sendEmailTranslated' import { sendEmailTranslated } from './sendEmailTranslated'
import { mock, jest, describe, it, expect, beforeEach, afterAll } from 'bun:test'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`)
@ -21,9 +19,8 @@ CONFIG.EMAIL_USERNAME = 'user'
CONFIG.EMAIL_PASSWORD = 'pwd' CONFIG.EMAIL_PASSWORD = 'pwd'
CONFIG.EMAIL_TLS = true CONFIG.EMAIL_TLS = true
jest.mock('nodemailer', () => { mock.module('nodemailer', () => {
return { return {
__esModule: true,
createTransport: jest.fn(() => { createTransport: jest.fn(() => {
return { return {
sendMail: () => { sendMail: () => {
@ -36,6 +33,13 @@ jest.mock('nodemailer', () => {
} }
}) })
afterAll(() => {
jest.restoreAllMocks()
})
const spySetLocale = jest.spyOn(i18n, 'setLocale')
const spyTranslate = jest.spyOn(i18n, '__')
describe('sendEmailTranslated', () => { describe('sendEmailTranslated', () => {
let result: Record<string, unknown> | boolean | null let result: Record<string, unknown> | boolean | null
@ -48,7 +52,7 @@ describe('sendEmailTranslated', () => {
}, },
template: 'accountMultiRegistration', template: 'accountMultiRegistration',
locals: { locals: {
locale: 'en', language: 'en',
}, },
}) })
}) })
@ -72,7 +76,7 @@ describe('sendEmailTranslated', () => {
}, },
template: 'accountMultiRegistration', template: 'accountMultiRegistration',
locals: { locals: {
locale: 'en', language: 'en',
}, },
}) })
}) })
@ -105,13 +109,13 @@ describe('sendEmailTranslated', () => {
}) })
}) })
}) })
it.skip('calls "i18n.setLocale" with "en"', () => { it('calls "i18n.setLocale" with "en"', async () => {
expect(i18n.setLocale).toBeCalledWith('en') expect(spySetLocale).toBeCalledWith('en')
}) })
it.skip('calls "i18n.__" for translation', () => { it('calls "i18n.__" for translation', () => {
expect(i18n.__).toBeCalled() expect(spyTranslate).toBeCalled()
}) })
}) })
@ -127,7 +131,7 @@ describe('sendEmailTranslated', () => {
}, },
template: 'accountMultiRegistration', template: 'accountMultiRegistration',
locals: { locals: {
locale: 'en', language: 'en',
}, },
}) })
}) })

View File

@ -1,12 +1,16 @@
import path from 'path' import path from 'path'
import Email from 'email-templates' import Email from 'email-templates'
import i18n from 'i18n' import { i18n } from './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'
import { getLogger } from 'log4js' import { getLogger } from 'log4js'
import gradidoHeader from './templates/includes/gradido-header.jpeg'
import facebookIcon from './templates/includes/facebook-icon.png'
import telegramIcon from './templates/includes/telegram-icon.png'
import twitterIcon from './templates/includes/twitter-icon.png'
import youtubeIcon from './templates/includes/youtube-icon.png'
import chatboxIcon from './templates/includes/chatbox-icon.png'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`) const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.emails.sendEmailTranslated`)
@ -21,7 +25,7 @@ export const sendEmailTranslated = async ({
} }
template: string template: string
locals: Record<string, unknown> locals: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null> => { }): Promise<Record<string, unknown> | boolean | null | Error> => {
// TODO: test the calling order of 'i18n.setLocale' for example: language of logging 'en', language of email receiver 'es', reset language of current user 'de' // TODO: test the calling order of 'i18n.setLocale' for example: language of logging 'en', language of email receiver 'es', reset language of current user 'de'
if (!CONFIG.EMAIL) { if (!CONFIG.EMAIL) {
@ -31,7 +35,6 @@ export const sendEmailTranslated = async ({
// because language of receiver can differ from language of current user who triggers the sending // because language of receiver can differ from language of current user who triggers the sending
// const rememberLocaleToRestore = i18n.getLocale() // const rememberLocaleToRestore = i18n.getLocale()
i18n.setLocale('en') // for logging i18n.setLocale('en') // for logging
logger.info( logger.info(
`send Email: language=${locals.locale as string} to=${receiver.to.substring(0, 3)}...` + `send Email: language=${locals.locale as string} to=${receiver.to.substring(0, 3)}...` +
@ -45,6 +48,7 @@ export const sendEmailTranslated = async ({
) )
receiver.to = CONFIG.EMAIL_TEST_RECEIVER receiver.to = CONFIG.EMAIL_TEST_RECEIVER
} }
const transport = createTransport({ const transport = createTransport({
host: CONFIG.EMAIL_SMTP_HOST, host: CONFIG.EMAIL_SMTP_HOST,
port: CONFIG.EMAIL_SMTP_PORT, port: CONFIG.EMAIL_SMTP_PORT,
@ -56,8 +60,7 @@ export const sendEmailTranslated = async ({
}, },
}) })
i18n.setLocale(locals.locale as string) // for email i18n.setLocale(locals.language as string) // for email
// TESTING: see 'README.md' // TESTING: see 'README.md'
const email = new Email({ const email = new Email({
message: { message: {
@ -66,9 +69,7 @@ export const sendEmailTranslated = async ({
send: CONFIG.EMAIL, send: CONFIG.EMAIL,
transport, transport,
preview: false, preview: false,
// i18n, // is only needed if you don't install i18n
}) })
const resultSend = await email const resultSend = await email
.send({ .send({
template: path.join(__dirname, 'templates', template), template: path.join(__dirname, 'templates', template),
@ -76,38 +77,39 @@ export const sendEmailTranslated = async ({
...receiver, ...receiver,
attachments: [ attachments: [
{ {
filename: 'gradido-header.jpeg', // filename: 'gradido-header.jpeg',
path: path.join(__dirname, 'templates/includes/gradido-header.jpeg'), content: gradidoHeader,
cid: 'gradidoheader', cid: 'gradidoheader',
}, },
{ {
filename: 'facebook-icon.png', // filename: 'facebook-icon.png',
path: path.join(__dirname, 'templates/includes/facebook-icon.png'), content: facebookIcon,
cid: 'facebookicon', cid: 'facebookicon',
}, },
{ {
filename: 'telegram-icon.png', // filename: 'telegram-icon.png',
path: path.join(__dirname, 'templates/includes/telegram-icon.png'), content: telegramIcon,
cid: 'telegramicon', cid: 'telegramicon',
}, },
{ {
filename: 'twitter-icon.png', // filename: 'twitter-icon.png',
path: path.join(__dirname, 'templates/includes/twitter-icon.png'), content: twitterIcon,
cid: 'twittericon', cid: 'twittericon',
}, },
{ {
filename: 'youtube-icon.png', // filename: 'youtube-icon.png',
path: path.join(__dirname, 'templates/includes/youtube-icon.png'), content: youtubeIcon,
cid: 'youtubeicon', cid: 'youtubeicon',
}, },
{ {
filename: 'chatbox-icon.png', // filename: 'chatbox-icon.png',
path: path.join(__dirname, 'templates/includes/chatbox-icon.png'), content: chatboxIcon,
cid: 'chatboxicon', cid: 'chatboxicon',
}, },
], ],
}, },
locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale' locals, // the 'locale' in here seems not to be used by 'email-template', because it doesn't work if the language isn't set before by 'i18n.setLocale'
// t: i18n.__.bind(i18n),
}) })
.catch((error: unknown) => { .catch((error: unknown) => {
logger.error('Error sending notification email', error) logger.error('Error sending notification email', error)

View File

@ -1,12 +1,5 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import { DataSource } from 'typeorm' import { CONFIG } from '../config'
import { testEnvironment } from '@test/helpers'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { CONFIG } from '@/config'
import * as sendEmailTranslatedApi from './sendEmailTranslated' import * as sendEmailTranslatedApi from './sendEmailTranslated'
import { import {
@ -26,6 +19,7 @@ const testMailServerHost = 'localhost'
const testMailServerPort = 1025 const testMailServerPort = 1025
const testMailTLS = false const testMailTLS = false
CONFIG.EMAIL = true
CONFIG.EMAIL_SENDER = 'info@gradido.net' CONFIG.EMAIL_SENDER = 'info@gradido.net'
CONFIG.EMAIL_SMTP_HOST = testMailServerHost CONFIG.EMAIL_SMTP_HOST = testMailServerHost
CONFIG.EMAIL_SMTP_PORT = testMailServerPort CONFIG.EMAIL_SMTP_PORT = testMailServerPort
@ -46,22 +40,6 @@ jest.mock('nodemailer', () => {
} }
}) })
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: DataSource
}
beforeAll(async () => {
testEnv = await testEnvironment(getLogger('apollo'), localization)
con = testEnv.con
})
afterAll(async () => {
await con.destroy()
})
const sendEmailTranslatedSpy = jest.spyOn(sendEmailTranslatedApi, 'sendEmailTranslated') const sendEmailTranslatedSpy = jest.spyOn(sendEmailTranslatedApi, 'sendEmailTranslated')
describe('sendEmailVariants', () => { describe('sendEmailVariants', () => {
@ -81,7 +59,7 @@ describe('sendEmailVariants', () => {
contributionMemo: 'My contribution.', contributionMemo: 'My contribution.',
contributionFrontendLink, contributionFrontendLink,
message: 'My message.', message: 'My message.',
}) })
}) })
describe('calls "sendEmailTranslated"', () => { describe('calls "sendEmailTranslated"', () => {
@ -91,24 +69,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'addedContributionMessage', template: 'addedContributionMessage',
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,
message: 'My message.', message: 'My message.',
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>',
@ -145,23 +125,26 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig <peter@lustig.de>', to: 'Peter Lustig <peter@lustig.de>',
}, },
template: 'accountActivation', template: 'accountActivation',
locals: { locals: expect.objectContaining({
firstName: 'Peter', firstName: 'Peter',
lastName: 'Lustig', lastName: 'Lustig',
locale: 'en', language: 'en',
activationLink: 'http://localhost/checkEmail/6627633878930542284', activationLink: 'http://localhost/checkEmail/6627633878930542284',
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>',
@ -179,6 +162,8 @@ describe('sendEmailVariants', () => {
}) })
}) })
/*
describe('sendAccountMultiRegistrationEmail', () => { describe('sendAccountMultiRegistrationEmail', () => {
beforeAll(async () => { beforeAll(async () => {
result = await sendAccountMultiRegistrationEmail({ result = await sendAccountMultiRegistrationEmail({
@ -620,4 +605,5 @@ describe('sendEmailVariants', () => {
}) })
}) })
}) })
*/
}) })

View File

@ -0,0 +1,179 @@
import { Decimal } from 'decimal.js-light'
import { CONFIG } from '../config'
import { decimalSeparatorByLanguage } from '../util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'
export interface EmailCommonData {
firstName: string
lastName: string
email: string
language: string
}
export interface ContributionEmailCommonData {
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionFrontendLink: string
}
function getEmailCommonLocales(): Record<string, unknown> {
return {
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
communityURL: CONFIG.COMMUNITY_URL,
}
}
export const sendAddedContributionMessageEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
message: string
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
},
template: 'addedContributionMessage',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendAccountActivationEmail = (data: EmailCommonData & {
activationLink: string
timeDurationObject: Record<string, unknown>
logoUrl?: string | null
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountActivation',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendAccountMultiRegistrationEmail = (data: EmailCommonData): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'accountMultiRegistration',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendContributionConfirmedEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
contributionAmount: Decimal
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
locals: {
...data,
...getEmailCommonLocales(),
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
},
})
}
export const sendContributionChangedByModeratorEmail = (
data: EmailCommonData & ContributionEmailCommonData & {
contributionMemoUpdated: string
},
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionChangedByModerator',
locals: {
...data,
...getEmailCommonLocales(),
contributionMemoUpdated: data.contributionMemoUpdated,
},
})
}
export const sendContributionDeletedEmail = (
data: EmailCommonData & ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendContributionDeniedEmail = (
data: EmailCommonData & ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendResetPasswordEmail = (data: EmailCommonData & {
resetLink: string
timeDurationObject: Record<string, unknown>
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'resetPassword',
locals: {
...data,
...getEmailCommonLocales(),
},
})
}
export const sendTransactionLinkRedeemedEmail = (data: EmailCommonData & {
senderFirstName: string
senderLastName: string
senderEmail: string
transactionMemo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionLinkRedeemed',
locals: {
...data,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
...getEmailCommonLocales(),
},
})
}
export const sendTransactionReceivedEmail = (data: EmailCommonData & {
senderFirstName: string
senderLastName: string
senderEmail: string
memo: string
transactionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null | Error> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'transactionReceived',
locals: {
...data,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
...getEmailCommonLocales(),
},
})
}

View File

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 323 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More