diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c3238507a..c136ca4b1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -139,7 +139,6 @@ jobs:
build_test_nginx:
name: Docker Build Test - Nginx
runs-on: ubuntu-latest
- needs: [build_test_backend, build_test_admin, build_test_frontend]
steps:
##########################################################################
# CHECKOUT CODE ##########################################################
@@ -528,7 +527,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
- min_coverage: 74
+ min_coverage: 76
token: ${{ github.token }}
##########################################################################
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26b71ea03..19957a309 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,30 @@ 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).
+#### [1.16.0](https://github.com/gradido/gradido/compare/1.15.0...1.16.0)
+
+- refactor(backend): cleaning user related old password junk [`#2426`](https://github.com/gradido/gradido/pull/2426)
+- fix(database): consistent transaction table [`#2453`](https://github.com/gradido/gradido/pull/2453)
+- refactor(backend): dissolve admin resolver [`#2416`](https://github.com/gradido/gradido/pull/2416)
+- fix(backend): email verification code never expired [`#2418`](https://github.com/gradido/gradido/pull/2418)
+- fix(database): consistent deleted at bewteen users and user contacts [`#2451`](https://github.com/gradido/gradido/pull/2451)
+- feat(backend): log client timezone offset [`#2454`](https://github.com/gradido/gradido/pull/2454)
+- refactor(backend): refactor more emails to translatables [`#2398`](https://github.com/gradido/gradido/pull/2398)
+- fix(backend): delete / undelete email contact as well [`#2444`](https://github.com/gradido/gradido/pull/2444)
+- feat(backend): 🍰 Mark creation via link [`#2363`](https://github.com/gradido/gradido/pull/2363)
+- fix(backend): run all timers for high values [`#2452`](https://github.com/gradido/gradido/pull/2452)
+- fix(backend): critical bug [`#2443`](https://github.com/gradido/gradido/pull/2443)
+- fix(other): missing files for docker production build [`#2442`](https://github.com/gradido/gradido/pull/2442)
+- fix(frontend): in contribution messages formular a message can be send twice, when clicking the submit button fast [`#2424`](https://github.com/gradido/gradido/pull/2424)
+- fix(backend): wrong month for contribution near turn of month [`#2201`](https://github.com/gradido/gradido/pull/2201)
+- feat(backend): add federation config properties [`#2374`](https://github.com/gradido/gradido/pull/2374)
+- fix(backend): moved all jest & type-definition related packages into the `devDependencies` section [`#2385`](https://github.com/gradido/gradido/pull/2385)
+
#### [1.15.0](https://github.com/gradido/gradido/compare/1.14.1...1.15.0)
+> 26 November 2022
+
+- chore(release): v1.15.0 [`#2425`](https://github.com/gradido/gradido/pull/2425)
- fix(database): wrong balance and decay values [`#2423`](https://github.com/gradido/gradido/pull/2423)
- fix(backend): wrong balance after transaction receive [`#2422`](https://github.com/gradido/gradido/pull/2422)
- feat(other): feature gradido roadmap [`#2301`](https://github.com/gradido/gradido/pull/2301)
diff --git a/admin/package.json b/admin/package.json
index 75800a526..58eb48d09 100644
--- a/admin/package.json
+++ b/admin/package.json
@@ -3,7 +3,7 @@
"description": "Administraion Interface for Gradido",
"main": "index.js",
"author": "Moriz Wahl",
- "version": "1.15.0",
+ "version": "1.16.0",
"license": "Apache-2.0",
"private": false,
"scripts": {
diff --git a/admin/src/components/DeletedUserFormular.vue b/admin/src/components/DeletedUserFormular.vue
index 43a353ef4..576ac9f93 100644
--- a/admin/src/components/DeletedUserFormular.vue
+++ b/admin/src/components/DeletedUserFormular.vue
@@ -65,7 +65,6 @@ export default {
},
})
.then((result) => {
- this.toastSuccess(this.$t('user_recovered'))
this.$emit('updateDeletedAt', {
userId: this.item.userId,
deletedAt: result.data.unDeleteUser,
diff --git a/backend/.env.dist b/backend/.env.dist
index f30c4cc2e..b238388f6 100644
--- a/backend/.env.dist
+++ b/backend/.env.dist
@@ -1,4 +1,4 @@
-CONFIG_VERSION=v13.2022-11-25
+CONFIG_VERSION=v14.2022-12-22
# Server
PORT=4000
@@ -30,6 +30,7 @@ COMMUNITY_REGISTER_URL=http://localhost/register
COMMUNITY_REDEEM_URL=http://localhost/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_URL=http://localhost/redeem/CL-{code}
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
+COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# Login Server
LOGIN_APP_SECRET=21ffbbc616fe
@@ -66,5 +67,4 @@ EVENT_PROTOCOL_DISABLED=false
# on an hash created from this topic
# FEDERATION_DHT_TOPIC=GRADIDO_HUB
# FEDERATION_DHT_SEED=64ebcb0e3ad547848fef4197c6e2332f
-# FEDERATION_DHT_TEST_SOCKET=false
# FEDERATION_COMMUNITY_URL=http://localhost:4000/api
diff --git a/backend/.env.template b/backend/.env.template
index 763527412..ab5072fd8 100644
--- a/backend/.env.template
+++ b/backend/.env.template
@@ -29,6 +29,7 @@ COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
COMMUNITY_REDEEM_URL=$COMMUNITY_REDEEM_URL
COMMUNITY_REDEEM_CONTRIBUTION_URL=$COMMUNITY_REDEEM_CONTRIBUTION_URL
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
+COMMUNITY_SUPPORT_MAIL=$COMMUNITY_SUPPORT_MAIL
# Login Server
LOGIN_APP_SECRET=21ffbbc616fe
diff --git a/backend/Dockerfile b/backend/Dockerfile
index c09e5aaf8..910bdd504 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -107,9 +107,7 @@ COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json
COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json
# Copy log4js-config.json to provide log configuration
COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json
-# Copy memonic type since its referenced in the sources
-# TODO: remove
-COPY --from=build ${DOCKER_WORKDIR}/src/config/mnemonic.uncompressed_buffer13116.txt ./src/config/mnemonic.uncompressed_buffer13116.txt
+
# Copy run scripts run/
# COPY --from=build ${DOCKER_WORKDIR}/run ./run
diff --git a/backend/package.json b/backend/package.json
index c6b3dabc2..69a436563 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "gradido-backend",
- "version": "1.15.0",
+ "version": "1.16.0",
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/backend",
@@ -20,6 +20,7 @@
"dependencies": {
"@hyperswarm/dht": "^6.2.0",
"apollo-server-express": "^2.25.2",
+ "await-semaphore": "^0.1.3",
"axios": "^0.21.1",
"class-validator": "^0.13.1",
"cors": "^2.8.5",
diff --git a/backend/src/auth/CustomJwtPayload.ts b/backend/src/auth/CustomJwtPayload.ts
index 2b52c3cea..7966b413e 100644
--- a/backend/src/auth/CustomJwtPayload.ts
+++ b/backend/src/auth/CustomJwtPayload.ts
@@ -1,5 +1,5 @@
import { JwtPayload } from 'jsonwebtoken'
export interface CustomJwtPayload extends JwtPayload {
- pubKey: Buffer
+ gradidoID: string
}
diff --git a/backend/src/auth/JWT.ts b/backend/src/auth/JWT.ts
index e32e68223..8399c881b 100644
--- a/backend/src/auth/JWT.ts
+++ b/backend/src/auth/JWT.ts
@@ -11,8 +11,8 @@ export const decode = (token: string): CustomJwtPayload | null => {
}
}
-export const encode = (pubKey: Buffer): string => {
- const token = jwt.sign({ pubKey }, CONFIG.JWT_SECRET, {
+export const encode = (gradidoID: string): string => {
+ const token = jwt.sign({ gradidoID }, CONFIG.JWT_SECRET, {
expiresIn: CONFIG.JWT_EXPIRES_IN,
})
return token
diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts
index 3806f01f9..698b17e67 100644
--- a/backend/src/config/index.ts
+++ b/backend/src/config/index.ts
@@ -10,14 +10,14 @@ Decimal.set({
})
const constants = {
- DB_VERSION: '0056-add_communities_table',
+ DB_VERSION: '0058-add_communities_table',
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info',
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
- EXPECTED: 'v13.2022-11-25',
+ EXPECTED: 'v14.2022-11-22',
CURRENT: '',
},
}
@@ -58,6 +58,7 @@ const community = {
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}',
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
+ COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL || 'support@supportmail.com',
}
const loginServer = {
@@ -119,7 +120,6 @@ if (
const federation = {
FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null,
FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null,
- FEDERATION_DHT_TEST_SOCKET: process.env.FEDERATION_DHT_TEST_SOCKET === 'true' || false,
FEDERATION_COMMUNITY_URL:
process.env.FEDERATION_COMMUNITY_URL === undefined
? null
diff --git a/backend/src/config/mnemonic.english.txt b/backend/src/config/mnemonic.english.txt
deleted file mode 100644
index 942040ed5..000000000
--- a/backend/src/config/mnemonic.english.txt
+++ /dev/null
@@ -1,2048 +0,0 @@
-abandon
-ability
-able
-about
-above
-absent
-absorb
-abstract
-absurd
-abuse
-access
-accident
-account
-accuse
-achieve
-acid
-acoustic
-acquire
-across
-act
-action
-actor
-actress
-actual
-adapt
-add
-addict
-address
-adjust
-admit
-adult
-advance
-advice
-aerobic
-affair
-afford
-afraid
-again
-age
-agent
-agree
-ahead
-aim
-air
-airport
-aisle
-alarm
-album
-alcohol
-alert
-alien
-all
-alley
-allow
-almost
-alone
-alpha
-already
-also
-alter
-always
-amateur
-amazing
-among
-amount
-amused
-analyst
-anchor
-ancient
-anger
-angle
-angry
-animal
-ankle
-announce
-annual
-another
-answer
-antenna
-antique
-anxiety
-any
-apart
-apology
-appear
-apple
-approve
-april
-arch
-arctic
-area
-arena
-argue
-arm
-armed
-armor
-army
-around
-arrange
-arrest
-arrive
-arrow
-art
-artefact
-artist
-artwork
-ask
-aspect
-assault
-asset
-assist
-assume
-asthma
-athlete
-atom
-attack
-attend
-attitude
-attract
-auction
-audit
-august
-aunt
-author
-auto
-autumn
-average
-avocado
-avoid
-awake
-aware
-away
-awesome
-awful
-awkward
-axis
-baby
-bachelor
-bacon
-badge
-bag
-balance
-balcony
-ball
-bamboo
-banana
-banner
-bar
-barely
-bargain
-barrel
-base
-basic
-basket
-battle
-beach
-bean
-beauty
-because
-become
-beef
-before
-begin
-behave
-behind
-believe
-below
-belt
-bench
-benefit
-best
-betray
-better
-between
-beyond
-bicycle
-bid
-bike
-bind
-biology
-bird
-birth
-bitter
-black
-blade
-blame
-blanket
-blast
-bleak
-bless
-blind
-blood
-blossom
-blouse
-blue
-blur
-blush
-board
-boat
-body
-boil
-bomb
-bone
-bonus
-book
-boost
-border
-boring
-borrow
-boss
-bottom
-bounce
-box
-boy
-bracket
-brain
-brand
-brass
-brave
-bread
-breeze
-brick
-bridge
-brief
-bright
-bring
-brisk
-broccoli
-broken
-bronze
-broom
-brother
-brown
-brush
-bubble
-buddy
-budget
-buffalo
-build
-bulb
-bulk
-bullet
-bundle
-bunker
-burden
-burger
-burst
-bus
-business
-busy
-butter
-buyer
-buzz
-cabbage
-cabin
-cable
-cactus
-cage
-cake
-call
-calm
-camera
-camp
-can
-canal
-cancel
-candy
-cannon
-canoe
-canvas
-canyon
-capable
-capital
-captain
-car
-carbon
-card
-cargo
-carpet
-carry
-cart
-case
-cash
-casino
-castle
-casual
-cat
-catalog
-catch
-category
-cattle
-caught
-cause
-caution
-cave
-ceiling
-celery
-cement
-census
-century
-cereal
-certain
-chair
-chalk
-champion
-change
-chaos
-chapter
-charge
-chase
-chat
-cheap
-check
-cheese
-chef
-cherry
-chest
-chicken
-chief
-child
-chimney
-choice
-choose
-chronic
-chuckle
-chunk
-churn
-cigar
-cinnamon
-circle
-citizen
-city
-civil
-claim
-clap
-clarify
-claw
-clay
-clean
-clerk
-clever
-click
-client
-cliff
-climb
-clinic
-clip
-clock
-clog
-close
-cloth
-cloud
-clown
-club
-clump
-cluster
-clutch
-coach
-coast
-coconut
-code
-coffee
-coil
-coin
-collect
-color
-column
-combine
-come
-comfort
-comic
-common
-company
-concert
-conduct
-confirm
-congress
-connect
-consider
-control
-convince
-cook
-cool
-copper
-copy
-coral
-core
-corn
-correct
-cost
-cotton
-couch
-country
-couple
-course
-cousin
-cover
-coyote
-crack
-cradle
-craft
-cram
-crane
-crash
-crater
-crawl
-crazy
-cream
-credit
-creek
-crew
-cricket
-crime
-crisp
-critic
-crop
-cross
-crouch
-crowd
-crucial
-cruel
-cruise
-crumble
-crunch
-crush
-cry
-crystal
-cube
-culture
-cup
-cupboard
-curious
-current
-curtain
-curve
-cushion
-custom
-cute
-cycle
-dad
-damage
-damp
-dance
-danger
-daring
-dash
-daughter
-dawn
-day
-deal
-debate
-debris
-decade
-december
-decide
-decline
-decorate
-decrease
-deer
-defense
-define
-defy
-degree
-delay
-deliver
-demand
-demise
-denial
-dentist
-deny
-depart
-depend
-deposit
-depth
-deputy
-derive
-describe
-desert
-design
-desk
-despair
-destroy
-detail
-detect
-develop
-device
-devote
-diagram
-dial
-diamond
-diary
-dice
-diesel
-diet
-differ
-digital
-dignity
-dilemma
-dinner
-dinosaur
-direct
-dirt
-disagree
-discover
-disease
-dish
-dismiss
-disorder
-display
-distance
-divert
-divide
-divorce
-dizzy
-doctor
-document
-dog
-doll
-dolphin
-domain
-donate
-donkey
-donor
-door
-dose
-double
-dove
-draft
-dragon
-drama
-drastic
-draw
-dream
-dress
-drift
-drill
-drink
-drip
-drive
-drop
-drum
-dry
-duck
-dumb
-dune
-during
-dust
-dutch
-duty
-dwarf
-dynamic
-eager
-eagle
-early
-earn
-earth
-easily
-east
-easy
-echo
-ecology
-economy
-edge
-edit
-educate
-effort
-egg
-eight
-either
-elbow
-elder
-electric
-elegant
-element
-elephant
-elevator
-elite
-else
-embark
-embody
-embrace
-emerge
-emotion
-employ
-empower
-empty
-enable
-enact
-end
-endless
-endorse
-enemy
-energy
-enforce
-engage
-engine
-enhance
-enjoy
-enlist
-enough
-enrich
-enroll
-ensure
-enter
-entire
-entry
-envelope
-episode
-equal
-equip
-era
-erase
-erode
-erosion
-error
-erupt
-escape
-essay
-essence
-estate
-eternal
-ethics
-evidence
-evil
-evoke
-evolve
-exact
-example
-excess
-exchange
-excite
-exclude
-excuse
-execute
-exercise
-exhaust
-exhibit
-exile
-exist
-exit
-exotic
-expand
-expect
-expire
-explain
-expose
-express
-extend
-extra
-eye
-eyebrow
-fabric
-face
-faculty
-fade
-faint
-faith
-fall
-false
-fame
-family
-famous
-fan
-fancy
-fantasy
-farm
-fashion
-fat
-fatal
-father
-fatigue
-fault
-favorite
-feature
-february
-federal
-fee
-feed
-feel
-female
-fence
-festival
-fetch
-fever
-few
-fiber
-fiction
-field
-figure
-file
-film
-filter
-final
-find
-fine
-finger
-finish
-fire
-firm
-first
-fiscal
-fish
-fit
-fitness
-fix
-flag
-flame
-flash
-flat
-flavor
-flee
-flight
-flip
-float
-flock
-floor
-flower
-fluid
-flush
-fly
-foam
-focus
-fog
-foil
-fold
-follow
-food
-foot
-force
-forest
-forget
-fork
-fortune
-forum
-forward
-fossil
-foster
-found
-fox
-fragile
-frame
-frequent
-fresh
-friend
-fringe
-frog
-front
-frost
-frown
-frozen
-fruit
-fuel
-fun
-funny
-furnace
-fury
-future
-gadget
-gain
-galaxy
-gallery
-game
-gap
-garage
-garbage
-garden
-garlic
-garment
-gas
-gasp
-gate
-gather
-gauge
-gaze
-general
-genius
-genre
-gentle
-genuine
-gesture
-ghost
-giant
-gift
-giggle
-ginger
-giraffe
-girl
-give
-glad
-glance
-glare
-glass
-glide
-glimpse
-globe
-gloom
-glory
-glove
-glow
-glue
-goat
-goddess
-gold
-good
-goose
-gorilla
-gospel
-gossip
-govern
-gown
-grab
-grace
-grain
-grant
-grape
-grass
-gravity
-great
-green
-grid
-grief
-grit
-grocery
-group
-grow
-grunt
-guard
-guess
-guide
-guilt
-guitar
-gun
-gym
-habit
-hair
-half
-hammer
-hamster
-hand
-happy
-harbor
-hard
-harsh
-harvest
-hat
-have
-hawk
-hazard
-head
-health
-heart
-heavy
-hedgehog
-height
-hello
-helmet
-help
-hen
-hero
-hidden
-high
-hill
-hint
-hip
-hire
-history
-hobby
-hockey
-hold
-hole
-holiday
-hollow
-home
-honey
-hood
-hope
-horn
-horror
-horse
-hospital
-host
-hotel
-hour
-hover
-hub
-huge
-human
-humble
-humor
-hundred
-hungry
-hunt
-hurdle
-hurry
-hurt
-husband
-hybrid
-ice
-icon
-idea
-identify
-idle
-ignore
-ill
-illegal
-illness
-image
-imitate
-immense
-immune
-impact
-impose
-improve
-impulse
-inch
-include
-income
-increase
-index
-indicate
-indoor
-industry
-infant
-inflict
-inform
-inhale
-inherit
-initial
-inject
-injury
-inmate
-inner
-innocent
-input
-inquiry
-insane
-insect
-inside
-inspire
-install
-intact
-interest
-into
-invest
-invite
-involve
-iron
-island
-isolate
-issue
-item
-ivory
-jacket
-jaguar
-jar
-jazz
-jealous
-jeans
-jelly
-jewel
-job
-join
-joke
-journey
-joy
-judge
-juice
-jump
-jungle
-junior
-junk
-just
-kangaroo
-keen
-keep
-ketchup
-key
-kick
-kid
-kidney
-kind
-kingdom
-kiss
-kit
-kitchen
-kite
-kitten
-kiwi
-knee
-knife
-knock
-know
-lab
-label
-labor
-ladder
-lady
-lake
-lamp
-language
-laptop
-large
-later
-latin
-laugh
-laundry
-lava
-law
-lawn
-lawsuit
-layer
-lazy
-leader
-leaf
-learn
-leave
-lecture
-left
-leg
-legal
-legend
-leisure
-lemon
-lend
-length
-lens
-leopard
-lesson
-letter
-level
-liar
-liberty
-library
-license
-life
-lift
-light
-like
-limb
-limit
-link
-lion
-liquid
-list
-little
-live
-lizard
-load
-loan
-lobster
-local
-lock
-logic
-lonely
-long
-loop
-lottery
-loud
-lounge
-love
-loyal
-lucky
-luggage
-lumber
-lunar
-lunch
-luxury
-lyrics
-machine
-mad
-magic
-magnet
-maid
-mail
-main
-major
-make
-mammal
-man
-manage
-mandate
-mango
-mansion
-manual
-maple
-marble
-march
-margin
-marine
-market
-marriage
-mask
-mass
-master
-match
-material
-math
-matrix
-matter
-maximum
-maze
-meadow
-mean
-measure
-meat
-mechanic
-medal
-media
-melody
-melt
-member
-memory
-mention
-menu
-mercy
-merge
-merit
-merry
-mesh
-message
-metal
-method
-middle
-midnight
-milk
-million
-mimic
-mind
-minimum
-minor
-minute
-miracle
-mirror
-misery
-miss
-mistake
-mix
-mixed
-mixture
-mobile
-model
-modify
-mom
-moment
-monitor
-monkey
-monster
-month
-moon
-moral
-more
-morning
-mosquito
-mother
-motion
-motor
-mountain
-mouse
-move
-movie
-much
-muffin
-mule
-multiply
-muscle
-museum
-mushroom
-music
-must
-mutual
-myself
-mystery
-myth
-naive
-name
-napkin
-narrow
-nasty
-nation
-nature
-near
-neck
-need
-negative
-neglect
-neither
-nephew
-nerve
-nest
-net
-network
-neutral
-never
-news
-next
-nice
-night
-noble
-noise
-nominee
-noodle
-normal
-north
-nose
-notable
-note
-nothing
-notice
-novel
-now
-nuclear
-number
-nurse
-nut
-oak
-obey
-object
-oblige
-obscure
-observe
-obtain
-obvious
-occur
-ocean
-october
-odor
-off
-offer
-office
-often
-oil
-okay
-old
-olive
-olympic
-omit
-once
-one
-onion
-online
-only
-open
-opera
-opinion
-oppose
-option
-orange
-orbit
-orchard
-order
-ordinary
-organ
-orient
-original
-orphan
-ostrich
-other
-outdoor
-outer
-output
-outside
-oval
-oven
-over
-own
-owner
-oxygen
-oyster
-ozone
-pact
-paddle
-page
-pair
-palace
-palm
-panda
-panel
-panic
-panther
-paper
-parade
-parent
-park
-parrot
-party
-pass
-patch
-path
-patient
-patrol
-pattern
-pause
-pave
-payment
-peace
-peanut
-pear
-peasant
-pelican
-pen
-penalty
-pencil
-people
-pepper
-perfect
-permit
-person
-pet
-phone
-photo
-phrase
-physical
-piano
-picnic
-picture
-piece
-pig
-pigeon
-pill
-pilot
-pink
-pioneer
-pipe
-pistol
-pitch
-pizza
-place
-planet
-plastic
-plate
-play
-please
-pledge
-pluck
-plug
-plunge
-poem
-poet
-point
-polar
-pole
-police
-pond
-pony
-pool
-popular
-portion
-position
-possible
-post
-potato
-pottery
-poverty
-powder
-power
-practice
-praise
-predict
-prefer
-prepare
-present
-pretty
-prevent
-price
-pride
-primary
-print
-priority
-prison
-private
-prize
-problem
-process
-produce
-profit
-program
-project
-promote
-proof
-property
-prosper
-protect
-proud
-provide
-public
-pudding
-pull
-pulp
-pulse
-pumpkin
-punch
-pupil
-puppy
-purchase
-purity
-purpose
-purse
-push
-put
-puzzle
-pyramid
-quality
-quantum
-quarter
-question
-quick
-quit
-quiz
-quote
-rabbit
-raccoon
-race
-rack
-radar
-radio
-rail
-rain
-raise
-rally
-ramp
-ranch
-random
-range
-rapid
-rare
-rate
-rather
-raven
-raw
-razor
-ready
-real
-reason
-rebel
-rebuild
-recall
-receive
-recipe
-record
-recycle
-reduce
-reflect
-reform
-refuse
-region
-regret
-regular
-reject
-relax
-release
-relief
-rely
-remain
-remember
-remind
-remove
-render
-renew
-rent
-reopen
-repair
-repeat
-replace
-report
-require
-rescue
-resemble
-resist
-resource
-response
-result
-retire
-retreat
-return
-reunion
-reveal
-review
-reward
-rhythm
-rib
-ribbon
-rice
-rich
-ride
-ridge
-rifle
-right
-rigid
-ring
-riot
-ripple
-risk
-ritual
-rival
-river
-road
-roast
-robot
-robust
-rocket
-romance
-roof
-rookie
-room
-rose
-rotate
-rough
-round
-route
-royal
-rubber
-rude
-rug
-rule
-run
-runway
-rural
-sad
-saddle
-sadness
-safe
-sail
-salad
-salmon
-salon
-salt
-salute
-same
-sample
-sand
-satisfy
-satoshi
-sauce
-sausage
-save
-say
-scale
-scan
-scare
-scatter
-scene
-scheme
-school
-science
-scissors
-scorpion
-scout
-scrap
-screen
-script
-scrub
-sea
-search
-season
-seat
-second
-secret
-section
-security
-seed
-seek
-segment
-select
-sell
-seminar
-senior
-sense
-sentence
-series
-service
-session
-settle
-setup
-seven
-shadow
-shaft
-shallow
-share
-shed
-shell
-sheriff
-shield
-shift
-shine
-ship
-shiver
-shock
-shoe
-shoot
-shop
-short
-shoulder
-shove
-shrimp
-shrug
-shuffle
-shy
-sibling
-sick
-side
-siege
-sight
-sign
-silent
-silk
-silly
-silver
-similar
-simple
-since
-sing
-siren
-sister
-situate
-six
-size
-skate
-sketch
-ski
-skill
-skin
-skirt
-skull
-slab
-slam
-sleep
-slender
-slice
-slide
-slight
-slim
-slogan
-slot
-slow
-slush
-small
-smart
-smile
-smoke
-smooth
-snack
-snake
-snap
-sniff
-snow
-soap
-soccer
-social
-sock
-soda
-soft
-solar
-soldier
-solid
-solution
-solve
-someone
-song
-soon
-sorry
-sort
-soul
-sound
-soup
-source
-south
-space
-spare
-spatial
-spawn
-speak
-special
-speed
-spell
-spend
-sphere
-spice
-spider
-spike
-spin
-spirit
-split
-spoil
-sponsor
-spoon
-sport
-spot
-spray
-spread
-spring
-spy
-square
-squeeze
-squirrel
-stable
-stadium
-staff
-stage
-stairs
-stamp
-stand
-start
-state
-stay
-steak
-steel
-stem
-step
-stereo
-stick
-still
-sting
-stock
-stomach
-stone
-stool
-story
-stove
-strategy
-street
-strike
-strong
-struggle
-student
-stuff
-stumble
-style
-subject
-submit
-subway
-success
-such
-sudden
-suffer
-sugar
-suggest
-suit
-summer
-sun
-sunny
-sunset
-super
-supply
-supreme
-sure
-surface
-surge
-surprise
-surround
-survey
-suspect
-sustain
-swallow
-swamp
-swap
-swarm
-swear
-sweet
-swift
-swim
-swing
-switch
-sword
-symbol
-symptom
-syrup
-system
-table
-tackle
-tag
-tail
-talent
-talk
-tank
-tape
-target
-task
-taste
-tattoo
-taxi
-teach
-team
-tell
-ten
-tenant
-tennis
-tent
-term
-test
-text
-thank
-that
-theme
-then
-theory
-there
-they
-thing
-this
-thought
-three
-thrive
-throw
-thumb
-thunder
-ticket
-tide
-tiger
-tilt
-timber
-time
-tiny
-tip
-tired
-tissue
-title
-toast
-tobacco
-today
-toddler
-toe
-together
-toilet
-token
-tomato
-tomorrow
-tone
-tongue
-tonight
-tool
-tooth
-top
-topic
-topple
-torch
-tornado
-tortoise
-toss
-total
-tourist
-toward
-tower
-town
-toy
-track
-trade
-traffic
-tragic
-train
-transfer
-trap
-trash
-travel
-tray
-treat
-tree
-trend
-trial
-tribe
-trick
-trigger
-trim
-trip
-trophy
-trouble
-truck
-true
-truly
-trumpet
-trust
-truth
-try
-tube
-tuition
-tumble
-tuna
-tunnel
-turkey
-turn
-turtle
-twelve
-twenty
-twice
-twin
-twist
-two
-type
-typical
-ugly
-umbrella
-unable
-unaware
-uncle
-uncover
-under
-undo
-unfair
-unfold
-unhappy
-uniform
-unique
-unit
-universe
-unknown
-unlock
-until
-unusual
-unveil
-update
-upgrade
-uphold
-upon
-upper
-upset
-urban
-urge
-usage
-use
-used
-useful
-useless
-usual
-utility
-vacant
-vacuum
-vague
-valid
-valley
-valve
-van
-vanish
-vapor
-various
-vast
-vault
-vehicle
-velvet
-vendor
-venture
-venue
-verb
-verify
-version
-very
-vessel
-veteran
-viable
-vibrant
-vicious
-victory
-video
-view
-village
-vintage
-violin
-virtual
-virus
-visa
-visit
-visual
-vital
-vivid
-vocal
-voice
-void
-volcano
-volume
-vote
-voyage
-wage
-wagon
-wait
-walk
-wall
-walnut
-want
-warfare
-warm
-warrior
-wash
-wasp
-waste
-water
-wave
-way
-wealth
-weapon
-wear
-weasel
-weather
-web
-wedding
-weekend
-weird
-welcome
-west
-wet
-whale
-what
-wheat
-wheel
-when
-where
-whip
-whisper
-wide
-width
-wife
-wild
-will
-win
-window
-wine
-wing
-wink
-winner
-winter
-wire
-wisdom
-wise
-wish
-witness
-wolf
-woman
-wonder
-wood
-wool
-word
-work
-world
-worry
-worth
-wrap
-wreck
-wrestle
-wrist
-write
-wrong
-yard
-year
-yellow
-you
-young
-youth
-zebra
-zero
-zone
-zoo
diff --git a/backend/src/config/mnemonic.uncompressed_buffer13116.txt b/backend/src/config/mnemonic.uncompressed_buffer13116.txt
deleted file mode 100644
index 8eceb1e2f..000000000
--- a/backend/src/config/mnemonic.uncompressed_buffer13116.txt
+++ /dev/null
@@ -1 +0,0 @@
-abandon,ability,able,about,above,absent,absorb,abstract,absurd,abuse,access,accident,account,accuse,achieve,acid,acoustic,acquire,across,act,action,actor,actress,actual,adapt,add,addict,address,adjust,admit,adult,advance,advice,aerobic,affair,afford,afraid,again,age,agent,agree,ahead,aim,air,airport,aisle,alarm,album,alcohol,alert,alien,all,alley,allow,almost,alone,alpha,already,also,alter,always,amateur,amazing,among,amount,amused,analyst,anchor,ancient,anger,angle,angry,animal,ankle,announce,annual,another,answer,antenna,antique,anxiety,any,apart,apology,appear,apple,approve,april,arch,arctic,area,arena,argue,arm,armed,armor,army,around,arrange,arrest,arrive,arrow,art,artefact,artist,artwork,ask,aspect,assault,asset,assist,assume,asthma,athlete,atom,attack,attend,attitude,attract,auction,audit,august,aunt,author,auto,autumn,average,avocado,avoid,awake,aware,away,awesome,awful,awkward,axis,baby,bachelor,bacon,badge,bag,balance,balcony,ball,bamboo,banana,banner,bar,barely,bargain,barrel,base,basic,basket,battle,beach,bean,beauty,because,become,beef,before,begin,behave,behind,believe,below,belt,bench,benefit,best,betray,better,between,beyond,bicycle,bid,bike,bind,biology,bird,birth,bitter,black,blade,blame,blanket,blast,bleak,bless,blind,blood,blossom,blouse,blue,blur,blush,board,boat,body,boil,bomb,bone,bonus,book,boost,border,boring,borrow,boss,bottom,bounce,box,boy,bracket,brain,brand,brass,brave,bread,breeze,brick,bridge,brief,bright,bring,brisk,broccoli,broken,bronze,broom,brother,brown,brush,bubble,buddy,budget,buffalo,build,bulb,bulk,bullet,bundle,bunker,burden,burger,burst,bus,business,busy,butter,buyer,buzz,cabbage,cabin,cable,cactus,cage,cake,call,calm,camera,camp,can,canal,cancel,candy,cannon,canoe,canvas,canyon,capable,capital,captain,car,carbon,card,cargo,carpet,carry,cart,case,cash,casino,castle,casual,cat,catalog,catch,category,cattle,caught,cause,caution,cave,ceiling,celery,cement,census,century,cereal,certain,chair,chalk,champion,change,chaos,chapter,charge,chase,chat,cheap,check,cheese,chef,cherry,chest,chicken,chief,child,chimney,choice,choose,chronic,chuckle,chunk,churn,cigar,cinnamon,circle,citizen,city,civil,claim,clap,clarify,claw,clay,clean,clerk,clever,click,client,cliff,climb,clinic,clip,clock,clog,close,cloth,cloud,clown,club,clump,cluster,clutch,coach,coast,coconut,code,coffee,coil,coin,collect,color,column,combine,come,comfort,comic,common,company,concert,conduct,confirm,congress,connect,consider,control,convince,cook,cool,copper,copy,coral,core,corn,correct,cost,cotton,couch,country,couple,course,cousin,cover,coyote,crack,cradle,craft,cram,crane,crash,crater,crawl,crazy,cream,credit,creek,crew,cricket,crime,crisp,critic,crop,cross,crouch,crowd,crucial,cruel,cruise,crumble,crunch,crush,cry,crystal,cube,culture,cup,cupboard,curious,current,curtain,curve,cushion,custom,cute,cycle,dad,damage,damp,dance,danger,daring,dash,daughter,dawn,day,deal,debate,debris,decade,december,decide,decline,decorate,decrease,deer,defense,define,defy,degree,delay,deliver,demand,demise,denial,dentist,deny,depart,depend,deposit,depth,deputy,derive,describe,desert,design,desk,despair,destroy,detail,detect,develop,device,devote,diagram,dial,diamond,diary,dice,diesel,diet,differ,digital,dignity,dilemma,dinner,dinosaur,direct,dirt,disagree,discover,disease,dish,dismiss,disorder,display,distance,divert,divide,divorce,dizzy,doctor,document,dog,doll,dolphin,domain,donate,donkey,donor,door,dose,double,dove,draft,dragon,drama,drastic,draw,dream,dress,drift,drill,drink,drip,drive,drop,drum,dry,duck,dumb,dune,during,dust,dutch,duty,dwarf,dynamic,eager,eagle,early,earn,earth,easily,east,easy,echo,ecology,economy,edge,edit,educate,effort,egg,eight,either,elbow,elder,electric,elegant,element,elephant,elevator,elite,else,embark,embody,embrace,emerge,emotion,employ,empower,empty,enable,enact,end,endless,endorse,enemy,energy,enforce,engage,engine,enhance,enjoy,enlist,enough,enrich,enroll,ensure,enter,entire,entry,envelope,episode,equal,equip,era,erase,erode,erosion,error,erupt,escape,essay,essence,estate,eternal,ethics,evidence,evil,evoke,evolve,exact,example,excess,exchange,excite,exclude,excuse,execute,exercise,exhaust,exhibit,exile,exist,exit,exotic,expand,expect,expire,explain,expose,express,extend,extra,eye,eyebrow,fabric,face,faculty,fade,faint,faith,fall,false,fame,family,famous,fan,fancy,fantasy,farm,fashion,fat,fatal,father,fatigue,fault,favorite,feature,february,federal,fee,feed,feel,female,fence,festival,fetch,fever,few,fiber,fiction,field,figure,file,film,filter,final,find,fine,finger,finish,fire,firm,first,fiscal,fish,fit,fitness,fix,flag,flame,flash,flat,flavor,flee,flight,flip,float,flock,floor,flower,fluid,flush,fly,foam,focus,fog,foil,fold,follow,food,foot,force,forest,forget,fork,fortune,forum,forward,fossil,foster,found,fox,fragile,frame,frequent,fresh,friend,fringe,frog,front,frost,frown,frozen,fruit,fuel,fun,funny,furnace,fury,future,gadget,gain,galaxy,gallery,game,gap,garage,garbage,garden,garlic,garment,gas,gasp,gate,gather,gauge,gaze,general,genius,genre,gentle,genuine,gesture,ghost,giant,gift,giggle,ginger,giraffe,girl,give,glad,glance,glare,glass,glide,glimpse,globe,gloom,glory,glove,glow,glue,goat,goddess,gold,good,goose,gorilla,gospel,gossip,govern,gown,grab,grace,grain,grant,grape,grass,gravity,great,green,grid,grief,grit,grocery,group,grow,grunt,guard,guess,guide,guilt,guitar,gun,gym,habit,hair,half,hammer,hamster,hand,happy,harbor,hard,harsh,harvest,hat,have,hawk,hazard,head,health,heart,heavy,hedgehog,height,hello,helmet,help,hen,hero,hidden,high,hill,hint,hip,hire,history,hobby,hockey,hold,hole,holiday,hollow,home,honey,hood,hope,horn,horror,horse,hospital,host,hotel,hour,hover,hub,huge,human,humble,humor,hundred,hungry,hunt,hurdle,hurry,hurt,husband,hybrid,ice,icon,idea,identify,idle,ignore,ill,illegal,illness,image,imitate,immense,immune,impact,impose,improve,impulse,inch,include,income,increase,index,indicate,indoor,industry,infant,inflict,inform,inhale,inherit,initial,inject,injury,inmate,inner,innocent,input,inquiry,insane,insect,inside,inspire,install,intact,interest,into,invest,invite,involve,iron,island,isolate,issue,item,ivory,jacket,jaguar,jar,jazz,jealous,jeans,jelly,jewel,job,join,joke,journey,joy,judge,juice,jump,jungle,junior,junk,just,kangaroo,keen,keep,ketchup,key,kick,kid,kidney,kind,kingdom,kiss,kit,kitchen,kite,kitten,kiwi,knee,knife,knock,know,lab,label,labor,ladder,lady,lake,lamp,language,laptop,large,later,latin,laugh,laundry,lava,law,lawn,lawsuit,layer,lazy,leader,leaf,learn,leave,lecture,left,leg,legal,legend,leisure,lemon,lend,length,lens,leopard,lesson,letter,level,liar,liberty,library,license,life,lift,light,like,limb,limit,link,lion,liquid,list,little,live,lizard,load,loan,lobster,local,lock,logic,lonely,long,loop,lottery,loud,lounge,love,loyal,lucky,luggage,lumber,lunar,lunch,luxury,lyrics,machine,mad,magic,magnet,maid,mail,main,major,make,mammal,man,manage,mandate,mango,mansion,manual,maple,marble,march,margin,marine,market,marriage,mask,mass,master,match,material,math,matrix,matter,maximum,maze,meadow,mean,measure,meat,mechanic,medal,media,melody,melt,member,memory,mention,menu,mercy,merge,merit,merry,mesh,message,metal,method,middle,midnight,milk,million,mimic,mind,minimum,minor,minute,miracle,mirror,misery,miss,mistake,mix,mixed,mixture,mobile,model,modify,mom,moment,monitor,monkey,monster,month,moon,moral,more,morning,mosquito,mother,motion,motor,mountain,mouse,move,movie,much,muffin,mule,multiply,muscle,museum,mushroom,music,must,mutual,myself,mystery,myth,naive,name,napkin,narrow,nasty,nation,nature,near,neck,need,negative,neglect,neither,nephew,nerve,nest,net,network,neutral,never,news,next,nice,night,noble,noise,nominee,noodle,normal,north,nose,notable,note,nothing,notice,novel,now,nuclear,number,nurse,nut,oak,obey,object,oblige,obscure,observe,obtain,obvious,occur,ocean,october,odor,off,offer,office,often,oil,okay,old,olive,olympic,omit,once,one,onion,online,only,open,opera,opinion,oppose,option,orange,orbit,orchard,order,ordinary,organ,orient,original,orphan,ostrich,other,outdoor,outer,output,outside,oval,oven,over,own,owner,oxygen,oyster,ozone,pact,paddle,page,pair,palace,palm,panda,panel,panic,panther,paper,parade,parent,park,parrot,party,pass,patch,path,patient,patrol,pattern,pause,pave,payment,peace,peanut,pear,peasant,pelican,pen,penalty,pencil,people,pepper,perfect,permit,person,pet,phone,photo,phrase,physical,piano,picnic,picture,piece,pig,pigeon,pill,pilot,pink,pioneer,pipe,pistol,pitch,pizza,place,planet,plastic,plate,play,please,pledge,pluck,plug,plunge,poem,poet,point,polar,pole,police,pond,pony,pool,popular,portion,position,possible,post,potato,pottery,poverty,powder,power,practice,praise,predict,prefer,prepare,present,pretty,prevent,price,pride,primary,print,priority,prison,private,prize,problem,process,produce,profit,program,project,promote,proof,property,prosper,protect,proud,provide,public,pudding,pull,pulp,pulse,pumpkin,punch,pupil,puppy,purchase,purity,purpose,purse,push,put,puzzle,pyramid,quality,quantum,quarter,question,quick,quit,quiz,quote,rabbit,raccoon,race,rack,radar,radio,rail,rain,raise,rally,ramp,ranch,random,range,rapid,rare,rate,rather,raven,raw,razor,ready,real,reason,rebel,rebuild,recall,receive,recipe,record,recycle,reduce,reflect,reform,refuse,region,regret,regular,reject,relax,release,relief,rely,remain,remember,remind,remove,render,renew,rent,reopen,repair,repeat,replace,report,require,rescue,resemble,resist,resource,response,result,retire,retreat,return,reunion,reveal,review,reward,rhythm,rib,ribbon,rice,rich,ride,ridge,rifle,right,rigid,ring,riot,ripple,risk,ritual,rival,river,road,roast,robot,robust,rocket,romance,roof,rookie,room,rose,rotate,rough,round,route,royal,rubber,rude,rug,rule,run,runway,rural,sad,saddle,sadness,safe,sail,salad,salmon,salon,salt,salute,same,sample,sand,satisfy,satoshi,sauce,sausage,save,say,scale,scan,scare,scatter,scene,scheme,school,science,scissors,scorpion,scout,scrap,screen,script,scrub,sea,search,season,seat,second,secret,section,security,seed,seek,segment,select,sell,seminar,senior,sense,sentence,series,service,session,settle,setup,seven,shadow,shaft,shallow,share,shed,shell,sheriff,shield,shift,shine,ship,shiver,shock,shoe,shoot,shop,short,shoulder,shove,shrimp,shrug,shuffle,shy,sibling,sick,side,siege,sight,sign,silent,silk,silly,silver,similar,simple,since,sing,siren,sister,situate,six,size,skate,sketch,ski,skill,skin,skirt,skull,slab,slam,sleep,slender,slice,slide,slight,slim,slogan,slot,slow,slush,small,smart,smile,smoke,smooth,snack,snake,snap,sniff,snow,soap,soccer,social,sock,soda,soft,solar,soldier,solid,solution,solve,someone,song,soon,sorry,sort,soul,sound,soup,source,south,space,spare,spatial,spawn,speak,special,speed,spell,spend,sphere,spice,spider,spike,spin,spirit,split,spoil,sponsor,spoon,sport,spot,spray,spread,spring,spy,square,squeeze,squirrel,stable,stadium,staff,stage,stairs,stamp,stand,start,state,stay,steak,steel,stem,step,stereo,stick,still,sting,stock,stomach,stone,stool,story,stove,strategy,street,strike,strong,struggle,student,stuff,stumble,style,subject,submit,subway,success,such,sudden,suffer,sugar,suggest,suit,summer,sun,sunny,sunset,super,supply,supreme,sure,surface,surge,surprise,surround,survey,suspect,sustain,swallow,swamp,swap,swarm,swear,sweet,swift,swim,swing,switch,sword,symbol,symptom,syrup,system,table,tackle,tag,tail,talent,talk,tank,tape,target,task,taste,tattoo,taxi,teach,team,tell,ten,tenant,tennis,tent,term,test,text,thank,that,theme,then,theory,there,they,thing,this,thought,three,thrive,throw,thumb,thunder,ticket,tide,tiger,tilt,timber,time,tiny,tip,tired,tissue,title,toast,tobacco,today,toddler,toe,together,toilet,token,tomato,tomorrow,tone,tongue,tonight,tool,tooth,top,topic,topple,torch,tornado,tortoise,toss,total,tourist,toward,tower,town,toy,track,trade,traffic,tragic,train,transfer,trap,trash,travel,tray,treat,tree,trend,trial,tribe,trick,trigger,trim,trip,trophy,trouble,truck,true,truly,trumpet,trust,truth,try,tube,tuition,tumble,tuna,tunnel,turkey,turn,turtle,twelve,twenty,twice,twin,twist,two,type,typical,ugly,umbrella,unable,unaware,uncle,uncover,under,undo,unfair,unfold,unhappy,uniform,unique,unit,universe,unknown,unlock,until,unusual,unveil,update,upgrade,uphold,upon,upper,upset,urban,urge,usage,use,used,useful,useless,usual,utility,vacant,vacuum,vague,valid,valley,valve,van,vanish,vapor,various,vast,vault,vehicle,velvet,vendor,venture,venue,verb,verify,version,very,vessel,veteran,viable,vibrant,vicious,victory,video,view,village,vintage,violin,virtual,virus,visa,visit,visual,vital,vivid,vocal,voice,void,volcano,volume,vote,voyage,wage,wagon,wait,walk,wall,walnut,want,warfare,warm,warrior,wash,wasp,waste,water,wave,way,wealth,weapon,wear,weasel,weather,web,wedding,weekend,weird,welcome,west,wet,whale,what,wheat,wheel,when,where,whip,whisper,wide,width,wife,wild,will,win,window,wine,wing,wink,winner,winter,wire,wisdom,wise,wish,witness,wolf,woman,wonder,wood,wool,word,work,world,worry,worth,wrap,wreck,wrestle,wrist,write,wrong,yard,year,yellow,you,young,youth,zebra,zero,zone,zoo,
\ No newline at end of file
diff --git a/backend/src/emails/sendEmailVariants.test.ts b/backend/src/emails/sendEmailVariants.test.ts
index 262b91be2..9396c2b13 100644
--- a/backend/src/emails/sendEmailVariants.test.ts
+++ b/backend/src/emails/sendEmailVariants.test.ts
@@ -70,6 +70,8 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -106,10 +108,14 @@ describe('sendEmailVariants', () => {
'To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
)
expect(result.originalMessage.html).toContain(
- `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
+ `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -140,6 +146,8 @@ describe('sendEmailVariants', () => {
activationLink: 'http://localhost/checkEmail/6627633878930542284',
timeDurationObject: { hours: 23, minutes: 30 },
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -178,12 +186,16 @@ describe('sendEmailVariants', () => {
'or copy the link above into your browser window.',
)
expect(result.originalMessage.html).toContain(
- '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 by entering your email address:',
+ '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:',
)
expect(result.originalMessage.html).toContain(
`${CONFIG.EMAIL_LINK_FORGOTPASSWORD}`,
)
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -210,6 +222,8 @@ describe('sendEmailVariants', () => {
lastName: 'Lustig',
locale: 'en',
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -255,9 +269,13 @@ describe('sendEmailVariants', () => {
'or copy the link above into your browser window.',
)
expect(result.originalMessage.html).toContain(
- 'If you are not the one who tried to register again, please contact our support:',
+ 'If you are not the one who tried to register again, please contact our support:
support@supportmail.com',
+ )
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
)
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
})
})
})
@@ -292,6 +310,8 @@ describe('sendEmailVariants', () => {
contributionMemo: 'My contribution.',
contributionAmount: '23.54',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -326,10 +346,14 @@ describe('sendEmailVariants', () => {
)
expect(result.originalMessage.html).toContain('Amount: 23.54 GDD')
expect(result.originalMessage.html).toContain(
- `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
+ `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -362,6 +386,8 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -398,10 +424,14 @@ describe('sendEmailVariants', () => {
'To see your common good contributions and related messages, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!',
)
expect(result.originalMessage.html).toContain(
- `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
+ `Link to your account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -432,6 +462,8 @@ describe('sendEmailVariants', () => {
resetLink: 'http://localhost/reset-password/3762660021544901417',
timeDurationObject: { hours: 23, minutes: 30 },
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -468,12 +500,16 @@ describe('sendEmailVariants', () => {
'or copy the link above into your browser window.',
)
expect(result.originalMessage.html).toContain(
- '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 by entering your email address:',
+ '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:',
)
expect(result.originalMessage.html).toContain(
`${CONFIG.EMAIL_LINK_FORGOTPASSWORD}`,
)
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -510,6 +546,8 @@ describe('sendEmailVariants', () => {
transactionMemo: 'You deserve it! 🙏🏼',
transactionAmount: '17.65',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -543,12 +581,16 @@ describe('sendEmailVariants', () => {
'Bibi Bloxberg (bibi@bloxberg.de) has just redeemed your link.',
)
expect(result.originalMessage.html).toContain('Amount: 17.65 GDD')
- expect(result.originalMessage.html).toContain('Memo: You deserve it! 🙏🏼')
+ expect(result.originalMessage.html).toContain('Message: You deserve it! 🙏🏼')
expect(result.originalMessage.html).toContain(
- `You can find transaction details in your Gradido account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
+ `You can find transaction details in your Gradido account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
@@ -583,6 +625,8 @@ describe('sendEmailVariants', () => {
senderEmail: 'bibi@bloxberg.de',
transactionAmount: '37.40',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@@ -598,26 +642,32 @@ describe('sendEmailVariants', () => {
to: 'Peter Lustig ',
from: 'Gradido (do not answer) ',
attachments: [],
- subject: 'Gradido: You have received Gradidos',
+ subject: 'Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
html: expect.any(String),
- text: expect.stringContaining('GRADIDO: YOU HAVE RECEIVED GRADIDOS'),
+ text: expect.stringContaining('GRADIDO: BIBI BLOXBERG HAS SENT YOU 37.40 GRADIDO'),
}),
})
expect(result.originalMessage.html).toContain('')
expect(result.originalMessage.html).toContain('')
expect(result.originalMessage.html).toContain(
- 'Gradido: You have received Gradidos',
+ 'Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
+ )
+ expect(result.originalMessage.html).toContain(
+ '>Gradido: Bibi Bloxberg has sent you 37.40 Gradido',
)
- expect(result.originalMessage.html).toContain('>Gradido: You have received Gradidos')
expect(result.originalMessage.html).toContain('Hello Peter Lustig')
expect(result.originalMessage.html).toContain(
'You have just received 37.40 GDD from Bibi Bloxberg (bibi@bloxberg.de).',
)
expect(result.originalMessage.html).toContain(
- `You can find transaction details in your Gradido account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
+ `You can find transaction details in your Gradido account: ${CONFIG.EMAIL_LINK_OVERVIEW}`,
)
expect(result.originalMessage.html).toContain('Please do not reply to this email!')
- expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('Kind regards,
your Gradido team')
+ expect(result.originalMessage.html).toContain('—————')
+ expect(result.originalMessage.html).toContain(
+ '
Gradido-Akademie
Institut fĂĽr Wirtschaftsbionik
Pfarrweg 2
74653 KĂĽnzelsau
Deutschland
support@supportmail.com
http://localhost/',
+ )
})
})
})
diff --git a/backend/src/emails/sendEmailVariants.ts b/backend/src/emails/sendEmailVariants.ts
index 356f95e39..cb1376510 100644
--- a/backend/src/emails/sendEmailVariants.ts
+++ b/backend/src/emails/sendEmailVariants.ts
@@ -25,6 +25,8 @@ export const sendAddedContributionMessageEmail = (data: {
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -47,6 +49,8 @@ export const sendAccountActivationEmail = (data: {
activationLink: data.activationLink,
timeDurationObject: data.timeDurationObject,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -65,6 +69,8 @@ export const sendAccountMultiRegistrationEmail = (data: {
lastName: data.lastName,
locale: data.language,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -91,6 +97,8 @@ export const sendContributionConfirmedEmail = (data: {
contributionMemo: data.contributionMemo,
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -115,6 +123,8 @@ export const sendContributionRejectedEmail = (data: {
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -137,6 +147,8 @@ export const sendResetPasswordEmail = (data: {
resetLink: data.resetLink,
timeDurationObject: data.timeDurationObject,
resendLink: CONFIG.EMAIL_LINK_FORGOTPASSWORD,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -165,6 +177,8 @@ export const sendTransactionLinkRedeemedEmail = (data: {
transactionMemo: data.transactionMemo,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
@@ -191,6 +205,8 @@ export const sendTransactionReceivedEmail = (data: {
senderEmail: data.senderEmail,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
+ supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
+ communityURL: CONFIG.COMMUNITY_URL,
},
})
}
diff --git a/backend/src/emails/templates/accountActivation/html.pug b/backend/src/emails/templates/accountActivation/html.pug
index f283e941e..6075fa8a8 100644
--- a/backend/src/emails/templates/accountActivation/html.pug
+++ b/backend/src/emails/templates/accountActivation/html.pug
@@ -5,16 +5,16 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.accountActivation.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.accountActivation.emailRegistered')
- p= t('emails.accountActivation.pleaseClickLink')
+ p
+ = t('emails.accountActivation.pleaseClickLink')
br
a(href=activationLink) #{activationLink}
br
- span= t('emails.general.orCopyLink')
- p= t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
+ = t('emails.general.orCopyLink')
+ p
+ = t('emails.accountActivation.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
br
a(href=resendLink) #{resendLink}
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/accountMultiRegistration/html.pug b/backend/src/emails/templates/accountMultiRegistration/html.pug
index b3764403b..f8570422c 100644
--- a/backend/src/emails/templates/accountMultiRegistration/html.pug
+++ b/backend/src/emails/templates/accountMultiRegistration/html.pug
@@ -5,18 +5,19 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.accountMultiRegistration.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
- p= t('emails.accountMultiRegistration.emailReused')
+ include ../hello.pug
+ p
+ = t('emails.accountMultiRegistration.emailReused')
br
- span= t('emails.accountMultiRegistration.emailExists')
- p= t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
+ = t('emails.accountMultiRegistration.emailExists')
+ p
+ = t('emails.accountMultiRegistration.onForgottenPasswordClickLink')
br
a(href=resendLink) #{resendLink}
br
- span= t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
- p= t('emails.accountMultiRegistration.ifYouAreNotTheOne')
+ = t('emails.accountMultiRegistration.onForgottenPasswordCopyLink')
+ p
+ = t('emails.accountMultiRegistration.ifYouAreNotTheOne')
br
- a(href='https://gradido.net/de/contact/') https://gradido.net/de/contact/
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ a(href='mailto:' + supportEmail)= supportEmail
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/addedContributionMessage/html.pug b/backend/src/emails/templates/addedContributionMessage/html.pug
index 5e5d0975c..e7410c0f0 100644
--- a/backend/src/emails/templates/addedContributionMessage/html.pug
+++ b/backend/src/emails/templates/addedContributionMessage/html.pug
@@ -5,13 +5,12 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.addedContributionMessage.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
- p= t('emails.general.linkToYourAccount')
- span= " "
+ p
+ = t('emails.general.linkToYourAccount')
+ = " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/contributionConfirmed/html.pug b/backend/src/emails/templates/contributionConfirmed/html.pug
index 32626b147..4ff17ffd4 100644
--- a/backend/src/emails/templates/contributionConfirmed/html.pug
+++ b/backend/src/emails/templates/contributionConfirmed/html.pug
@@ -5,13 +5,12 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.contributionConfirmed.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.contributionConfirmed.commonGoodContributionConfirmed', { senderFirstName, senderLastName, contributionMemo })
p= t('emails.general.amountGDD', { amountGDD: contributionAmount })
- p= t('emails.general.linkToYourAccount')
- span= " "
+ p
+ = t('emails.general.linkToYourAccount')
+ = " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/contributionRejected/html.pug b/backend/src/emails/templates/contributionRejected/html.pug
index 07c014f92..531f75543 100644
--- a/backend/src/emails/templates/contributionRejected/html.pug
+++ b/backend/src/emails/templates/contributionRejected/html.pug
@@ -5,13 +5,12 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.contributionRejected.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.contributionRejected.commonGoodContributionRejected', { senderFirstName, senderLastName, contributionMemo })
p= t('emails.contributionRejected.toSeeContributionsAndMessages')
- p= t('emails.general.linkToYourAccount')
- span= " "
+ p
+ = t('emails.general.linkToYourAccount')
+ = " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/greatingFormularImprint.pug b/backend/src/emails/templates/greatingFormularImprint.pug
new file mode 100644
index 000000000..d7d8c3a14
--- /dev/null
+++ b/backend/src/emails/templates/greatingFormularImprint.pug
@@ -0,0 +1,16 @@
+p(style='margin-top: 24px;')
+ = t('emails.general.sincerelyYours')
+ br
+ = t('emails.general.yourGradidoTeam')
+p(style='margin-top: 24px;')= '—————'
+p(style='margin-top: 24px;')
+ if t('general.imprintImageURL').length > 0
+ div(style='position: relative; left: -22px;')
+ img(src=t('general.imprintImageURL'), width='200', alt=t('general.imprintImageAlt'))
+ br
+ each line in t('general.imprint').split(/\n/)
+ = line
+ br
+ a(href='mailto:' + supportEmail)= supportEmail
+ br
+ a(href=communityURL)= communityURL
diff --git a/backend/src/emails/templates/hello.pug b/backend/src/emails/templates/hello.pug
new file mode 100644
index 000000000..3e2591951
--- /dev/null
+++ b/backend/src/emails/templates/hello.pug
@@ -0,0 +1 @@
+p= t('emails.general.helloName', { firstName, lastName })
diff --git a/backend/src/emails/templates/resetPassword/html.pug b/backend/src/emails/templates/resetPassword/html.pug
index a3ced9a75..53ffbcd04 100644
--- a/backend/src/emails/templates/resetPassword/html.pug
+++ b/backend/src/emails/templates/resetPassword/html.pug
@@ -5,16 +5,16 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.resetPassword.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.resetPassword.youOrSomeoneResetPassword')
- p= t('emails.resetPassword.pleaseClickLink')
+ p
+ = t('emails.resetPassword.pleaseClickLink')
br
a(href=resetLink) #{resetLink}
br
- span= t('emails.general.orCopyLink')
- p= t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
+ = t('emails.general.orCopyLink')
+ p
+ = t('emails.resetPassword.duration', { hours: timeDurationObject.hours, minutes: timeDurationObject.minutes })
br
a(href=resendLink) #{resendLink}
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/transactionLinkRedeemed/html.pug b/backend/src/emails/templates/transactionLinkRedeemed/html.pug
index 321d070b4..fb561f5c5 100644
--- a/backend/src/emails/templates/transactionLinkRedeemed/html.pug
+++ b/backend/src/emails/templates/transactionLinkRedeemed/html.pug
@@ -5,15 +5,15 @@ html(lang=locale)
body
h1(style='margin-bottom: 24px;')= t('emails.transactionLinkRedeemed.subject')
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.transactionLinkRedeemed.hasRedeemedYourLink', { senderFirstName, senderLastName, senderEmail })
- p= t('emails.general.amountGDD', { amountGDD: transactionAmount })
+ p
+ = t('emails.general.amountGDD', { amountGDD: transactionAmount })
br
- span= t('emails.transactionLinkRedeemed.memo', { transactionMemo })
- p= t('emails.general.detailsYouFindOnLinkToYourAccount')
- span= " "
+ = t('emails.transactionLinkRedeemed.memo', { transactionMemo })
+ p
+ = t('emails.general.detailsYouFindOnLinkToYourAccount')
+ = " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/transactionReceived/html.pug b/backend/src/emails/templates/transactionReceived/html.pug
index eaf57f975..e6807f539 100644
--- a/backend/src/emails/templates/transactionReceived/html.pug
+++ b/backend/src/emails/templates/transactionReceived/html.pug
@@ -1,16 +1,15 @@
doctype html
html(lang=locale)
head
- title= t('emails.transactionReceived.subject')
+ title= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
body
- h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject')
+ h1(style='margin-bottom: 24px;')= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
#container.col
- p(style='margin-bottom: 24px;')= t('emails.general.helloName', { firstName, lastName })
+ include ../hello.pug
p= t('emails.transactionReceived.haveReceivedAmountGDDFrom', { transactionAmount, senderFirstName, senderLastName, senderEmail })
- p= t('emails.general.detailsYouFindOnLinkToYourAccount')
- span= " "
+ p
+ = t('emails.general.detailsYouFindOnLinkToYourAccount')
+ = " "
a(href=overviewURL) #{overviewURL}
p= t('emails.general.pleaseDoNotReply')
- p(style='margin-top: 24px;')= t('emails.general.sincerelyYours')
- br
- span= t('emails.general.yourGradidoTeam')
+ include ../greatingFormularImprint.pug
diff --git a/backend/src/emails/templates/transactionReceived/subject.pug b/backend/src/emails/templates/transactionReceived/subject.pug
index 630752b02..872806ebc 100644
--- a/backend/src/emails/templates/transactionReceived/subject.pug
+++ b/backend/src/emails/templates/transactionReceived/subject.pug
@@ -1 +1 @@
-= t('emails.transactionReceived.subject')
+= t('emails.transactionReceived.subject', { senderFirstName, senderLastName, transactionAmount })
diff --git a/backend/src/federation/index.test.ts b/backend/src/federation/index.test.ts
index 2a9c6d476..e797e5378 100644
--- a/backend/src/federation/index.test.ts
+++ b/backend/src/federation/index.test.ts
@@ -9,7 +9,6 @@ import { Community as DbCommunity } from '@entity/Community'
import { testEnvironment, cleanDB } from '@test/helpers'
CONFIG.FEDERATION_DHT_SEED = '64ebcb0e3ad547848fef4197c6e2332f'
-CONFIG.FEDERATION_DHT_TEST_SOCKET = false
jest.mock('@hyperswarm/dht')
diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts
index c24cde47a..2843225ae 100644
--- a/backend/src/graphql/directive/isAuthorized.ts
+++ b/backend/src/graphql/directive/isAuthorized.ts
@@ -5,9 +5,8 @@ import { AuthChecker } from 'type-graphql'
import { decode, encode } from '@/auth/JWT'
import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
import { RIGHTS } from '@/auth/RIGHTS'
-import { getCustomRepository } from '@dbTools/typeorm'
-import { UserRepository } from '@repository/User'
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
+import { User } from '@entity/User'
const isAuthorized: AuthChecker = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user
@@ -26,14 +25,16 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => {
if (!decoded) {
throw new Error('403.13 - Client certificate revoked')
}
- // Set context pubKey
- context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
+ // Set context gradidoID
+ context.gradidoID = decoded.gradidoID
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
- const userRepository = getCustomRepository(UserRepository)
try {
- const user = await userRepository.findByPubkeyHex(context.pubKey)
+ const user = await User.findOneOrFail({
+ where: { gradidoID: decoded.gradidoID },
+ relations: ['emailContact'],
+ })
context.user = user
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
} catch {
@@ -48,7 +49,7 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => {
}
// set new header token
- context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
+ context.setHeaders.push({ key: 'token', value: encode(decoded.gradidoID) })
return true
}
diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts
index 387018624..cf2d55d94 100644
--- a/backend/src/graphql/resolver/ContributionResolver.test.ts
+++ b/backend/src/graphql/resolver/ContributionResolver.test.ts
@@ -1961,8 +1961,7 @@ describe('ContributionResolver', () => {
})
})
- // In the futrue this should not throw anymore
- it('throws an error for the second confirmation', async () => {
+ it('throws no error for the second confirmation', async () => {
const r1 = mutate({
mutation: confirmContribution,
variables: {
@@ -1982,8 +1981,7 @@ describe('ContributionResolver', () => {
)
await expect(r2).resolves.toEqual(
expect.objectContaining({
- // data: { confirmContribution: true },
- errors: [new GraphQLError('Creation was not successful.')],
+ data: { confirmContribution: true },
}),
)
})
diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts
index 32c72b9b1..2587aab61 100644
--- a/backend/src/graphql/resolver/ContributionResolver.ts
+++ b/backend/src/graphql/resolver/ContributionResolver.ts
@@ -50,6 +50,7 @@ import {
sendContributionConfirmedEmail,
sendContributionRejectedEmail,
} from '@/emails/sendEmailVariants'
+import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
@Resolver()
export class ContributionResolver {
@@ -579,8 +580,10 @@ export class ContributionResolver {
clientTimezoneOffset,
)
- const receivedCallDate = new Date()
+ // acquire lock
+ const releaseLock = await TRANSACTIONS_LOCK.acquire()
+ const receivedCallDate = new Date()
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
@@ -590,7 +593,7 @@ export class ContributionResolver {
.select('transaction')
.from(DbTransaction, 'transaction')
.where('transaction.userId = :id', { id: contribution.userId })
- .orderBy('transaction.balanceDate', 'DESC')
+ .orderBy('transaction.id', 'DESC')
.getOne()
logger.info('lastTransaction ID', lastTransaction ? lastTransaction.id : 'undefined')
@@ -639,10 +642,11 @@ export class ContributionResolver {
})
} catch (e) {
await queryRunner.rollbackTransaction()
- logger.error(`Creation was not successful: ${e}`)
- throw new Error(`Creation was not successful.`)
+ logger.error('Creation was not successful', e)
+ throw new Error('Creation was not successful.')
} finally {
await queryRunner.release()
+ releaseLock()
}
const event = new Event()
diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts
index 28422af26..9f7d30244 100644
--- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts
+++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts
@@ -23,6 +23,11 @@ import { User } from '@entity/User'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import Decimal from 'decimal.js-light'
import { GraphQLError } from 'graphql'
+import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
+
+// mock semaphore to allow use fake timers
+jest.mock('@/util/TRANSACTIONS_LOCK')
+TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())
let mutate: any, query: any, con: any
let testEnv: any
@@ -185,8 +190,7 @@ describe('TransactionLinkResolver', () => {
describe('after one day', () => {
beforeAll(async () => {
jest.useFakeTimers()
- /* eslint-disable-next-line @typescript-eslint/no-empty-function */
- setTimeout(() => {}, 1000 * 60 * 60 * 24)
+ setTimeout(jest.fn(), 1000 * 60 * 60 * 24)
jest.runAllTimers()
await mutate({
mutation: login,
diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts
index 9041aae67..897cf9252 100644
--- a/backend/src/graphql/resolver/TransactionLinkResolver.ts
+++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts
@@ -31,6 +31,7 @@ import { calculateDecay } from '@/util/decay'
import { getUserCreation, validateContribution } from './util/creations'
import { executeTransaction } from './TransactionResolver'
import QueryLinkResult from '@union/QueryLinkResult'
+import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
@@ -165,10 +166,12 @@ export class TransactionLinkResolver {
): Promise {
const clientTimezoneOffset = getClientTimezoneOffset(context)
const user = getUser(context)
- const now = new Date()
if (code.match(/^CL-/)) {
+ // acquire lock
+ const releaseLock = await TRANSACTIONS_LOCK.acquire()
logger.info('redeem contribution link...')
+ const now = new Date()
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
@@ -273,7 +276,7 @@ export class TransactionLinkResolver {
.select('transaction')
.from(DbTransaction, 'transaction')
.where('transaction.userId = :id', { id: user.id })
- .orderBy('transaction.balanceDate', 'DESC')
+ .orderBy('transaction.id', 'DESC')
.getOne()
let newBalance = new Decimal(0)
@@ -309,9 +312,11 @@ export class TransactionLinkResolver {
throw new Error(`Creation from contribution link was not successful. ${e}`)
} finally {
await queryRunner.release()
+ releaseLock()
}
return true
} else {
+ const now = new Date()
const transactionLink = await DbTransactionLink.findOneOrFail({ code })
const linkedUser = await DbUser.findOneOrFail(
{ id: transactionLink.userId },
@@ -322,6 +327,9 @@ export class TransactionLinkResolver {
throw new Error('Cannot redeem own transaction link.')
}
+ // TODO: The now check should be done within the semaphore lock,
+ // since the program might wait a while till it is ready to proceed
+ // writing the transaction.
if (transactionLink.validUntil.getTime() < now.getTime()) {
throw new Error('Transaction Link is not valid anymore.')
}
diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts
index 1d4fe5708..6115ef846 100644
--- a/backend/src/graphql/resolver/TransactionResolver.test.ts
+++ b/backend/src/graphql/resolver/TransactionResolver.test.ts
@@ -368,5 +368,74 @@ describe('send coins', () => {
)
})
})
+
+ describe('more transactions to test semaphore', () => {
+ it('sends the coins four times in a row', async () => {
+ await expect(
+ mutate({
+ mutation: sendCoins,
+ variables: {
+ email: 'peter@lustig.de',
+ amount: 10,
+ memo: 'first transaction',
+ },
+ }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ sendCoins: 'true',
+ },
+ }),
+ )
+ await expect(
+ mutate({
+ mutation: sendCoins,
+ variables: {
+ email: 'peter@lustig.de',
+ amount: 20,
+ memo: 'second transaction',
+ },
+ }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ sendCoins: 'true',
+ },
+ }),
+ )
+ await expect(
+ mutate({
+ mutation: sendCoins,
+ variables: {
+ email: 'peter@lustig.de',
+ amount: 30,
+ memo: 'third transaction',
+ },
+ }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ sendCoins: 'true',
+ },
+ }),
+ )
+ await expect(
+ mutate({
+ mutation: sendCoins,
+ variables: {
+ email: 'peter@lustig.de',
+ amount: 40,
+ memo: 'fourth transaction',
+ },
+ }),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ data: {
+ sendCoins: 'true',
+ },
+ }),
+ )
+ })
+ })
})
})
diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts
index 4b5754132..0ac5b382e 100644
--- a/backend/src/graphql/resolver/TransactionResolver.ts
+++ b/backend/src/graphql/resolver/TransactionResolver.ts
@@ -16,12 +16,12 @@ import { Transaction } from '@model/Transaction'
import { TransactionList } from '@model/TransactionList'
import { Order } from '@enum/Order'
import { TransactionTypeId } from '@enum/TransactionTypeId'
+import { calculateBalance } from '@/util/validate'
import TransactionSendArgs from '@arg/TransactionSendArgs'
import Paginated from '@arg/Paginated'
import { backendLogger as logger } from '@/server/logger'
import { Context, getUser } from '@/server/context'
-import { calculateBalance, isHexPublicKey } from '@/util/validate'
import { RIGHTS } from '@/auth/RIGHTS'
import { communityUser } from '@/util/communityUser'
import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions'
@@ -36,6 +36,8 @@ import { BalanceResolver } from './BalanceResolver'
import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const'
import { findUserByEmail } from './UserResolver'
+import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
+
export const executeTransaction = async (
amount: Decimal,
memo: string,
@@ -62,124 +64,133 @@ export const executeTransaction = async (
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
}
- // validate amount
- const receivedCallDate = new Date()
- const sendBalance = await calculateBalance(
- sender.id,
- amount.mul(-1),
- receivedCallDate,
- transactionLink,
- )
- logger.debug(`calculated Balance=${sendBalance}`)
- if (!sendBalance) {
- logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`)
- throw new Error("user hasn't enough GDD or amount is < 0")
- }
+ // acquire lock
+ const releaseLock = await TRANSACTIONS_LOCK.acquire()
- const queryRunner = getConnection().createQueryRunner()
- await queryRunner.connect()
- await queryRunner.startTransaction('REPEATABLE READ')
- logger.debug(`open Transaction to write...`)
try {
- // transaction
- const transactionSend = new dbTransaction()
- transactionSend.typeId = TransactionTypeId.SEND
- transactionSend.memo = memo
- transactionSend.userId = sender.id
- transactionSend.linkedUserId = recipient.id
- transactionSend.amount = amount.mul(-1)
- transactionSend.balance = sendBalance.balance
- transactionSend.balanceDate = receivedCallDate
- transactionSend.decay = sendBalance.decay.decay
- transactionSend.decayStart = sendBalance.decay.start
- transactionSend.previous = sendBalance.lastTransactionId
- transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null
- await queryRunner.manager.insert(dbTransaction, transactionSend)
-
- logger.debug(`sendTransaction inserted: ${dbTransaction}`)
-
- const transactionReceive = new dbTransaction()
- transactionReceive.typeId = TransactionTypeId.RECEIVE
- transactionReceive.memo = memo
- transactionReceive.userId = recipient.id
- transactionReceive.linkedUserId = sender.id
- transactionReceive.amount = amount
- const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
- transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
- transactionReceive.balanceDate = receivedCallDate
- transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
- transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
- transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
- transactionReceive.linkedTransactionId = transactionSend.id
- transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null
- await queryRunner.manager.insert(dbTransaction, transactionReceive)
- logger.debug(`receive Transaction inserted: ${dbTransaction}`)
-
- // Save linked transaction id for send
- transactionSend.linkedTransactionId = transactionReceive.id
- await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
- logger.debug(`send Transaction updated: ${transactionSend}`)
-
- if (transactionLink) {
- logger.info(`transactionLink: ${transactionLink}`)
- transactionLink.redeemedAt = receivedCallDate
- transactionLink.redeemedBy = recipient.id
- await queryRunner.manager.update(
- dbTransactionLink,
- { id: transactionLink.id },
- transactionLink,
- )
+ // validate amount
+ const receivedCallDate = new Date()
+ const sendBalance = await calculateBalance(
+ sender.id,
+ amount.mul(-1),
+ receivedCallDate,
+ transactionLink,
+ )
+ logger.debug(`calculated Balance=${sendBalance}`)
+ if (!sendBalance) {
+ logger.error(`user hasn't enough GDD or amount is < 0 : balance=${sendBalance}`)
+ throw new Error("user hasn't enough GDD or amount is < 0")
}
- await queryRunner.commitTransaction()
- logger.info(`commit Transaction successful...`)
+ const queryRunner = getConnection().createQueryRunner()
+ await queryRunner.connect()
+ await queryRunner.startTransaction('REPEATABLE READ')
+ logger.debug(`open Transaction to write...`)
+ try {
+ // transaction
+ const transactionSend = new dbTransaction()
+ transactionSend.typeId = TransactionTypeId.SEND
+ transactionSend.memo = memo
+ transactionSend.userId = sender.id
+ transactionSend.linkedUserId = recipient.id
+ transactionSend.amount = amount.mul(-1)
+ transactionSend.balance = sendBalance.balance
+ transactionSend.balanceDate = receivedCallDate
+ transactionSend.decay = sendBalance.decay.decay
+ transactionSend.decayStart = sendBalance.decay.start
+ transactionSend.previous = sendBalance.lastTransactionId
+ transactionSend.transactionLinkId = transactionLink ? transactionLink.id : null
+ await queryRunner.manager.insert(dbTransaction, transactionSend)
- const eventTransactionSend = new EventTransactionSend()
- eventTransactionSend.userId = transactionSend.userId
- eventTransactionSend.xUserId = transactionSend.linkedUserId
- eventTransactionSend.transactionId = transactionSend.id
- eventTransactionSend.amount = transactionSend.amount.mul(-1)
- await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
+ logger.debug(`sendTransaction inserted: ${dbTransaction}`)
- const eventTransactionReceive = new EventTransactionReceive()
- eventTransactionReceive.userId = transactionReceive.userId
- eventTransactionReceive.xUserId = transactionReceive.linkedUserId
- eventTransactionReceive.transactionId = transactionReceive.id
- eventTransactionReceive.amount = transactionReceive.amount
- await eventProtocol.writeEvent(new Event().setEventTransactionReceive(eventTransactionReceive))
- } catch (e) {
- await queryRunner.rollbackTransaction()
- logger.error(`Transaction was not successful: ${e}`)
- throw new Error(`Transaction was not successful: ${e}`)
- } finally {
- await queryRunner.release()
- }
- logger.debug(`prepare Email for transaction received...`)
- await sendTransactionReceivedEmail({
- firstName: recipient.firstName,
- lastName: recipient.lastName,
- email: recipient.emailContact.email,
- language: recipient.language,
- senderFirstName: sender.firstName,
- senderLastName: sender.lastName,
- senderEmail: sender.emailContact.email,
- transactionAmount: amount,
- })
- if (transactionLink) {
- await sendTransactionLinkRedeemedEmail({
- firstName: sender.firstName,
- lastName: sender.lastName,
- email: sender.emailContact.email,
- language: sender.language,
- senderFirstName: recipient.firstName,
- senderLastName: recipient.lastName,
- senderEmail: recipient.emailContact.email,
+ const transactionReceive = new dbTransaction()
+ transactionReceive.typeId = TransactionTypeId.RECEIVE
+ transactionReceive.memo = memo
+ transactionReceive.userId = recipient.id
+ transactionReceive.linkedUserId = sender.id
+ transactionReceive.amount = amount
+ const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate)
+ transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount
+ transactionReceive.balanceDate = receivedCallDate
+ transactionReceive.decay = receiveBalance ? receiveBalance.decay.decay : new Decimal(0)
+ transactionReceive.decayStart = receiveBalance ? receiveBalance.decay.start : null
+ transactionReceive.previous = receiveBalance ? receiveBalance.lastTransactionId : null
+ transactionReceive.linkedTransactionId = transactionSend.id
+ transactionReceive.transactionLinkId = transactionLink ? transactionLink.id : null
+ await queryRunner.manager.insert(dbTransaction, transactionReceive)
+ logger.debug(`receive Transaction inserted: ${dbTransaction}`)
+
+ // Save linked transaction id for send
+ transactionSend.linkedTransactionId = transactionReceive.id
+ await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
+ logger.debug(`send Transaction updated: ${transactionSend}`)
+
+ if (transactionLink) {
+ logger.info(`transactionLink: ${transactionLink}`)
+ transactionLink.redeemedAt = receivedCallDate
+ transactionLink.redeemedBy = recipient.id
+ await queryRunner.manager.update(
+ dbTransactionLink,
+ { id: transactionLink.id },
+ transactionLink,
+ )
+ }
+
+ await queryRunner.commitTransaction()
+ logger.info(`commit Transaction successful...`)
+
+ const eventTransactionSend = new EventTransactionSend()
+ eventTransactionSend.userId = transactionSend.userId
+ eventTransactionSend.xUserId = transactionSend.linkedUserId
+ eventTransactionSend.transactionId = transactionSend.id
+ eventTransactionSend.amount = transactionSend.amount.mul(-1)
+ await eventProtocol.writeEvent(new Event().setEventTransactionSend(eventTransactionSend))
+
+ const eventTransactionReceive = new EventTransactionReceive()
+ eventTransactionReceive.userId = transactionReceive.userId
+ eventTransactionReceive.xUserId = transactionReceive.linkedUserId
+ eventTransactionReceive.transactionId = transactionReceive.id
+ eventTransactionReceive.amount = transactionReceive.amount
+ await eventProtocol.writeEvent(
+ new Event().setEventTransactionReceive(eventTransactionReceive),
+ )
+ } catch (e) {
+ await queryRunner.rollbackTransaction()
+ logger.error(`Transaction was not successful: ${e}`)
+ throw new Error(`Transaction was not successful: ${e}`)
+ } finally {
+ await queryRunner.release()
+ }
+ logger.debug(`prepare Email for transaction received...`)
+ await sendTransactionReceivedEmail({
+ firstName: recipient.firstName,
+ lastName: recipient.lastName,
+ email: recipient.emailContact.email,
+ language: recipient.language,
+ senderFirstName: sender.firstName,
+ senderLastName: sender.lastName,
+ senderEmail: sender.emailContact.email,
transactionAmount: amount,
- transactionMemo: memo,
})
+ if (transactionLink) {
+ await sendTransactionLinkRedeemedEmail({
+ firstName: sender.firstName,
+ lastName: sender.lastName,
+ email: sender.emailContact.email,
+ language: sender.language,
+ senderFirstName: recipient.firstName,
+ senderLastName: recipient.lastName,
+ senderEmail: recipient.emailContact.email,
+ transactionAmount: amount,
+ transactionMemo: memo,
+ })
+ }
+ logger.info(`finished executeTransaction successfully`)
+ return true
+ } finally {
+ releaseLock()
}
- logger.info(`finished executeTransaction successfully`)
- return true
}
@Resolver()
@@ -315,10 +326,6 @@ export class TransactionResolver {
// TODO this is subject to replay attacks
const senderUser = getUser(context)
- if (senderUser.pubKey.length !== 32) {
- logger.error(`invalid sender public key:${senderUser.pubKey}`)
- throw new Error('invalid sender public key')
- }
// validate recipient user
const recipientUser = await findUserByEmail(email)
@@ -331,10 +338,6 @@ export class TransactionResolver {
logger.error(`The recipient account is not activated: recipientUser=${recipientUser}`)
throw new Error('The recipient account is not activated')
}
- if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) {
- logger.error(`invalid recipient public key: recipientUser=${recipientUser}`)
- throw new Error('invalid recipient public key')
- }
await executeTransaction(amount, memo, senderUser, recipientUser)
logger.info(
diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts
index 484aabca6..b70dd1522 100644
--- a/backend/src/graphql/resolver/UserResolver.test.ts
+++ b/backend/src/graphql/resolver/UserResolver.test.ts
@@ -138,12 +138,8 @@ describe('UserResolver', () => {
firstName: 'Peter',
lastName: 'Lustig',
password: '0',
- pubKey: null,
- privKey: null,
- // emailHash: expect.any(Buffer),
createdAt: expect.any(Date),
// emailChecked: false,
- passphrase: expect.any(String),
language: 'de',
isAdmin: null,
deletedAt: null,
diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts
index a1e70b019..4b4744b67 100644
--- a/backend/src/graphql/resolver/UserResolver.ts
+++ b/backend/src/graphql/resolver/UserResolver.ts
@@ -1,4 +1,3 @@
-import fs from 'fs'
import i18n from 'i18n'
import { v4 as uuidv4 } from 'uuid'
import {
@@ -60,8 +59,8 @@ import {
EventActivateAccount,
} from '@/event/Event'
import { getUserCreation, getUserCreations } from './util/creations'
+import { isValidPassword } from '@/password/EncryptorUtils'
import { FULL_CREATION_AVAILABLE } from './const/const'
-import { isValidPassword, SecretKeyCryptographyCreateKey } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { PasswordEncryptionType } from '../enum/PasswordEncryptionType'
@@ -76,79 +75,6 @@ const isLanguage = (language: string): boolean => {
return LANGUAGES.includes(language)
}
-const PHRASE_WORD_COUNT = 24
-const WORDS = fs
- .readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt')
- .toString()
- .split(',')
-const PassphraseGenerate = (): string[] => {
- logger.trace('PassphraseGenerate...')
- const result = []
- for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
- result.push(WORDS[sodium.randombytes_random() % 2048])
- }
- return result
-}
-
-const KeyPairEd25519Create = (passphrase: string[]): Buffer[] => {
- logger.trace('KeyPairEd25519Create...')
- if (!passphrase.length || passphrase.length < PHRASE_WORD_COUNT) {
- logger.error('passphrase empty or to short')
- throw new Error('passphrase empty or to short')
- }
-
- const state = Buffer.alloc(sodium.crypto_hash_sha512_STATEBYTES)
- sodium.crypto_hash_sha512_init(state)
-
- // To prevent breaking existing passphrase-hash combinations word indices will be put into 64 Bit Variable to mimic first implementation of algorithms
- for (let i = 0; i < PHRASE_WORD_COUNT; i++) {
- const value = Buffer.alloc(8)
- const wordIndex = WORDS.indexOf(passphrase[i])
- value.writeBigInt64LE(BigInt(wordIndex))
- sodium.crypto_hash_sha512_update(state, value)
- }
- // trailing space is part of the login_server implementation
- const clearPassphrase = passphrase.join(' ') + ' '
- sodium.crypto_hash_sha512_update(state, Buffer.from(clearPassphrase))
- const outputHashBuffer = Buffer.alloc(sodium.crypto_hash_sha512_BYTES)
- sodium.crypto_hash_sha512_final(state, outputHashBuffer)
-
- const pubKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES)
- const privKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES)
-
- sodium.crypto_sign_seed_keypair(
- pubKey,
- privKey,
- outputHashBuffer.slice(0, sodium.crypto_sign_SEEDBYTES),
- )
- logger.debug(`KeyPair creation ready. pubKey=${pubKey}`)
-
- return [pubKey, privKey]
-}
-
-const SecretKeyCryptographyEncrypt = (message: Buffer, encryptionKey: Buffer): Buffer => {
- logger.trace('SecretKeyCryptographyEncrypt...')
- const encrypted = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES)
- const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
- nonce.fill(31) // static nonce
-
- sodium.crypto_secretbox_easy(encrypted, message, nonce, encryptionKey)
- logger.debug(`SecretKeyCryptographyEncrypt...successful: ${encrypted}`)
- return encrypted
-}
-
-const SecretKeyCryptographyDecrypt = (encryptedMessage: Buffer, encryptionKey: Buffer): Buffer => {
- logger.trace('SecretKeyCryptographyDecrypt...')
- const message = Buffer.alloc(encryptedMessage.length - sodium.crypto_secretbox_MACBYTES)
- const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES)
- nonce.fill(31) // static nonce
-
- sodium.crypto_secretbox_open_easy(message, encryptedMessage, nonce, encryptionKey)
-
- logger.debug(`SecretKeyCryptographyDecrypt...successful: ${message}`)
- return message
-}
-
const newEmailContact = (email: string, userId: number): DbUserContact => {
logger.trace(`newEmailContact...`)
const emailContact = new DbUserContact()
@@ -191,7 +117,6 @@ export class UserResolver {
const clientTimezoneOffset = getClientTimezoneOffset(context)
const userEntity = getUser(context)
const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset))
- // user.pubkey = userEntity.pubKey.toString('hex')
// Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage(context)
@@ -223,11 +148,6 @@ export class UserResolver {
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
throw new Error('User has no password set yet')
}
- if (!dbUser.pubKey || !dbUser.privKey) {
- logger.error('The User has no private or publicKey.')
- // TODO we want to catch this on the frontend and ask the user to check his emails or resend code
- throw new Error('User has no private or publicKey')
- }
if (!verifyPassword(dbUser, password)) {
logger.error('The User has no valid credentials.')
@@ -259,7 +179,7 @@ export class UserResolver {
context.setHeaders.push({
key: 'token',
- value: encode(dbUser.pubKey),
+ value: encode(dbUser.gradidoID),
})
const ev = new EventLogin()
ev.userId = user.id
@@ -352,11 +272,6 @@ export class UserResolver {
}
}
- const passphrase = PassphraseGenerate()
- // const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
- // const passwordHash = SecretKeyCryptographyCreateKey(email, password) // return short and long hash
- // const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
- // const emailHash = getEmailHash(email)
const gradidoID = await newGradidoID()
const eventRegister = new EventRegister()
@@ -370,7 +285,6 @@ export class UserResolver {
dbUser.language = language
dbUser.publisherId = publisherId
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
- dbUser.passphrase = passphrase.join(' ')
logger.debug('new dbUser=' + dbUser)
if (redeemCode) {
if (redeemCode.match(/^CL-/)) {
@@ -391,12 +305,6 @@ export class UserResolver {
}
}
}
- // TODO this field has no null allowed unlike the loginServer table
- // dbUser.pubKey = Buffer.from(randomBytes(32)) // Buffer.alloc(32, 0) default to 0000...
- // dbUser.pubkey = keyPair[0]
- // loginUser.password = passwordHash[0].readBigUInt64LE() // using the shorthash
- // loginUser.pubKey = keyPair[0]
- // loginUser.privKey = encryptedPrivkey
const queryRunner = getConnection().createQueryRunner()
await queryRunner.connect()
@@ -575,34 +483,12 @@ export class UserResolver {
const user = userContact.user
logger.debug('user with EmailVerificationCode found...')
- // Generate Passphrase if needed
- if (!user.passphrase) {
- const passphrase = PassphraseGenerate()
- user.passphrase = passphrase.join(' ')
- logger.debug('new Passphrase generated...')
- }
-
- const passphrase = user.passphrase.split(' ')
- if (passphrase.length < PHRASE_WORD_COUNT) {
- logger.error('Could not load a correct passphrase')
- // TODO if this can happen we cannot recover from that
- // this seem to be good on production data, if we dont
- // make a coding mistake we do not have a problem here
- throw new Error('Could not load a correct passphrase')
- }
- logger.debug('Passphrase is valid...')
-
// Activate EMail
userContact.emailChecked = true
// Update Password
user.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
- const passwordHash = SecretKeyCryptographyCreateKey(userContact.email, password) // return short and long hash
- const keyPair = KeyPairEd25519Create(passphrase) // return pub, priv Key
- const encryptedPrivkey = SecretKeyCryptographyEncrypt(keyPair[1], passwordHash[1])
user.password = encryptPassword(user, password)
- user.pubKey = keyPair[0]
- user.privKey = encryptedPrivkey
logger.debug('User credentials updated ...')
const queryRunner = getConnection().createQueryRunner()
@@ -713,30 +599,14 @@ export class UserResolver {
)
}
- // TODO: This had some error cases defined - like missing private key. This is no longer checked.
- const oldPasswordHash = SecretKeyCryptographyCreateKey(
- userEntity.emailContact.email,
- password,
- )
if (!verifyPassword(userEntity, password)) {
logger.error(`Old password is invalid`)
throw new Error(`Old password is invalid`)
}
- const privKey = SecretKeyCryptographyDecrypt(userEntity.privKey, oldPasswordHash[1])
- logger.debug('oldPassword decrypted...')
- const newPasswordHash = SecretKeyCryptographyCreateKey(
- userEntity.emailContact.email,
- passwordNew,
- ) // return short and long hash
- logger.debug('newPasswordHash created...')
- const encryptedPrivkey = SecretKeyCryptographyEncrypt(privKey, newPasswordHash[1])
- logger.debug('PrivateKey encrypted...')
-
// Save new password hash and newly encrypted private key
userEntity.passwordEncryptionType = PasswordEncryptionType.GRADIDO_ID
userEntity.password = encryptPassword(userEntity, passwordNew)
- userEntity.privKey = encryptedPrivkey
}
const queryRunner = getConnection().createQueryRunner()
diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts
new file mode 100644
index 000000000..e334910f1
--- /dev/null
+++ b/backend/src/graphql/resolver/semaphore.test.ts
@@ -0,0 +1,190 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+import Decimal from 'decimal.js-light'
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { logger } from '@test/testSetup'
+import { userFactory } from '@/seeds/factory/user'
+import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
+import { bobBaumeister } from '@/seeds/users/bob-baumeister'
+import { peterLustig } from '@/seeds/users/peter-lustig'
+import { creationFactory, nMonthsBefore } from '@/seeds/factory/creation'
+import { cleanDB, testEnvironment, contributionDateFormatter } from '@test/helpers'
+import {
+ confirmContribution,
+ createContribution,
+ createTransactionLink,
+ redeemTransactionLink,
+ login,
+ createContributionLink,
+ sendCoins,
+} from '@/seeds/graphql/mutations'
+
+let mutate: any, con: any
+let testEnv: any
+
+beforeAll(async () => {
+ testEnv = await testEnvironment()
+ mutate = testEnv.mutate
+ con = testEnv.con
+ await cleanDB()
+})
+
+afterAll(async () => {
+ await cleanDB()
+ await con.close()
+})
+
+describe('semaphore', () => {
+ let contributionLinkCode = ''
+ let bobsTransactionLinkCode = ''
+ let bibisTransactionLinkCode = ''
+ let bibisOpenContributionId = -1
+ let bobsOpenContributionId = -1
+
+ beforeAll(async () => {
+ const now = new Date()
+ await userFactory(testEnv, bibiBloxberg)
+ await userFactory(testEnv, peterLustig)
+ await userFactory(testEnv, bobBaumeister)
+ await creationFactory(testEnv, {
+ email: 'bibi@bloxberg.de',
+ amount: 1000,
+ memo: 'Herzlich Willkommen bei Gradido!',
+ creationDate: nMonthsBefore(new Date()),
+ confirmed: true,
+ })
+ await creationFactory(testEnv, {
+ email: 'bob@baumeister.de',
+ amount: 1000,
+ memo: 'Herzlich Willkommen bei Gradido!',
+ creationDate: nMonthsBefore(new Date()),
+ confirmed: true,
+ })
+ await mutate({
+ mutation: login,
+ variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
+ })
+ const {
+ data: { createContributionLink: contributionLink },
+ } = await mutate({
+ mutation: createContributionLink,
+ variables: {
+ amount: new Decimal(200),
+ name: 'Test Contribution Link',
+ memo: 'Danke fĂĽr deine Teilnahme an dem Test der Contribution Links',
+ cycle: 'ONCE',
+ validFrom: new Date(2022, 5, 18).toISOString(),
+ validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
+ maxAmountPerMonth: new Decimal(200),
+ maxPerCycle: 1,
+ },
+ })
+ contributionLinkCode = 'CL-' + contributionLink.code
+ await mutate({
+ mutation: login,
+ variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
+ })
+ const {
+ data: { createTransactionLink: bobsLink },
+ } = await mutate({
+ mutation: createTransactionLink,
+ variables: {
+ email: 'bob@baumeister.de',
+ amount: 20,
+ memo: 'Bobs Link',
+ },
+ })
+ const {
+ data: { createContribution: bobsContribution },
+ } = await mutate({
+ mutation: createContribution,
+ variables: {
+ creationDate: contributionDateFormatter(new Date()),
+ amount: 200,
+ memo: 'Bobs Contribution',
+ },
+ })
+ await mutate({
+ mutation: login,
+ variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
+ })
+ const {
+ data: { createTransactionLink: bibisLink },
+ } = await mutate({
+ mutation: createTransactionLink,
+ variables: {
+ amount: 20,
+ memo: 'Bibis Link',
+ },
+ })
+ const {
+ data: { createContribution: bibisContribution },
+ } = await mutate({
+ mutation: createContribution,
+ variables: {
+ creationDate: contributionDateFormatter(new Date()),
+ amount: 200,
+ memo: 'Bibis Contribution',
+ },
+ })
+ bobsTransactionLinkCode = bobsLink.code
+ bibisTransactionLinkCode = bibisLink.code
+ bibisOpenContributionId = bibisContribution.id
+ bobsOpenContributionId = bobsContribution.id
+ })
+
+ it('creates a lot of transactions without errors', async () => {
+ await mutate({
+ mutation: login,
+ variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
+ })
+ const bibiRedeemContributionLink = mutate({
+ mutation: redeemTransactionLink,
+ variables: { code: contributionLinkCode },
+ })
+ const redeemBobsLink = mutate({
+ mutation: redeemTransactionLink,
+ variables: { code: bobsTransactionLinkCode },
+ })
+ const bibisTransaction = mutate({
+ mutation: sendCoins,
+ variables: { email: 'bob@baumeister.de', amount: '50', memo: 'Das ist fĂĽr dich, Bob' },
+ })
+ await mutate({
+ mutation: login,
+ variables: { email: 'bob@baumeister.de', password: 'Aa12345_' },
+ })
+ const bobRedeemContributionLink = mutate({
+ mutation: redeemTransactionLink,
+ variables: { code: contributionLinkCode },
+ })
+ const redeemBibisLink = mutate({
+ mutation: redeemTransactionLink,
+ variables: { code: bibisTransactionLinkCode },
+ })
+ const bobsTransaction = mutate({
+ mutation: sendCoins,
+ variables: { email: 'bibi@bloxberg.de', amount: '50', memo: 'Das ist fĂĽr dich, Bibi' },
+ })
+ await mutate({
+ mutation: login,
+ variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
+ })
+ const confirmBibisContribution = mutate({
+ mutation: confirmContribution,
+ variables: { id: bibisOpenContributionId },
+ })
+ const confirmBobsContribution = mutate({
+ mutation: confirmContribution,
+ variables: { id: bobsOpenContributionId },
+ })
+ await expect(bibiRedeemContributionLink).resolves.toMatchObject({ errors: undefined })
+ await expect(redeemBobsLink).resolves.toMatchObject({ errors: undefined })
+ await expect(bibisTransaction).resolves.toMatchObject({ errors: undefined })
+ await expect(bobRedeemContributionLink).resolves.toMatchObject({ errors: undefined })
+ await expect(redeemBibisLink).resolves.toMatchObject({ errors: undefined })
+ await expect(bobsTransaction).resolves.toMatchObject({ errors: undefined })
+ await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined })
+ await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined })
+ })
+})
diff --git a/backend/src/locales/de.json b/backend/src/locales/de.json
index 8aff6caa4..2e6796794 100644
--- a/backend/src/locales/de.json
+++ b/backend/src/locales/de.json
@@ -6,7 +6,7 @@
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
},
"accountActivation": {
- "duration": "Der Link hat eine GĂĽltigkeit von {hours} Stunden und {minutes} Minuten. Sollte die GĂĽltigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:",
+ "duration": "Der Link hat eine GĂĽltigkeit von {hours} Stunden und {minutes} Minuten. Sollte die GĂĽltigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
"pleaseClickLink": "Klicke bitte auf diesen Link, um die Registrierung abzuschlieĂźen und dein Gradido-Konto zu aktivieren:",
"subject": "Gradido: E-Mail ĂśberprĂĽfung"
@@ -14,7 +14,7 @@
"accountMultiRegistration": {
"emailExists": "Es existiert jedoch zu deiner E-Mail-Adresse schon ein Konto.",
"emailReused": "deine E-Mail-Adresse wurde soeben erneut benutzt, um bei Gradido ein Konto zu registrieren.",
- "ifYouAreNotTheOne": "Wenn du nicht derjenige bist, der sich versucht hat erneut zu registrieren, wende dich bitte an unseren support:",
+ "ifYouAreNotTheOne": "Wenn du nicht derjenige bist, der versucht hat sich erneut zu registrieren, wende dich bitte an unseren Support:",
"onForgottenPasswordClickLink": "Klicke bitte auf den folgenden Link, falls du dein Passwort vergessen haben solltest:",
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
@@ -40,22 +40,25 @@
"yourGradidoTeam": "dein Gradido-Team"
},
"resetPassword": {
- "duration": "Der Link hat eine GĂĽltigkeit von {hours} Stunden und {minutes} Minuten. Sollte die GĂĽltigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen, in dem du deine E-Mail-Adresse eingibst:",
+ "duration": "Der Link hat eine GĂĽltigkeit von {hours} Stunden und {minutes} Minuten. Sollte die GĂĽltigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
"pleaseClickLink": "Wenn du es warst, klicke bitte auf den Link:",
"subject": "Gradido: Passwort zurĂĽcksetzen",
"youOrSomeoneResetPassword": "du, oder jemand anderes, hast fĂĽr dieses Konto ein ZurĂĽcksetzen des Passworts angefordert."
},
"transactionLinkRedeemed": {
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) hat soeben deinen Link eingelöst.",
- "memo": "Memo: {transactionMemo}",
+ "memo": "Nachricht: {transactionMemo}",
"subject": "Gradido: Dein Gradido-Link wurde eingelöst"
},
"transactionReceived": {
"haveReceivedAmountGDDFrom": "du hast soeben {transactionAmount} GDD von {senderFirstName} {senderLastName} ({senderEmail}) erhalten.",
- "subject": "Gradido: Du hast Gradidos erhalten"
+ "subject": "Gradido: {senderFirstName} {senderLastName} hat dir {transactionAmount} Gradido gesendet"
}
},
"general": {
- "decimalSeparator": ","
+ "decimalSeparator": ",",
+ "imprint": "Gradido-Akademie\nInstitut fĂĽr Wirtschaftsbionik\nPfarrweg 2\n74653 KĂĽnzelsau\nDeutschland",
+ "imprintImageAlt": "Gradido-Akademie Logo",
+ "imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
}
}
diff --git a/backend/src/locales/en.json b/backend/src/locales/en.json
index 99217840e..3b25f8ae4 100644
--- a/backend/src/locales/en.json
+++ b/backend/src/locales/en.json
@@ -6,7 +6,7 @@
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
},
"accountActivation": {
- "duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:",
+ "duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
"emailRegistered": "Your email address has just been registered with Gradido.",
"pleaseClickLink": "Please click on this link to complete the registration and activate your Gradido account:",
"subject": "Gradido: Email Verification"
@@ -40,22 +40,25 @@
"yourGradidoTeam": "your Gradido team"
},
"resetPassword": {
- "duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here by entering your email address:",
+ "duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
"pleaseClickLink": "If it was you, please click on the link:",
"subject": "Gradido: Reset password",
"youOrSomeoneResetPassword": "You, or someone else, requested a password reset for this account."
},
"transactionLinkRedeemed": {
"hasRedeemedYourLink": "{senderFirstName} {senderLastName} ({senderEmail}) has just redeemed your link.",
- "memo": "Memo: {transactionMemo}",
+ "memo": "Message: {transactionMemo}",
"subject": "Gradido: Your Gradido link has been redeemed"
},
"transactionReceived": {
"haveReceivedAmountGDDFrom": "You have just received {transactionAmount} GDD from {senderFirstName} {senderLastName} ({senderEmail}).",
- "subject": "Gradido: You have received Gradidos"
+ "subject": "Gradido: {senderFirstName} {senderLastName} has sent you {transactionAmount} Gradido"
}
},
"general": {
- "decimalSeparator": "."
+ "decimalSeparator": ".",
+ "imprint": "Gradido-Akademie\nInstitut fĂĽr Wirtschaftsbionik\nPfarrweg 2\n74653 KĂĽnzelsau\nDeutschland",
+ "imprintImageAlt": "Gradido-Akademie Logo",
+ "imprintImageURL": "https://gdd.gradido.net/img/brand/green.png"
}
}
diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts
index 3675d381d..9e1939db8 100644
--- a/backend/src/seeds/index.ts
+++ b/backend/src/seeds/index.ts
@@ -75,10 +75,7 @@ const run = async () => {
// create GDD
for (let i = 0; i < creations.length; i++) {
- const now = new Date().getTime() // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886)
await creationFactory(seedClient, creations[i])
- // eslint-disable-next-line no-empty
- while (new Date().getTime() < now + 1000) {} // we have to wait a little! quick fix for account sum problem of bob@baumeister.de, (see https://github.com/gradido/gradido/issues/1886)
}
logger.info('##seed## seeding all creations successful...')
diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts
index c20ef85ff..4972aa9c4 100644
--- a/backend/src/typeorm/repository/User.ts
+++ b/backend/src/typeorm/repository/User.ts
@@ -4,21 +4,6 @@ import { User as DbUser } from '@entity/User'
@EntityRepository(DbUser)
export class UserRepository extends Repository {
- async findByPubkeyHex(pubkeyHex: string): Promise {
- const dbUser = await this.createQueryBuilder('user')
- .leftJoinAndSelect('user.emailContact', 'emailContact')
- .where('hex(user.pubKey) = :pubkeyHex', { pubkeyHex })
- .getOneOrFail()
- /*
- const dbUser = await this.findOneOrFail(`hex(user.pubKey) = { pubkeyHex }`)
- const emailContact = await this.query(
- `SELECT * from user_contacts where id = { dbUser.emailId }`,
- )
- dbUser.emailContact = emailContact
- */
- return dbUser
- }
-
async findBySearchCriteriaPagedFiltered(
select: string[],
searchCriteria: string,
diff --git a/backend/src/util/TRANSACTIONS_LOCK.ts b/backend/src/util/TRANSACTIONS_LOCK.ts
new file mode 100644
index 000000000..847386e4d
--- /dev/null
+++ b/backend/src/util/TRANSACTIONS_LOCK.ts
@@ -0,0 +1,4 @@
+import { Semaphore } from 'await-semaphore'
+
+const CONCURRENT_TRANSACTIONS = 1
+export const TRANSACTIONS_LOCK = new Semaphore(CONCURRENT_TRANSACTIONS)
diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts
index 298348f0f..98279db15 100644
--- a/backend/src/util/communityUser.ts
+++ b/backend/src/util/communityUser.ts
@@ -16,8 +16,6 @@ const communityDbUser: dbUser = {
emailId: -1,
firstName: 'Gradido',
lastName: 'Akademie',
- pubKey: Buffer.from(''),
- privKey: Buffer.from(''),
deletedAt: null,
password: BigInt(0),
// emailHash: Buffer.from(''),
@@ -26,7 +24,6 @@ const communityDbUser: dbUser = {
language: '',
isAdmin: null,
publisherId: 0,
- passphrase: '',
// default password encryption type
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,
hasId: function (): boolean {
diff --git a/backend/src/util/validate.ts b/backend/src/util/validate.ts
index edd8d55f6..397a38730 100644
--- a/backend/src/util/validate.ts
+++ b/backend/src/util/validate.ts
@@ -14,17 +14,13 @@ function isStringBoolean(value: string): boolean {
return false
}
-function isHexPublicKey(publicKey: string): boolean {
- return /^[0-9A-Fa-f]{64}$/i.test(publicKey)
-}
-
async function calculateBalance(
userId: number,
amount: Decimal,
time: Date,
transactionLink?: dbTransactionLink | null,
): Promise<{ balance: Decimal; decay: Decay; lastTransactionId: number } | null> {
- const lastTransaction = await Transaction.findOne({ userId }, { order: { balanceDate: 'DESC' } })
+ const lastTransaction = await Transaction.findOne({ userId }, { order: { id: 'DESC' } })
if (!lastTransaction) return null
const decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, time)
@@ -45,4 +41,4 @@ async function calculateBalance(
return { balance, lastTransactionId: lastTransaction.id, decay }
}
-export { isHexPublicKey, calculateBalance, isStringBoolean }
+export { calculateBalance, isStringBoolean }
diff --git a/backend/yarn.lock b/backend/yarn.lock
index 940906cfa..82bcd6b1f 100644
--- a/backend/yarn.lock
+++ b/backend/yarn.lock
@@ -1643,6 +1643,11 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+await-semaphore@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/await-semaphore/-/await-semaphore-0.1.3.tgz#2b88018cc8c28e06167ae1cdff02504f1f9688d3"
+ integrity sha512-d1W2aNSYcz/sxYO4pMGX9vq65qOTu0P800epMud+6cYYX0QcT7zyqcxec3VWzpgvdXo57UWmVbZpLMjX2m1I7Q==
+
axios@^0.21.1:
version "0.21.4"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
diff --git a/database/entity/0057-clear_old_password_junk/User.ts b/database/entity/0057-clear_old_password_junk/User.ts
new file mode 100644
index 000000000..c511a98c8
--- /dev/null
+++ b/database/entity/0057-clear_old_password_junk/User.ts
@@ -0,0 +1,112 @@
+import {
+ BaseEntity,
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ DeleteDateColumn,
+ OneToMany,
+ JoinColumn,
+ OneToOne,
+} from 'typeorm'
+import { Contribution } from '../Contribution'
+import { ContributionMessage } from '../ContributionMessage'
+import { UserContact } from '../UserContact'
+
+@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
+export class User extends BaseEntity {
+ @PrimaryGeneratedColumn('increment', { unsigned: true })
+ id: number
+
+ @Column({
+ name: 'gradido_id',
+ length: 36,
+ nullable: false,
+ collation: 'utf8mb4_unicode_ci',
+ })
+ gradidoID: string
+
+ @Column({
+ name: 'alias',
+ length: 20,
+ nullable: true,
+ default: null,
+ collation: 'utf8mb4_unicode_ci',
+ })
+ alias: string
+
+ @OneToOne(() => UserContact, (emailContact: UserContact) => emailContact.user)
+ @JoinColumn({ name: 'email_id' })
+ emailContact: UserContact
+
+ @Column({ name: 'email_id', type: 'int', unsigned: true, nullable: true, default: null })
+ emailId: number | null
+
+ @Column({
+ name: 'first_name',
+ length: 255,
+ nullable: true,
+ default: null,
+ collation: 'utf8mb4_unicode_ci',
+ })
+ firstName: string
+
+ @Column({
+ name: 'last_name',
+ length: 255,
+ nullable: true,
+ default: null,
+ collation: 'utf8mb4_unicode_ci',
+ })
+ lastName: string
+
+ @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
+ createdAt: Date
+
+ @DeleteDateColumn({ name: 'deleted_at', nullable: true })
+ deletedAt: Date | null
+
+ @Column({ type: 'bigint', default: 0, unsigned: true })
+ password: BigInt
+
+ @Column({
+ name: 'password_encryption_type',
+ type: 'int',
+ unsigned: true,
+ nullable: false,
+ default: 0,
+ })
+ passwordEncryptionType: number
+
+ @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false })
+ language: string
+
+ @Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null })
+ isAdmin: Date | null
+
+ @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
+ referrerId?: number | null
+
+ @Column({
+ name: 'contribution_link_id',
+ type: 'int',
+ unsigned: true,
+ nullable: true,
+ default: null,
+ })
+ contributionLinkId?: number | null
+
+ @Column({ name: 'publisher_id', default: 0 })
+ publisherId: number
+
+ @OneToMany(() => Contribution, (contribution) => contribution.user)
+ @JoinColumn({ name: 'user_id' })
+ contributions?: Contribution[]
+
+ @OneToMany(() => ContributionMessage, (message) => message.user)
+ @JoinColumn({ name: 'user_id' })
+ messages?: ContributionMessage[]
+
+ @OneToMany(() => UserContact, (userContact: UserContact) => userContact.user)
+ @JoinColumn({ name: 'user_id' })
+ userContacts?: UserContact[]
+}
diff --git a/database/entity/0057-clear_old_password_junk/UserContact.ts b/database/entity/0057-clear_old_password_junk/UserContact.ts
new file mode 100644
index 000000000..c101fba4c
--- /dev/null
+++ b/database/entity/0057-clear_old_password_junk/UserContact.ts
@@ -0,0 +1,57 @@
+import {
+ BaseEntity,
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ DeleteDateColumn,
+ OneToOne,
+} from 'typeorm'
+import { User } from './User'
+
+@Entity('user_contacts', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' })
+export class UserContact extends BaseEntity {
+ @PrimaryGeneratedColumn('increment', { unsigned: true })
+ id: number
+
+ @Column({
+ name: 'type',
+ length: 100,
+ nullable: true,
+ default: null,
+ collation: 'utf8mb4_unicode_ci',
+ })
+ type: string
+
+ @OneToOne(() => User, (user) => user.emailContact)
+ user: User
+
+ @Column({ name: 'user_id', type: 'int', unsigned: true, nullable: false })
+ userId: number
+
+ @Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' })
+ email: string
+
+ @Column({ name: 'email_verification_code', type: 'bigint', unsigned: true, unique: true })
+ emailVerificationCode: BigInt
+
+ @Column({ name: 'email_opt_in_type_id' })
+ emailOptInTypeId: number
+
+ @Column({ name: 'email_resend_count' })
+ emailResendCount: number
+
+ @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false })
+ emailChecked: boolean
+
+ @Column({ length: 255, unique: false, nullable: true, collation: 'utf8mb4_unicode_ci' })
+ phone: string
+
+ @Column({ name: 'created_at', default: () => 'CURRENT_TIMESTAMP', nullable: false })
+ createdAt: Date
+
+ @Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' })
+ updatedAt: Date | null
+
+ @DeleteDateColumn({ name: 'deleted_at', nullable: true })
+ deletedAt: Date | null
+}
diff --git a/database/entity/0056-add_communities_table/Community.ts b/database/entity/0058-add_communities_table/Community.ts
similarity index 100%
rename from database/entity/0056-add_communities_table/Community.ts
rename to database/entity/0058-add_communities_table/Community.ts
diff --git a/database/entity/Community.ts b/database/entity/Community.ts
index 0faab133f..457d03eae 100644
--- a/database/entity/Community.ts
+++ b/database/entity/Community.ts
@@ -1 +1 @@
-export { Community } from './0056-add_communities_table/Community'
+export { Community } from './0058-add_communities_table/Community'
diff --git a/database/entity/User.ts b/database/entity/User.ts
index b3c00a9b4..5cffc688e 100644
--- a/database/entity/User.ts
+++ b/database/entity/User.ts
@@ -1 +1 @@
-export { User } from './0053-change_password_encryption/User'
+export { User } from './0057-clear_old_password_junk/User'
diff --git a/database/entity/UserContact.ts b/database/entity/UserContact.ts
index dd74e65c4..17d4575b0 100644
--- a/database/entity/UserContact.ts
+++ b/database/entity/UserContact.ts
@@ -1 +1 @@
-export { UserContact } from './0053-change_password_encryption/UserContact'
+export { UserContact } from './0057-clear_old_password_junk/UserContact'
diff --git a/database/migrations/0056-consistent_transactions_table.ts b/database/migrations/0056-consistent_transactions_table.ts
new file mode 100644
index 000000000..02ed3b7be
--- /dev/null
+++ b/database/migrations/0056-consistent_transactions_table.ts
@@ -0,0 +1,39 @@
+/* MIGRATION TO add users that have a transaction but do not exist */
+
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { v4 as uuidv4 } from 'uuid'
+import { OkPacket } from 'mysql'
+
+export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) {
+ const missingUserIds = await queryFn(`
+ SELECT user_id FROM transactions
+ WHERE NOT EXISTS (SELECT id FROM users WHERE id = user_id) GROUP BY user_id;`)
+
+ for (let i = 0; i < missingUserIds.length; i++) {
+ let gradidoId = ''
+ let countIds: any[] = []
+ do {
+ gradidoId = uuidv4()
+ countIds = await queryFn(
+ `SELECT COUNT(*) FROM \`users\` WHERE \`gradido_id\` = "${gradidoId}"`,
+ )
+ } while (countIds[0] > 0)
+
+ const userContact = (await queryFn(`
+ INSERT INTO user_contacts
+ (type, user_id, email, email_checked, created_at, deleted_at)
+ VALUES
+ ('EMAIL', ${missingUserIds[i].user_id}, 'deleted.user${missingUserIds[i].user_id}@gradido.net', 0, NOW(), NOW());`)) as unknown as OkPacket
+
+ await queryFn(`
+ INSERT INTO users
+ (id, gradido_id, email_id, first_name, last_name, deleted_at, password_encryption_type, created_at, language)
+ VALUES
+ (${missingUserIds[i].user_id}, '${gradidoId}', ${userContact.insertId}, 'DELETED', 'USER', NOW(), 0, NOW(), 'de');`)
+ }
+}
+
+/* eslint-disable @typescript-eslint/no-empty-function */
+/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
+export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) {}
diff --git a/database/migrations/0057-clear_old_password_junk.ts b/database/migrations/0057-clear_old_password_junk.ts
new file mode 100644
index 000000000..3e6f3f76a
--- /dev/null
+++ b/database/migrations/0057-clear_old_password_junk.ts
@@ -0,0 +1,16 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) {
+ await queryFn('ALTER TABLE users DROP COLUMN public_key;')
+ await queryFn('ALTER TABLE users DROP COLUMN privkey;')
+ await queryFn('ALTER TABLE users DROP COLUMN email_hash;')
+ await queryFn('ALTER TABLE users DROP COLUMN passphrase;')
+}
+
+export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) {
+ await queryFn('ALTER TABLE users ADD COLUMN public_key binary(32) DEFAULT NULL;')
+ await queryFn('ALTER TABLE users ADD COLUMN privkey binary(80) DEFAULT NULL;')
+ await queryFn('ALTER TABLE users ADD COLUMN email_hash binary(32) DEFAULT NULL;')
+ await queryFn('ALTER TABLE users ADD COLUMN passphrase text DEFAULT NULL;')
+}
diff --git a/database/migrations/0056-add_communities_table.ts b/database/migrations/0058-add_communities_table.ts
similarity index 100%
rename from database/migrations/0056-add_communities_table.ts
rename to database/migrations/0058-add_communities_table.ts
diff --git a/database/package.json b/database/package.json
index abc7789c4..0c69941b4 100644
--- a/database/package.json
+++ b/database/package.json
@@ -1,6 +1,6 @@
{
"name": "gradido-database",
- "version": "1.15.0",
+ "version": "1.16.0",
"description": "Gradido Database Tool to execute database migrations",
"main": "src/index.ts",
"repository": "https://github.com/gradido/gradido/database",
diff --git a/deployment/bare_metal/.env.dist b/deployment/bare_metal/.env.dist
index 9c6bfd735..a5f6996d7 100644
--- a/deployment/bare_metal/.env.dist
+++ b/deployment/bare_metal/.env.dist
@@ -24,9 +24,10 @@ COMMUNITY_REGISTER_URL=https://stage1.gradido.net/register
COMMUNITY_REDEEM_URL=https://stage1.gradido.net/redeem/{code}
COMMUNITY_REDEEM_CONTRIBUTION_URL=https://stage1.gradido.net/redeem/CL-{code}
COMMUNITY_DESCRIPTION="Gradido Development Stage1 Test Community"
+COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# backend
-BACKEND_CONFIG_VERSION=v12.2022-11-10
+BACKEND_CONFIG_VERSION=v13.2022-12-20
JWT_EXPIRES_IN=10m
GDT_API_URL=https://gdt.gradido.net
@@ -69,7 +70,7 @@ EVENT_PROTOCOL_DISABLED=false
DATABASE_CONFIG_VERSION=v1.2022-03-18
# frontend
-FRONTEND_CONFIG_VERSION=v3.2022-09-16
+FRONTEND_CONFIG_VERSION=v4.2022-12-20
GRAPHQL_URI=https://stage1.gradido.net/graphql
ADMIN_AUTH_URL=https://stage1.gradido.net/admin/authenticate?token={token}
@@ -85,8 +86,6 @@ META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natü
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
-SUPPORT_MAIL=support@supportmail.com
-
# admin
ADMIN_CONFIG_VERSION=v1.2022-03-18
diff --git a/frontend/.env.dist b/frontend/.env.dist
index 4127f339a..5ce6b430d 100644
--- a/frontend/.env.dist
+++ b/frontend/.env.dist
@@ -1,4 +1,4 @@
-CONFIG_VERSION=v3.2022-09-16
+CONFIG_VERSION=v4.2022-12-20
# Environment
DEFAULT_PUBLISHER_ID=2896
@@ -12,6 +12,7 @@ COMMUNITY_NAME=Gradido Entwicklung
COMMUNITY_URL=http://localhost/
COMMUNITY_REGISTER_URL=http://localhost/register
COMMUNITY_DESCRIPTION=Die lokale Entwicklungsumgebung von Gradido.
+COMMUNITY_SUPPORT_MAIL=support@supportmail.com
# Meta
META_URL=http://localhost
@@ -22,6 +23,3 @@ META_DESCRIPTION_EN="Gratitude is the currency of the new age. More and more peo
META_KEYWORDS_DE="Grundeinkommen, Währung, Dankbarkeit, Schenk-Ökonomie, Natürliche Ökonomie des Lebens, Ökonomie, Ökologie, Potenzialentfaltung, Schenken und Danken, Kreislauf des Lebens, Geldsystem"
META_KEYWORDS_EN="Basic Income, Currency, Gratitude, Gift Economy, Natural Economy of Life, Economy, Ecology, Potential Development, Giving and Thanking, Cycle of Life, Monetary System"
META_AUTHOR="Bernd Hückstädt - Gradido-Akademie"
-
-# Support Mail
-SUPPORT_MAIL=support@supportmail.com
\ No newline at end of file
diff --git a/frontend/.env.template b/frontend/.env.template
index 0b9d34b38..59e34eb80 100644
--- a/frontend/.env.template
+++ b/frontend/.env.template
@@ -12,6 +12,7 @@ COMMUNITY_NAME=$COMMUNITY_NAME
COMMUNITY_URL=$COMMUNITY_URL
COMMUNITY_REGISTER_URL=$COMMUNITY_REGISTER_URL
COMMUNITY_DESCRIPTION=$COMMUNITY_DESCRIPTION
+COMMUNITY_SUPPORT_MAIL=$COMMUNITY_SUPPORT_MAIL
# Meta
META_URL=$META_URL
@@ -22,6 +23,3 @@ META_DESCRIPTION_EN=$META_DESCRIPTION_EN
META_KEYWORDS_DE=$META_KEYWORDS_DE
META_KEYWORDS_EN=$META_KEYWORDS_EN
META_AUTHOR=$META_AUTHOR
-
-# Support Mail
-SUPPORT_MAIL=$SUPPORT_MAIL
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
index 6f1474521..35c5437f4 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "bootstrap-vue-gradido-wallet",
- "version": "1.15.0",
+ "version": "1.16.0",
"private": true,
"scripts": {
"start": "node run/server.js",
diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js
index 5ab5f2392..b90376672 100644
--- a/frontend/src/config/index.js
+++ b/frontend/src/config/index.js
@@ -8,7 +8,7 @@ const constants = {
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
- EXPECTED: 'v3.2022-09-16',
+ EXPECTED: 'v4.2022-12-20',
CURRENT: '',
},
}
@@ -39,6 +39,7 @@ const community = {
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register',
COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.',
+ COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL || 'support@supportmail.com',
}
const meta = {
@@ -60,10 +61,6 @@ const meta = {
META_AUTHOR: process.env.META_AUTHOR || 'Bernd Hückstädt - Gradido-Akademie',
}
-const supportmail = {
- SUPPORT_MAIL: process.env.SUPPORT_MAIL || 'support@supportmail.com',
-}
-
// Check config version
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
if (
@@ -83,7 +80,6 @@ const CONFIG = {
...endpoints,
...community,
...meta,
- ...supportmail,
}
module.exports = CONFIG
diff --git a/frontend/src/pages/InfoStatistic.vue b/frontend/src/pages/InfoStatistic.vue
index 254a895e0..978c7fde5 100644
--- a/frontend/src/pages/InfoStatistic.vue
+++ b/frontend/src/pages/InfoStatistic.vue
@@ -89,7 +89,7 @@ export default {
countAdminUser: null,
itemsContributionLinks: [],
itemsAdminUser: [],
- supportMail: CONFIG.SUPPORT_MAIL,
+ supportMail: CONFIG.COMMUNITY_SUPPORT_MAIL,
membersCount: '1203',
totalUsers: null,
totalGradidoCreated: null,
diff --git a/package.json b/package.json
index 22f444155..a4bedfdf2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gradido",
- "version": "1.15.0",
+ "version": "1.16.0",
"description": "Gradido",
"main": "index.js",
"repository": "git@github.com:gradido/gradido.git",