Merge branch 'upgrade_logging' into refactor_shared_module

This commit is contained in:
einhornimmond 2025-06-18 15:10:47 +02:00
commit 9f22e54957
349 changed files with 3145 additions and 2533 deletions

View File

@ -21,7 +21,7 @@ jobs:
list-files: shell
build:
name: typecheck - Config-Schema
name: Unit Tests, typecheck - Config-Schema
if: needs.files-changed.outputs.config == 'true' || needs.files-changed.outputs.docker-compose == 'true'
needs: files-changed
runs-on: ubuntu-latest
@ -38,3 +38,6 @@ jobs:
- name: typecheck
run: cd config-schema && yarn typecheck
- name: unit tests
run: cd config-schema && yarn test

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.bak
.turbo
vite.config.mjs.timestamp-*
log4js-config*.json
/node_modules/*
messages.pot
nbproject

91
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,91 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "DHT-Node Debug Tests",
"runtimeExecutable": "yarn",
"runtimeArgs": [
"run",
"test:debug"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}/dht-node"
},
{
"type": "node",
"request": "launch",
"name": "DHT-Node Debug",
"stopOnEntry": true,
"runtimeExecutable": "yarn",
"runtimeArgs": [
"run",
"dev"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "openOnSessionStart",
"cwd": "${workspaceFolder}/dht-node"
},
{
"type": "node",
"request": "launch",
"name": "Federation Debug Tests",
"runtimeExecutable": "yarn",
"runtimeArgs": [
"run",
"test:debug"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}/federation"
},
{
"type": "node",
"request": "launch",
"name": "Federation Debug",
"stopOnEntry": true,
"runtimeExecutable": "yarn",
"runtimeArgs": [
"run",
"dev"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "openOnSessionStart",
"cwd": "${workspaceFolder}/federation"
},
{
"type": "node",
"request": "launch",
"name": "Backend Debug",
"stopOnEntry": true,
"runtimeExecutable": "yarn",
"runtimeArgs": [
"run",
"dev"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "openOnSessionStart",
"cwd": "${workspaceFolder}/backend"
}
]
}

View File

@ -4,8 +4,26 @@ 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).
#### [2.5.2](https://github.com/gradido/gradido/compare/2.3.1...2.5.2)
#### [2.6.0](https://github.com/gradido/gradido/compare/2.3.1...2.6.0)
- fix(frontend): fix contribution link [`#3500`](https://github.com/gradido/gradido/pull/3500)
- feat(other): disable index html caching, reenable limits [`#3497`](https://github.com/gradido/gradido/pull/3497)
- fix(admin): fix accidently remove user states [`#3496`](https://github.com/gradido/gradido/pull/3496)
- feat(frontend): use grass as faster alternative to sass [`#3491`](https://github.com/gradido/gradido/pull/3491)
- refactor(database): ms precision and git compatible entity versioning [`#3495`](https://github.com/gradido/gradido/pull/3495)
- refactor(frontend): rename community page and child components to contributions [`#3494`](https://github.com/gradido/gradido/pull/3494)
- feat(frontend): change experimental to beta [`#3493`](https://github.com/gradido/gradido/pull/3493)
- fix(workflow): wrong database docker call [`#3490`](https://github.com/gradido/gradido/pull/3490)
- feat(frontend): disable cross community tx redeem in frontend [`#3486`](https://github.com/gradido/gradido/pull/3486)
- feat(other): split start script, put additional installs in extra script [`#3488`](https://github.com/gradido/gradido/pull/3488)
- fix(other): correct bun install script [`#3487`](https://github.com/gradido/gradido/pull/3487)
- refactor(frontend): contribution page and some graphql code in backend used by the page [`#3483`](https://github.com/gradido/gradido/pull/3483)
- refactor(other): use esbuild instead of tsc [`#3479`](https://github.com/gradido/gradido/pull/3479)
- refactor(other): update to yarn workspaces and turbo [`#3478`](https://github.com/gradido/gradido/pull/3478)
- feat(workflow): x-cross tx per link [`#3467`](https://github.com/gradido/gradido/pull/3467)
- fix(backend): fix problem with humhub, changing from and to alias [`#3484`](https://github.com/gradido/gradido/pull/3484)
- feat(workflow): stop deploy script on error, always cleanup lock file [`#3482`](https://github.com/gradido/gradido/pull/3482)
- chore(release): v2.5.2 beta [`#3480`](https://github.com/gradido/gradido/pull/3480)
- refactor(other): use biome instead of eslint [`#3472`](https://github.com/gradido/gradido/pull/3472)
- fix(frontend): set explicit page size for admin and moderator user on information page [`#3474`](https://github.com/gradido/gradido/pull/3474)
- fix(backend): humhub sync on edge cases [`#3471`](https://github.com/gradido/gradido/pull/3471)

View File

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

View File

@ -113,8 +113,6 @@ COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/build/worker.js ./wo
# add node_modules from production_node_modules
COPY --chown=app:app --from=production-node-modules ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy log4js-config.json to provide log configuration
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/log4js-config.json ./log4js-config.json
# Copy locales
COPY --chown=app:app --from=build ${DOCKER_WORKDIR}/backend/locales ./locales

View File

@ -13,4 +13,5 @@ build({
external: ['sodium-native', 'email-templates'],
plugins: [esbuildDecorators()],
minify: true,
sourcemap: true,
})

View File

@ -2,7 +2,7 @@
module.exports = {
verbose: true,
preset: 'ts-jest',
collectCoverage: true,
collectCoverage: false,
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: {
global: {

View File

@ -1,173 +0,0 @@
{
"appenders":
{
"access":
{
"type": "dateFile",
"filename": "../logs/backend/access.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"apollo":
{
"type": "dateFile",
"filename": "../logs/backend/apollo.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"backend":
{
"type": "dateFile",
"filename": "../logs/backend/backend.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"klicktipp":
{
"type": "dateFile",
"filename": "../logs/backend/klicktipp.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"gms":
{
"type": "dateFile",
"filename": "../logs/backend/gms.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"errorFile":
{
"type": "dateFile",
"filename": "../logs/backend/errors.log",
"pattern": "yyyy-MM-dd",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m %s"
},
"compress": true,
"keepFileExt" : true,
"fileNameSep" : "_",
"numBackups" : 30
},
"errors":
{
"type": "logLevelFilter",
"level": "error",
"appender": "errorFile"
},
"out":
{
"type": "stdout",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
}
},
"apolloOut":
{
"type": "stdout",
"layout":
{
"type": "pattern", "pattern": "%d{ISO8601} %p %c [%X{user}] [%f : %l] - %m"
}
}
},
"categories":
{
"default":
{
"appenders":
[
"out",
"errors"
],
"level": "debug",
"enableCallStack": true
},
"apollo":
{
"appenders":
[
"apollo",
"apolloOut",
"errors"
],
"level": "debug",
"enableCallStack": true
},
"backend":
{
"appenders":
[
"backend",
"errors"
],
"level": "debug",
"enableCallStack": true
},
"klicktipp":
{
"appenders":
[
"klicktipp",
"errors"
],
"level": "debug",
"enableCallStack": true
},
"gms":
{
"appenders":
[
"gms",
"errors"
],
"level": "debug",
"enableCallStack": true
},
"http":
{
"appenders":
[
"access"
],
"level": "info"
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "backend",
"version": "2.5.2",
"version": "2.6.0",
"private": false,
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
"repository": "https://github.com/gradido/gradido/backend",
@ -9,9 +9,9 @@
"main": "src/index.ts",
"scripts": {
"build": "ts-node ./esbuild.config.ts && mkdirp build/templates/ && ncp src/emails/templates build/templates && mkdirp locales/ && ncp src/locales locales",
"clean": "tsc --build --clean",
"dev": "cross-env TZ=UTC nodemon -w src --ext ts,pug,json,css -r tsconfig-paths/register src/index.ts",
"test": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --runInBand --forceExit --detectOpenHandles",
"test:coverage": "cross-env TZ=UTC NODE_ENV=development DB_DATABASE=gradido_test_backend jest --coverage --runInBand --forceExit --detectOpenHandles",
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/executeKlicktipp.ts",
"gmsusers": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/apis/gms/ExportUsers.ts",
@ -21,11 +21,13 @@
"lint:fix:unsafe": "biome check --fix --unsafe",
"locales": "scripts/sort.sh",
"locales:fix": "scripts/sort.sh --fix",
"start": "cross-env TZ=UTC NODE_ENV=production node build/index.js",
"start": "cross-env TZ=UTC node build/index.js",
"typecheck": "tsc --noEmit"
},
"nodemonConfig": {
"ignore": ["**/*.test.ts"]
"ignore": [
"**/*.test.ts"
]
},
"dependencies": {
"cross-env": "^7.0.3",
@ -34,7 +36,7 @@
},
"devDependencies": {
"@anatine/esbuild-decorators": "^0.2.19",
"@biomejs/biome": "1.9.4",
"@biomejs/biome": "2.0.0",
"@swc/cli": "^0.7.3",
"@swc/core": "^1.11.24",
"@swc/helpers": "^0.5.17",
@ -47,6 +49,7 @@
"@types/node": "^17.0.21",
"@types/nodemailer": "^6.4.4",
"@types/sodium-native": "^2.3.5",
"@types/source-map-support": "^0.5.10",
"@types/uuid": "^8.3.4",
"apollo-server-express": "^2.25.2",
"apollo-server-testing": "^2.25.2",
@ -85,12 +88,13 @@
"random-bigint": "^0.0.1",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.14.1",
"source-map-support": "^0.5.21",
"ts-jest": "27.0.5",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.1.1",
"type-graphql": "^1.1.1",
"typed-rest-client": "^1.8.11",
"typeorm": "^0.3.16",
"typeorm": "^0.3.22",
"typescript": "^4.9.5",
"uuid": "^8.3.2",
"workerpool": "^9.2.0",

View File

@ -1,7 +1,10 @@
import axios from 'axios'
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_APIS_CATEGORY_NAME}.HttpRequest`)
import { httpAgent, httpsAgent } from './ConnectionAgents'

View File

@ -1,9 +1,10 @@
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis'
import { CONFIG } from '@/config'
import { backendLogger as logger } from '@/server/logger'
import KlicktippConnector from 'klicktipp-api'
import { getLogger } from 'log4js'
const klicktippConnector = new KlicktippConnector()
const logger = getLogger(`${LOG4JS_APIS_CATEGORY_NAME}.KlicktippController`)
export const subscribe = async (
email: string,

View File

@ -1,19 +1,18 @@
import { Transaction as DbTransaction } from 'database'
import { Decimal } from 'decimal.js-light'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { DltConnectorClient } from './DltConnectorClient'
let con: Connection
let con: DataSource
let testEnv: {
con: Connection
con: DataSource
}
// Mock the GraphQLClient
@ -76,14 +75,14 @@ describe.skip('transmitTransaction, without db connection', () => {
describe('transmitTransaction', () => {
beforeAll(async () => {
testEnv = await testEnvironment(logger)
testEnv = await testEnvironment()
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
const transaction = new DbTransaction()

View File

@ -1,14 +1,17 @@
import { Transaction as DbTransaction } from 'database'
import { GraphQLClient, gql } from 'graphql-request'
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis/index'
import { CONFIG } from '@/config'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { TransactionResult } from './model/TransactionResult'
import { UserIdentifier } from './model/UserIdentifier'
const logger = getLogger(`${LOG4JS_APIS_CATEGORY_NAME}.dltConnector`)
const sendTransaction = gql`
mutation ($input: TransactionInput!) {
sendTransaction(data: $input) {

View File

@ -1,34 +1,28 @@
import { User as DbUser } from 'database'
// import { createTestClient } from 'apollo-server-testing'
import { LOG4JS_GMS_CATEGORY_NAME } from '@/apis/gms/index'
// import { createGmsUser } from '@/apis/gms/GmsClient'
// import { GmsUser } from '@/apis/gms/model/GmsUser'
import { CONFIG } from '@/config'
import { getHomeCommunity } from '@/graphql/resolver/util/communities'
import { sendUserToGms } from '@/graphql/resolver/util/sendUserToGms'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { checkDBVersion } from '@/typeorm/DBVersion'
import { Connection } from '@/typeorm/connection'
import { initLogging } from '@/server/logger'
import { AppDatabase } from 'database'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_GMS_CATEGORY_NAME}.ExportUsers`)
CONFIG.EMAIL = false
// use force to copy over all user even if gmsRegistered is set to true
const forceMode = process.argv.includes('--force')
async function main() {
initLogging()
// open mysql connection
const con = await Connection.getInstance()
if (!con?.isConnected) {
logger.fatal(`Couldn't open connection to database!`)
throw new Error(`Fatal: Couldn't open connection to database`)
}
// check for correct database version
const dbVersion = await checkDBVersion(CONFIG.DB_VERSION)
if (!dbVersion) {
logger.fatal('Fatal: Database Version incorrect')
throw new Error('Fatal: Database Version incorrect')
}
const con = AppDatabase.getInstance()
await con.init()
const homeCom = await getHomeCommunity()
if (homeCom.gmsApiKey === null) {
@ -81,7 +75,6 @@ async function main() {
}
main().catch((e) => {
// biome-ignore lint/suspicious/noConsole: logger isn't used here
console.error(e)
logger.error(e)
process.exit(1)
})

View File

@ -1,13 +1,16 @@
import axios from 'axios'
import { httpAgent, httpsAgent } from '@/apis/ConnectionAgents'
import { LOG4JS_GMS_CATEGORY_NAME } from '@/apis/gms/index'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { getLogger } from 'log4js'
import { GmsUser } from './model/GmsUser'
const logger = getLogger(`${LOG4JS_GMS_CATEGORY_NAME}.GmsClient`)
/*
export async function communityList(): Promise<GmsCommunity[] | string | undefined> {
const baseUrl = ensureUrlEndsWithSlash(CONFIG.GMS_URL)

View File

@ -0,0 +1,3 @@
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis'
export const LOG4JS_GMS_CATEGORY_NAME = `${LOG4JS_APIS_CATEGORY_NAME}.gms`

View File

@ -1,12 +1,9 @@
import { User } from 'database'
import { AppDatabase, User } from 'database'
import { IsNull, Not } from 'typeorm'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { checkDBVersion } from '@/typeorm/DBVersion'
import { Connection } from '@/typeorm/connection'
import { LOG4JS_HUMHUB_CATEGORY_NAME } from '@/apis/humhub/index'
import { initLogging } from '@/server/logger'
import { getLogger } from 'log4js'
import { HumHubClient } from './HumHubClient'
import { GetUser } from './model/GetUser'
import { UsersResponse } from './model/UsersResponse'
@ -14,6 +11,7 @@ import { ExecutedHumhubAction, syncUser } from './syncUser'
const USER_BULK_SIZE = 20
const HUMHUB_BULK_SIZE = 50
const logger = getLogger(`${LOG4JS_HUMHUB_CATEGORY_NAME}.ExportUsers`)
function getUsersPage(page: number, limit: number): Promise<[User[], number]> {
return User.findAndCount({
@ -37,7 +35,7 @@ async function loadUsersFromHumHub(client: HumHubClient): Promise<Map<string, Ge
do {
usersPage = await client.users(page, HUMHUB_BULK_SIZE)
if (!usersPage) {
throw new LogError('error requesting next users page from humhub')
throw new Error('error requesting next users page from humhub')
}
for (const user of usersPage.results) {
// deleted users have empty emails
@ -64,26 +62,16 @@ async function loadUsersFromHumHub(client: HumHubClient): Promise<Map<string, Ge
async function main() {
const start = new Date().getTime()
initLogging()
// open mysql connection
const con = await Connection.getInstance()
if (!con?.isConnected) {
logger.fatal(`Couldn't open connection to database!`)
throw new Error(`Fatal: Couldn't open connection to database`)
}
// check for correct database version
const dbVersion = await checkDBVersion(CONFIG.DB_VERSION)
if (!dbVersion) {
logger.fatal('Fatal: Database Version incorrect')
throw new Error('Fatal: Database Version incorrect')
}
const con = AppDatabase.getInstance()
await con.init()
let userCount = 0
let page = 0
const humHubClient = HumHubClient.getInstance()
if (!humHubClient) {
throw new LogError('error creating humhub client')
throw new Error('error creating humhub client')
}
const humhubUsers = await loadUsersFromHumHub(humHubClient)
@ -128,7 +116,6 @@ async function main() {
}
main().catch((e) => {
// biome-ignore lint/suspicious/noConsole: logger isn't used here
console.error(e)
logger.error(e)
process.exit(1)
})

View File

@ -4,8 +4,9 @@ import { IRequestOptions, IRestResponse, RestClient } from 'typed-rest-client'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_HUMHUB_CATEGORY_NAME } from '@/apis/humhub/index'
import { getLogger } from 'log4js'
import { PostUserLoggingView } from './logging/PostUserLogging.view'
import { GetUser } from './model/GetUser'
import { PostUser } from './model/PostUser'
@ -13,6 +14,8 @@ import { Space } from './model/Space'
import { SpacesResponse } from './model/SpacesResponse'
import { UsersResponse } from './model/UsersResponse'
const logger = getLogger(`${LOG4JS_HUMHUB_CATEGORY_NAME}.HumHubClient`)
/**
* HumHubClient as singleton class
*/

View File

@ -0,0 +1,3 @@
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis'
export const LOG4JS_HUMHUB_CATEGORY_NAME = `${LOG4JS_APIS_CATEGORY_NAME}.humhub`

View File

@ -1,13 +1,16 @@
import { User } from 'database'
import { LOG4JS_HUMHUB_CATEGORY_NAME } from '@/apis/humhub/index'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { HumHubClient } from './HumHubClient'
import { isHumhubUserIdenticalToDbUser } from './compareHumhubUserDbUser'
import { GetUser } from './model/GetUser'
import { PostUser } from './model/PostUser'
const logger = getLogger(`${LOG4JS_HUMHUB_CATEGORY_NAME}.syncUser`)
export enum ExecutedHumhubAction {
UPDATE,
CREATE,

View File

@ -0,0 +1,3 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
export const LOG4JS_APIS_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.apis`

View File

@ -4,10 +4,14 @@ import { Message } from 'openai/resources/beta/threads/messages'
import { httpsAgent } from '@/apis/ConnectionAgents'
import { CONFIG } from '@/config'
import { backendLogger as logger } from '@/server/logger'
import { Message as MessageModel } from './model/Message'
import { LOG4JS_APIS_CATEGORY_NAME } from '@/apis'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_APIS_CATEGORY_NAME}.openai.OpenaiClient`)
/**
* The `OpenaiClient` class is a singleton that provides an interface to interact with the OpenAI API.
* It ensures that only one instance of the client is created and used throughout the application.

View File

@ -1,12 +1,14 @@
import { createPrivateKey, sign } from 'node:crypto'
import { JWTPayload, SignJWT, decodeJwt, jwtVerify } from 'jose'
import { SignJWT, decodeJwt, jwtVerify } from 'jose'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { JwtPayloadType } from './payloadtypes/JwtPayloadType'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.auth.jwt.JWT`)
export const verify = async (token: string, signkey: string): Promise<JwtPayloadType | null> => {
if (!token) {
throw new LogError('401 Unauthorized')

View File

@ -0,0 +1 @@
export const LOG4JS_BASE_CATEGORY_NAME = 'backend'

View File

@ -1,9 +0,0 @@
import { CONFIG } from './index'
describe('config/index', () => {
describe('decay start block', () => {
it('has the correct date set', () => {
expect(CONFIG.DECAY_START_TIME).toEqual(new Date('2021-05-13 17:46:31-0000'))
})
})
})

View File

@ -1,24 +1,18 @@
// ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env)
import { validate } from 'config-schema'
import { latestDbVersion } from 'database'
import { Decimal } from 'decimal.js-light'
import { LogLevel, validate } from 'config-schema'
import dotenv from 'dotenv'
import { schema } from './schema'
dotenv.config()
Decimal.set({
precision: 25,
rounding: Decimal.ROUND_HALF_UP,
})
const constants = {
// DB_VERSION: '0087-add_index_on_user_roles',
DB_VERSION: latestDbVersion,
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json',
const logging = {
LOG4JS_CONFIG: process.env.LOG4JS_CONFIG ?? 'log4js-config.json',
// default log level on production should be info
// log level for default log4js-config.json, don't change existing log4js-config.json
LOG_LEVEL: (process.env.LOG_LEVEL ?? 'info') as LogLevel,
LOG_FILES_BASE_PATH: process.env.LOG_FILES_BASE_PATH ?? '../logs/backend',
}
const server = {
@ -30,23 +24,6 @@ const server = {
GDT_ACTIVE: process.env.GDT_ACTIVE === 'true' || false,
GDT_API_URL: process.env.GDT_API_URL ?? 'https://gdt.gradido.net',
PRODUCTION: process.env.NODE_ENV === 'production' || false,
// default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
}
const database = {
DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT
? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT)
: 15,
DB_CONNECT_RETRY_DELAY_MS: process.env.DB_CONNECT_RETRY_DELAY_MS
? Number.parseInt(process.env.DB_CONNECT_RETRY_DELAY_MS)
: 500,
DB_HOST: process.env.DB_HOST ?? 'localhost',
DB_PORT: process.env.DB_PORT ? Number.parseInt(process.env.DB_PORT) : 3306,
DB_USER: process.env.DB_USER ?? 'root',
DB_PASSWORD: process.env.DB_PASSWORD ?? '',
DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community',
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.backend.log',
}
const klicktipp = {
@ -161,9 +138,8 @@ const openai = {
}
export const CONFIG = {
...constants,
...logging,
...server,
...database,
...klicktipp,
...dltConnector,
...community,

View File

@ -3,14 +3,6 @@ import {
COMMUNITY_NAME,
COMMUNITY_SUPPORT_MAIL,
COMMUNITY_URL,
DB_CONNECT_RETRY_COUNT,
DB_CONNECT_RETRY_DELAY_MS,
DB_DATABASE,
DB_HOST,
DB_PASSWORD,
DB_PORT,
DB_USER,
DB_VERSION,
DECAY_START_TIME,
GDT_ACTIVE,
GDT_API_URL,
@ -21,11 +13,11 @@ import {
LOG4JS_CONFIG,
LOGIN_APP_SECRET,
LOGIN_SERVER_KEY,
LOG_FILES_BASE_PATH,
LOG_LEVEL,
NODE_ENV,
OPENAI_ACTIVE,
PRODUCTION,
TYPEORM_LOGGING_RELATIVE_PATH,
} from 'config-schema'
import Joi from 'joi'
@ -34,14 +26,6 @@ export const schema = Joi.object({
COMMUNITY_URL,
COMMUNITY_DESCRIPTION,
COMMUNITY_SUPPORT_MAIL,
DB_HOST,
DB_PASSWORD,
DB_PORT,
DB_USER,
DB_VERSION,
DB_DATABASE,
DB_CONNECT_RETRY_COUNT,
DB_CONNECT_RETRY_DELAY_MS,
DECAY_START_TIME,
GDT_API_URL,
GDT_ACTIVE,
@ -49,6 +33,7 @@ export const schema = Joi.object({
GRAPHIQL,
HUMHUB_ACTIVE,
HUMHUB_API_URL,
LOG_FILES_BASE_PATH,
LOG4JS_CONFIG,
LOGIN_APP_SECRET,
LOGIN_SERVER_KEY,
@ -56,7 +41,6 @@ export const schema = Joi.object({
NODE_ENV,
OPENAI_ACTIVE,
PRODUCTION,
TYPEORM_LOGGING_RELATIVE_PATH,
COMMUNITY_REDEEM_URL: Joi.string()
.uri({ scheme: ['http', 'https'] })

View File

@ -496,7 +496,10 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Read and reply to message</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"http://localhost/community/contributions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">
<p>„My message.“</p>
<p>To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</p>
</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
@ -667,8 +670,8 @@ exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has th
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"http://localhost/community/contributions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/community/contributions\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/community/contributions</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
@ -839,8 +842,8 @@ exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"http://localhost/community/contributions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/community/contributions\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/community/contributions</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
@ -1011,8 +1014,8 @@ exports[`sendEmailVariants sendContributionDeletedEmail result has the correct h
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"http://localhost/community/contributions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/community/contributions\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/community/contributions</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">
@ -1183,8 +1186,8 @@ exports[`sendEmailVariants sendContributionDeniedEmail result has the correct ht
</div>
<div class=\\"content\\" style=\\"display: block; width: 78%; margin: 40px 1% 40px 1%; padding: 20px 10% 40px 10%; border-radius: 24px; background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5);\\">
<h2 style=\\"margin-top: 15px; color: #383838;\\">Contribution details</h2>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"http://localhost/community/contributions\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"http://localhost/community/contributions\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">http://localhost/community/contributions</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.</div><a class=\\"button-3\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"display: inline-block; padding: 9px 15px; color: white; border: 0; line-height: inherit; text-decoration: none; cursor: pointer; border-radius: 20px; background-image: radial-gradient(circle farthest-corner at 0% 0%, #f9cd69, #c58d38); box-shadow: 16px 13px 35px 0 rgba(56, 56, 56, 0.3); margin: 25px 0 25px 0; width: 50%;\\">To account</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Or copy the link into your browser window.</div><a class=\\"clink\\" href=\\"https://gradido.net/contributions/own-contributions/1#contributionListItem-1\\" style=\\"line-break: anywhere; margin-bottom: 40px;\\">https://gradido.net/contributions/own-contributions/1#contributionListItem-1</a>
<div class=\\"p_content\\" style=\\"margin: 15px 0 15px 0; line-height: 26px; color: #696c72;\\">Please do not reply to this email.</div>
</div>
<div class=\\"text-block\\" style=\\"margin-top: 20px; color: #9ca0a8;\\">

View File

@ -0,0 +1,3 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
export const LOG4JS_EMAILS_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.emails`

View File

@ -1,11 +1,15 @@
import { createTransport } from 'nodemailer'
import { i18n, logger } from '@test/testSetup'
import { i18n } from '@test/testSetup'
import { CONFIG } from '@/config'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_EMAILS_CATEGORY_NAME } from '.'
import { sendEmailTranslated } from './sendEmailTranslated'
const logger = getLogger(`${LOG4JS_EMAILS_CATEGORY_NAME}.sendEmailTranslated`)
const testMailServerHost = 'localhost'
const testMailServerPort = 1025

View File

@ -5,7 +5,10 @@ import i18n from 'i18n'
import { createTransport } from 'nodemailer'
import { CONFIG } from '@/config'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_EMAILS_CATEGORY_NAME } from '@/emails'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_EMAILS_CATEGORY_NAME}.sendEmailTranslated`)
export const sendEmailTranslated = async ({
receiver,
@ -31,8 +34,8 @@ export const sendEmailTranslated = async ({
i18n.setLocale('en') // for logging
logger.info(
`send Email: language=${locals.locale as string} to=${receiver.to}` +
(receiver.cc ? `, cc=${receiver.cc}` : '') +
`send Email: language=${locals.locale as string} to=${receiver.to.substring(0, 3)}...` +
(receiver.cc ? `, cc=${receiver.cc.substring(0, 3)}...` : '') +
`, subject=${i18n.__('emails.' + template + '.subject')}`,
)

View File

@ -1,9 +1,13 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Decimal } from 'decimal.js-light'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_EMAILS_CATEGORY_NAME } from '.'
const logger = getLogger(`${LOG4JS_EMAILS_CATEGORY_NAME}.sendEmailTranslated`)
import { CONFIG } from '@/config'
@ -45,11 +49,11 @@ jest.mock('nodemailer', () => {
}
})
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -58,13 +62,15 @@ beforeAll(async () => {
})
afterAll(async () => {
await con.close()
await con.destroy()
})
const sendEmailTranslatedSpy = jest.spyOn(sendEmailTranslatedApi, 'sendEmailTranslated')
describe('sendEmailVariants', () => {
let result: any
const contributionFrontendLink =
'https://gradido.net/contributions/own-contributions/1#contributionListItem-1'
describe('sendAddedContributionMessageEmail', () => {
beforeAll(async () => {
@ -76,6 +82,8 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
message: 'My message.',
})
})
@ -93,9 +101,9 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
contributionFrontendLink,
message: 'My message.',
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@ -234,6 +242,7 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionAmount: new Decimal(23.54),
contributionFrontendLink,
})
})
@ -252,9 +261,8 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionAmount: '23.54',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
contributionFrontendLink,
},
})
})
@ -291,6 +299,7 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionMemoUpdated: 'This is a better contribution memo.',
contributionFrontendLink,
})
})
@ -309,9 +318,8 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionMemoUpdated: 'This is a better contribution memo.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@ -347,6 +355,7 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
})
})
@ -364,9 +373,8 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@ -402,6 +410,7 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
contributionFrontendLink,
})
})
@ -419,9 +428,8 @@ describe('sendEmailVariants', () => {
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
contributionMemo: 'My contribution.',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
})
@ -531,7 +539,6 @@ describe('sendEmailVariants', () => {
senderEmail: 'bibi@bloxberg.de',
transactionMemo: 'You deserve it! 🙏🏼',
transactionAmount: '17.65',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
@ -590,7 +597,6 @@ describe('sendEmailVariants', () => {
senderLastName: 'Bloxberg',
senderEmail: 'bibi@bloxberg.de',
transactionAmount: '37.40',
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},

View File

@ -5,7 +5,7 @@ import { decimalSeparatorByLanguage } from '@/util/utilities'
import { sendEmailTranslated } from './sendEmailTranslated'
export const sendAddedContributionMessageEmail = (data: {
export interface ContributionEmailCommonData {
firstName: string
lastName: string
email: string
@ -13,22 +13,35 @@ export const sendAddedContributionMessageEmail = (data: {
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | boolean | null> => {
contributionFrontendLink: string
}
function toContributionEmailLocales(data: ContributionEmailCommonData): Record<string, unknown> {
return {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
contributionFrontendLink: data.contributionFrontendLink,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
}
}
export const sendAddedContributionMessageEmail = (
data: ContributionEmailCommonData & {
message: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: {
to: `${data.firstName} ${data.lastName} <${data.email}>`,
},
template: 'addedContributionMessage',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
...toContributionEmailLocales(data),
message: data.message,
},
})
}
@ -79,111 +92,53 @@ export const sendAccountMultiRegistrationEmail = (data: {
})
}
export const sendContributionConfirmedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionAmount: Decimal
}): Promise<Record<string, unknown> | boolean | null> => {
export const sendContributionConfirmedEmail = (
data: ContributionEmailCommonData & {
contributionAmount: Decimal
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionConfirmed',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
...toContributionEmailLocales(data),
contributionAmount: decimalSeparatorByLanguage(data.contributionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionChangedByModeratorEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
contributionMemoUpdated: string
}): Promise<Record<string, unknown> | boolean | null> => {
export const sendContributionChangedByModeratorEmail = (
data: ContributionEmailCommonData & {
contributionMemoUpdated: string
},
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionChangedByModerator',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
...toContributionEmailLocales(data),
contributionMemoUpdated: data.contributionMemoUpdated,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
})
}
export const sendContributionDeletedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | boolean | null> => {
export const sendContributionDeletedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDeleted',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
locals: toContributionEmailLocales(data),
})
}
export const sendContributionDeniedEmail = (data: {
firstName: string
lastName: string
email: string
language: string
senderFirstName: string
senderLastName: string
contributionMemo: string
}): Promise<Record<string, unknown> | boolean | null> => {
export const sendContributionDeniedEmail = (
data: ContributionEmailCommonData,
): Promise<Record<string, unknown> | boolean | null> => {
return sendEmailTranslated({
receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` },
template: 'contributionDenied',
locals: {
firstName: data.firstName,
lastName: data.lastName,
locale: data.language,
senderFirstName: data.senderFirstName,
senderLastName: data.senderLastName,
contributionMemo: data.contributionMemo,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
locals: toContributionEmailLocales(data),
})
}
@ -234,7 +189,6 @@ export const sendTransactionLinkRedeemedEmail = (data: {
senderEmail: data.senderEmail,
transactionMemo: data.transactionMemo,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},
@ -264,7 +218,6 @@ export const sendTransactionReceivedEmail = (data: {
senderLastName: data.senderLastName,
senderEmail: data.senderEmail,
transactionAmount: decimalSeparatorByLanguage(data.transactionAmount, data.language),
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL,
communityURL: CONFIG.COMMUNITY_URL,
},

View File

@ -7,8 +7,10 @@ block content
p= t('emails.addedContributionMessage.commonGoodContributionMessage', { senderFirstName, senderLastName, contributionMemo })
.content
h2= t('emails.addedContributionMessage.readMessage')
div(class="p_content")= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
div(class="p_content")
p= t('emails.addedContributionMessage.message', { message })
p= t('emails.addedContributionMessage.toSeeAndAnswerMessage')
a.button-3(href=`${communityURL}/community/contributions`) #{t('emails.general.toAccount')}
a.button-3(href=`${contributionFrontendLink}`) #{t('emails.general.toAccount')}
include ../includes/doNotReply.pug

View File

@ -1,7 +1,7 @@
//-
h2= t('emails.general.contributionDetails')
div(class="p_content")= t('emails.contribution.toSeeContributionsAndMessages')
a.button-3(href=`${communityURL}/community/contributions`) #{t('emails.general.toAccount')}
a.button-3(href=`${contributionFrontendLink}`) #{t('emails.general.toAccount')}
div(class="p_content")= t('emails.general.orCopyLink')
a.clink(href=`${communityURL}/community/contributions`) #{`${communityURL}/community/contributions`}
a.clink(href=`${contributionFrontendLink}`) #{`${contributionFrontendLink}`}

View File

@ -4,12 +4,15 @@ import { validate as validateUUID, version as versionUUID } from 'uuid'
import { CONFIG } from '@/config'
import { AuthenticationClient as V1_0_AuthenticationClient } from '@/federation/client/1_0/AuthenticationClient'
import { backendLogger as logger } from '@/server/logger'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { LOG4JS_FEDERATION_CATEGORY_NAME } from '@/federation'
import { getLogger } from 'log4js'
import { OpenConnectionArgs } from './client/1_0/model/OpenConnectionArgs'
import { AuthenticationClientFactory } from './client/AuthenticationClientFactory'
const logger = getLogger(`${LOG4JS_FEDERATION_CATEGORY_NAME}.authenticateCommunities`)
export async function startCommunityAuthentication(
foreignFedCom: DbFederatedCommunity,
): Promise<void> {

View File

@ -1,12 +1,15 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { backendLogger as logger } from '@/server/logger'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME } from '@/federation/client/1_0'
import { getLogger } from 'log4js'
import { OpenConnectionArgs } from './model/OpenConnectionArgs'
import { openConnection } from './query/openConnection'
const logger = getLogger(`${LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME}.AuthenticationClient`)
export class AuthenticationClient {
dbCom: DbFederatedCommunity
endpoint: string
@ -25,25 +28,19 @@ export class AuthenticationClient {
}
async openConnection(args: OpenConnectionArgs): Promise<boolean | undefined> {
logger.debug(`Authentication: openConnection at ${this.endpoint} for args:`, args)
logger.debug(`openConnection at ${this.endpoint} for args:`, args)
try {
const { data } = await this.client.rawRequest<{ openConnection: boolean }>(openConnection, {
args,
})
if (!data?.openConnection) {
logger.warn(
'Authentication: openConnection without response data from endpoint',
this.endpoint,
)
logger.warn('openConnection without response data from endpoint', this.endpoint)
return false
}
logger.debug(
'Authentication: openConnection successfully started with endpoint',
this.endpoint,
)
logger.debug('openConnection successfully started with endpoint', this.endpoint)
return true
} catch (err) {
logger.error('Authentication: error on openConnection: ', err)
logger.error('error on openConnection: ', err)
}
}
}

View File

@ -1,15 +1,18 @@
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME } from '@/federation/client/1_0'
import { getPublicCommunityInfo } from '@/federation/client/1_0/query/getPublicCommunityInfo'
import { getPublicKey } from '@/federation/client/1_0/query/getPublicKey'
import { backendLogger as logger } from '@/server/logger'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { getLogger } from 'log4js'
import { PublicCommunityInfoLoggingView } from './logging/PublicCommunityInfoLogging.view'
import { GetPublicKeyResult } from './model/GetPublicKeyResult'
import { PublicCommunityInfo } from './model/PublicCommunityInfo'
const logger = getLogger(`${LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME}.FederationClient`)
export class FederationClient {
dbCom: DbFederatedCommunity
endpoint: string
@ -32,25 +35,25 @@ export class FederationClient {
}
getPublicKey = async (): Promise<string | undefined> => {
logger.debug('Federation: getPublicKey from endpoint', this.endpoint)
logger.debug('getPublicKey from endpoint', this.endpoint)
try {
const { data } = await this.client.rawRequest<{ getPublicKey: GetPublicKeyResult }>(
getPublicKey,
{},
)
if (!data?.getPublicKey?.publicKey) {
logger.warn('Federation: getPublicKey without response data from endpoint', this.endpoint)
logger.warn('getPublicKey without response data from endpoint', this.endpoint)
return
}
logger.debug(
'Federation: getPublicKey successful from endpoint',
'getPublicKey successful from endpoint',
this.endpoint,
data.getPublicKey.publicKey,
)
return data.getPublicKey.publicKey
} catch (err) {
const errorString = JSON.stringify(err)
logger.warn('Federation: getPublicKey failed for endpoint', {
logger.warn('getPublicKey failed for endpoint', {
endpoint: this.endpoint,
err: errorString.length <= 200 ? errorString : errorString.substring(0, 200) + '...',
})
@ -58,20 +61,17 @@ export class FederationClient {
}
getPublicCommunityInfo = async (): Promise<PublicCommunityInfo | undefined> => {
logger.debug(`Federation: getPublicCommunityInfo with endpoint='${this.endpoint}'...`)
logger.debug(`getPublicCommunityInfo with endpoint='${this.endpoint}'...`)
try {
const { data } = await this.client.rawRequest<{
getPublicCommunityInfo: PublicCommunityInfo
}>(getPublicCommunityInfo, {})
if (!data?.getPublicCommunityInfo?.name) {
logger.warn(
'Federation: getPublicCommunityInfo without response data from endpoint',
this.endpoint,
)
logger.warn('getPublicCommunityInfo without response data from endpoint', this.endpoint)
return
}
logger.debug(`Federation: getPublicCommunityInfo successful from endpoint=${this.endpoint}`)
logger.debug(`getPublicCommunityInfo successful from endpoint=${this.endpoint}`)
logger.debug(
`publicCommunityInfo:`,
new PublicCommunityInfoLoggingView(data.getPublicCommunityInfo),
@ -80,7 +80,7 @@ export class FederationClient {
} catch (err) {
logger.warn(' err', err)
const errorString = JSON.stringify(err)
logger.warn('Federation: getPublicCommunityInfo failed for endpoint', {
logger.warn('getPublicCommunityInfo failed for endpoint', {
endpoint: this.endpoint,
err: errorString.length <= 200 ? errorString : errorString.substring(0, 200) + '...',
})

View File

@ -2,9 +2,10 @@ import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { getLogger } from 'log4js'
import { LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME } from '.'
import { SendCoinsArgsLoggingView } from './logging/SendCoinsArgsLogging.view'
import { SendCoinsResultLoggingView } from './logging/SendCoinsResultLogging.view'
import { SendCoinsArgs } from './model/SendCoinsArgs'
@ -14,6 +15,8 @@ import { revertSettledSendCoins as revertSettledSendCoinsQuery } from './query/r
import { settleSendCoins as settleSendCoinsQuery } from './query/settleSendCoins'
import { voteForSendCoins as voteForSendCoinsQuery } from './query/voteForSendCoins'
const logger = getLogger(`${LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME}.SendCoinsClient`)
export class SendCoinsClient {
dbCom: DbFederatedCommunity
endpoint: string
@ -32,123 +35,87 @@ export class SendCoinsClient {
}
async voteForSendCoins(args: SendCoinsArgs): Promise<SendCoinsResult> {
logger.debug('X-Com: voteForSendCoins against endpoint=', this.endpoint)
logger.debug('voteForSendCoins against endpoint=', this.endpoint)
try {
logger.debug(
`X-Com: SendCoinsClient: voteForSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
logger.debug(`voteForSendCoins with args=`, new SendCoinsArgsLoggingView(args))
const { data } = await this.client.rawRequest<{ voteForSendCoins: SendCoinsResult }>(
voteForSendCoinsQuery,
{ args },
)
const result = data.voteForSendCoins
if (!data?.voteForSendCoins?.vote) {
logger.debug(
'X-Com: voteForSendCoins failed with: ',
new SendCoinsResultLoggingView(result),
)
logger.debug('voteForSendCoins failed with: ', new SendCoinsResultLoggingView(result))
return new SendCoinsResult()
}
logger.debug(
'X-Com: voteForSendCoins successful with result=',
'voteForSendCoins successful with result=',
new SendCoinsResultLoggingView(result),
)
return result
} catch (err) {
throw new LogError(`X-Com: voteForSendCoins failed for endpoint=${this.endpoint}:`, err)
throw new LogError(`voteForSendCoins failed for endpoint=${this.endpoint}:`, err)
}
}
async revertSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug('X-Com: revertSendCoins against endpoint=', this.endpoint)
logger.debug('revertSendCoins against endpoint=', this.endpoint)
try {
logger.debug(
`X-Com: SendCoinsClient: revertSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
logger.debug(`revertSendCoins with args=`, new SendCoinsArgsLoggingView(args))
const { data } = await this.client.rawRequest<{ revertSendCoins: boolean }>(
revertSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after revertSendCoins: data=`, data)
logger.debug(`after revertSendCoins: data=`, data)
if (!data?.revertSendCoins) {
logger.warn('X-Com: revertSendCoins without response data from endpoint', this.endpoint)
logger.warn('revertSendCoins without response data from endpoint', this.endpoint)
return false
}
logger.debug(
`X-Com: SendCoinsClient: revertSendCoins successful from endpoint=${this.endpoint}`,
)
logger.debug(`revertSendCoins successful from endpoint=${this.endpoint}`)
return true
} catch (err) {
logger.error(
`X-Com: SendCoinsClient: revertSendCoins failed for endpoint=${this.endpoint}`,
err,
)
logger.error(`revertSendCoins failed for endpoint=${this.endpoint}`, err)
return false
}
}
async settleSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug(`X-Com: settleSendCoins against endpoint='${this.endpoint}'...`)
logger.debug(`settleSendCoins against endpoint='${this.endpoint}'...`)
try {
logger.debug(
`X-Com: SendCoinsClient: settleSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
logger.debug(`settleSendCoins with args=`, new SendCoinsArgsLoggingView(args))
const { data } = await this.client.rawRequest<{ settleSendCoins: boolean }>(
settleSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after settleSendCoins: data=`, data)
logger.debug(`after settleSendCoins: data=`, data)
if (!data?.settleSendCoins) {
logger.warn(
'X-Com: SendCoinsClient: settleSendCoins without response data from endpoint',
this.endpoint,
)
logger.warn('settleSendCoins without response data from endpoint', this.endpoint)
return false
}
logger.debug(
`X-Com: SendCoinsClient: settleSendCoins successful from endpoint=${this.endpoint}`,
)
logger.debug(`settleSendCoins successful from endpoint=${this.endpoint}`)
return true
} catch (err) {
throw new LogError(
`X-Com: SendCoinsClient: settleSendCoins failed for endpoint=${this.endpoint}`,
err,
)
throw new LogError(`settleSendCoins failed for endpoint=${this.endpoint}`, err)
}
}
async revertSettledSendCoins(args: SendCoinsArgs): Promise<boolean> {
logger.debug(`X-Com: revertSettledSendCoins against endpoint='${this.endpoint}'...`)
logger.debug(`revertSettledSendCoins against endpoint='${this.endpoint}'...`)
try {
logger.debug(
`X-Com: SendCoinsClient: revertSettledSendCoins with args=`,
new SendCoinsArgsLoggingView(args),
)
logger.debug(`revertSettledSendCoins with args=`, new SendCoinsArgsLoggingView(args))
const { data } = await this.client.rawRequest<{ revertSettledSendCoins: boolean }>(
revertSettledSendCoinsQuery,
{ args },
)
logger.debug(`X-Com: SendCoinsClient: after revertSettledSendCoins: data=`, data)
logger.debug(`after revertSettledSendCoins: data=`, data)
if (!data?.revertSettledSendCoins) {
logger.warn(
'X-Com: SendCoinsClient: revertSettledSendCoins without response data from endpoint',
this.endpoint,
)
logger.warn('revertSettledSendCoins without response data from endpoint', this.endpoint)
return false
}
logger.debug(
`X-Com: SendCoinsClient: revertSettledSendCoins successful from endpoint=${this.endpoint}`,
)
logger.debug(`revertSettledSendCoins successful from endpoint=${this.endpoint}`)
return true
} catch (err) {
throw new LogError(
`X-Com: SendCoinsClient: revertSettledSendCoins failed for endpoint=${this.endpoint}`,
err,
)
throw new LogError(`revertSettledSendCoins failed for endpoint=${this.endpoint}`, err)
}
}
}

View File

@ -0,0 +1,3 @@
import { LOG4JS_FEDERATION_CATEGORY_NAME } from '@/federation'
export const LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME = `${LOG4JS_FEDERATION_CATEGORY_NAME}.client.1_0`

View File

@ -0,0 +1,3 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
export const LOG4JS_FEDERATION_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.federation`

View File

@ -2,18 +2,25 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
import { FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLClient } from 'graphql-request'
import { Response } from 'graphql-request/dist/types'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { LOG4JS_FEDERATION_CATEGORY_NAME } from '@/federation'
import { LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME } from '@/federation/client/1_0'
import { cleanDB, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
import { clearLogs, getLogger, printLogs } from 'config-schema/test/testSetup'
import { validateCommunities } from './validateCommunities'
let con: Connection
const logger = getLogger(`${LOG4JS_FEDERATION_CATEGORY_NAME}.validateCommunities`)
const federationClientLogger = getLogger(
`${LOG4JS_FEDERATION_CLIENT1_0_CATEGORY_NAME}.FederationClient`,
)
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -24,7 +31,7 @@ beforeAll(async () => {
afterAll(async () => {
// await cleanDB()
await con.close()
await con.destroy()
})
describe('validate Communities', () => {
@ -49,7 +56,7 @@ describe('validate Communities', () => {
})
it('logs zero communities found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 0 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 0 dbCommunities`)
})
describe('with one Community of api 1_0 but missing pubKey response', () => {
@ -79,11 +86,11 @@ describe('validate Communities', () => {
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 1 dbCommunities`)
})
it('logs requestGetPublicKey missing response data ', () => {
expect(logger.warn).toBeCalledWith(
'Federation: getPublicKey without response data from endpoint',
expect(federationClientLogger.warn).toBeCalledWith(
'getPublicKey without response data from endpoint',
'http//localhost:5001/api/1_0/',
)
})
@ -153,17 +160,17 @@ describe('validate Communities', () => {
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs not matching publicKeys', () => {
expect(logger.debug).toBeCalledWith(
'Federation: received not matching publicKey:',
'received not matching publicKey:',
'somePubKey',
expect.stringMatching('11111111111111111111111111111111'),
)
@ -203,18 +210,18 @@ describe('validate Communities', () => {
})
it('logs one community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 1 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 1 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs community pubKey verified', () => {
expect(logger.debug).toHaveBeenNthCalledWith(
5,
'Federation: getPublicKey successful from endpoint',
expect(federationClientLogger.debug).toHaveBeenNthCalledWith(
2,
'getPublicKey successful from endpoint',
'http//localhost:5001/api/1_0/',
'11111111111111111111111111111111',
)
@ -269,17 +276,17 @@ describe('validate Communities', () => {
await validateCommunities()
})
it('logs two communities found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 2 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 2 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
@ -321,23 +328,23 @@ describe('validate Communities', () => {
await validateCommunities()
})
it('logs three community found', () => {
expect(logger.debug).toBeCalledWith(`Federation: found 3 dbCommunities`)
expect(logger.debug).toBeCalledWith(`found 3 dbCommunities`)
})
it('logs requestGetPublicKey for community api 1_0 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_0/',
)
})
it('logs requestGetPublicKey for community api 1_1 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: getPublicKey from endpoint',
expect(federationClientLogger.debug).toBeCalledWith(
'getPublicKey from endpoint',
'http//localhost:5001/api/1_1/',
)
})
it('logs unsupported api for community with api 2_0 ', () => {
expect(logger.debug).toBeCalledWith(
'Federation: dbCom with unsupported apiVersion',
'dbCom with unsupported apiVersion',
dbCom.endPoint,
'2_0',
)

View File

@ -5,23 +5,23 @@ import {
} from 'database'
import { IsNull } from 'typeorm'
import { LOG4JS_FEDERATION_CATEGORY_NAME } from '@/federation'
import { FederationClient as V1_0_FederationClient } from '@/federation/client/1_0/FederationClient'
import { PublicCommunityInfo } from '@/federation/client/1_0/model/PublicCommunityInfo'
import { FederationClientFactory } from '@/federation/client/FederationClientFactory'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { startCommunityAuthentication } from './authenticateCommunities'
import { PublicCommunityInfoLoggingView } from './client/1_0/logging/PublicCommunityInfoLogging.view'
import { ApiVersionType } from './enum/apiVersionType'
const logger = getLogger(`${LOG4JS_FEDERATION_CATEGORY_NAME}.validateCommunities`)
export async function startValidateCommunities(timerInterval: number): Promise<void> {
if (Number.isNaN(timerInterval) || timerInterval <= 0) {
throw new LogError('FEDERATION_VALIDATE_COMMUNITY_TIMER is not a positive number')
}
logger.info(
`Federation: startValidateCommunities loop with an interval of ${timerInterval} ms...`,
)
logger.info(`startValidateCommunities loop with an interval of ${timerInterval} ms...`)
// delete all foreign federated community entries to avoid increasing validation efforts and log-files
await DbFederatedCommunity.delete({ foreign: true })
@ -40,17 +40,13 @@ export async function validateCommunities(): Promise<void> {
.orWhere('verified_at < last_announced_at')
.getMany()
logger.debug(`Federation: found ${dbFederatedCommunities.length} dbCommunities`)
logger.debug(`found ${dbFederatedCommunities.length} dbCommunities`)
for (const dbCom of dbFederatedCommunities) {
logger.debug('Federation: dbCom', new FederatedCommunityLoggingView(dbCom))
logger.debug('dbCom', new FederatedCommunityLoggingView(dbCom))
const apiValueStrings: string[] = Object.values(ApiVersionType)
logger.debug(`suppported ApiVersions=`, apiValueStrings)
if (!apiValueStrings.includes(dbCom.apiVersion)) {
logger.debug(
'Federation: dbCom with unsupported apiVersion',
dbCom.endPoint,
dbCom.apiVersion,
)
logger.debug('dbCom with unsupported apiVersion', dbCom.endPoint, dbCom.apiVersion)
continue
}
try {
@ -60,21 +56,17 @@ export async function validateCommunities(): Promise<void> {
const pubKey = await client.getPublicKey()
if (pubKey && pubKey === dbCom.publicKey.toString('hex')) {
await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() })
logger.debug(`Federation: verified community with:`, dbCom.endPoint)
logger.debug(`verified community with:`, dbCom.endPoint)
const pubComInfo = await client.getPublicCommunityInfo()
if (pubComInfo) {
await writeForeignCommunity(dbCom, pubComInfo)
await startCommunityAuthentication(dbCom)
logger.debug(`Federation: write publicInfo of community: name=${pubComInfo.name}`)
logger.debug(`write publicInfo of community: name=${pubComInfo.name}`)
} else {
logger.debug('Federation: missing result of getPublicCommunityInfo')
logger.debug('missing result of getPublicCommunityInfo')
}
} else {
logger.debug(
'Federation: received not matching publicKey:',
pubKey,
dbCom.publicKey.toString('hex'),
)
logger.debug('received not matching publicKey:', pubKey, dbCom.publicKey.toString('hex'))
}
}
} catch (err) {

View File

@ -9,9 +9,10 @@ import { RIGHTS } from '@/auth/RIGHTS'
import { BalanceLoggingView } from '@/logging/BalanceLogging.view'
import { DecayLoggingView } from '@/logging/DecayLogging.view'
import { Context, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { calculateDecay } from '@/util/decay'
import { getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { GdtResolver } from './GdtResolver'
import { getLastTransaction } from './util/getLastTransaction'
import { transactionLinkSummary } from './util/transactionLinkSummary'
@ -23,9 +24,10 @@ export class BalanceResolver {
async balance(@Ctx() context: Context): Promise<Balance> {
const user = getUser(context)
const now = new Date()
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.BalanceResolver`)
logger.addContext('user', user.id)
logger.info(`balance(userId=${user.id})...`)
logger.info(`balance...`)
let balanceGDT
if (!context.balanceGDT) {

View File

@ -1,11 +1,11 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database'
import { GraphQLError } from 'graphql/error/GraphQLError'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { userFactory } from '@/seeds/factory/user'
import { login, updateHomeCommunityQuery } from '@/seeds/graphql/mutations'
@ -18,19 +18,23 @@ import {
} from '@/seeds/graphql/queries'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { getCommunityByUuid } from './util/communities'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.CommunityResolver`)
// to do: We need a setup for the tests that closes the connection
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
const peterLoginData = {
@ -49,7 +53,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
// real valid ed25519 key pairs

View File

@ -2,11 +2,11 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
import { ContributionLink as DbContributionLink, Event as DbEvent } from 'database'
import { Decimal } from 'decimal.js-light'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import {
@ -18,16 +18,19 @@ import {
import { listContributionLinks } from '@/seeds/graphql/queries'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { getLogger } from 'config-schema/test/testSetup'
jest.mock('@/password/EncryptorUtils')
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -42,7 +45,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('Contribution Links', () => {
@ -286,7 +289,7 @@ describe('Contribution Links', () => {
})
it('logs the error "A Start-Date must be set"', () => {
expect(logger.error).toBeCalledWith('A Start-Date must be set')
expect(logErrorLogger.error).toBeCalledWith('A Start-Date must be set')
})
it('returns an error if missing endDate', async () => {
@ -307,7 +310,7 @@ describe('Contribution Links', () => {
})
it('logs the error "An End-Date must be set"', () => {
expect(logger.error).toBeCalledWith('An End-Date must be set')
expect(logErrorLogger.error).toBeCalledWith('An End-Date must be set')
})
it('returns an error if endDate is before startDate', async () => {
@ -331,7 +334,7 @@ describe('Contribution Links', () => {
})
it('logs the error "The value of validFrom must before or equals the validTo"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
`The value of validFrom must before or equals the validTo`,
)
})
@ -531,7 +534,7 @@ describe('Contribution Links', () => {
})
it('logs the error "Contribution Link not found"', () => {
expect(logger.error).toBeCalledWith('Contribution Link not found', -1)
expect(logErrorLogger.error).toBeCalledWith('Contribution Link not found', -1)
})
describe('valid id', () => {
@ -613,7 +616,7 @@ describe('Contribution Links', () => {
})
it('logs the error "Contribution Link not found"', () => {
expect(logger.error).toBeCalledWith('Contribution Link not found', -1)
expect(logErrorLogger.error).toBeCalledWith('Contribution Link not found', -1)
})
})

View File

@ -1,14 +1,16 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Contribution as DbContribution, Event as DbEvent } from 'database'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { ContributionStatus } from '@enum/ContributionStatus'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants'
import { EventType } from '@/event/Events'
import { LOG4JS_INTERACTION_CATEGORY_NAME } from '@/interactions'
import { userFactory } from '@/seeds/factory/user'
import {
adminCreateContributionMessage,
@ -20,6 +22,14 @@ import { adminListContributionMessages, listContributionMessages } from '@/seeds
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { clearLogs, getLogger, printLogs } from 'config-schema/test/testSetup'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.ContributionMessageResolver`)
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
const interactionLogger = getLogger(
`${LOG4JS_INTERACTION_CATEGORY_NAME}.updateUnconfirmedContribution`,
)
jest.mock('@/password/EncryptorUtils')
jest.mock('@/emails/sendEmailVariants', () => {
@ -34,11 +44,11 @@ jest.mock('@/emails/sendEmailVariants', () => {
})
let mutate: ApolloServerTestClient['mutate']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
let result: any
@ -51,7 +61,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('ContributionMessageResolver', () => {
@ -121,7 +131,7 @@ describe('ContributionMessageResolver', () => {
})
it('logs the error "ContributionMessage was not sent successfully: Error: Contribution not found"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Contribution not found',
new Error('Contribution not found'),
)
@ -148,9 +158,7 @@ describe('ContributionMessageResolver', () => {
message: 'Test',
},
})
expect(logger.debug).toBeCalledTimes(5)
expect(logger.debug).toHaveBeenNthCalledWith(
5,
expect(interactionLogger.debug).toBeCalledWith(
'use UnconfirmedContributionUserAddMessageRole',
)
expect(mutationResult).toEqual(
@ -244,9 +252,11 @@ describe('ContributionMessageResolver', () => {
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
language: 'de',
message: 'Admin Test',
senderFirstName: 'Peter',
senderLastName: 'Lustig',
contributionMemo: 'Test env contribution',
contributionFrontendLink: `http://localhost/contributions/own-contributions/1#contributionListItem-${result.data.createContribution.id}`,
})
})
@ -325,7 +335,7 @@ describe('ContributionMessageResolver', () => {
})
it('logs the error "ContributionMessage was not sent successfully: Error: Contribution not found"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'ContributionMessage was not sent successfully: Error: Contribution not found',
new Error('Contribution not found'),
)
@ -346,9 +356,7 @@ describe('ContributionMessageResolver', () => {
},
})
expect(logger.debug).toBeCalledTimes(5)
expect(logger.debug).toHaveBeenNthCalledWith(
5,
expect(interactionLogger.debug).toBeCalledWith(
'use UnconfirmedContributionAdminAddMessageRole',
)
@ -380,10 +388,7 @@ describe('ContributionMessageResolver', () => {
message: 'Test',
},
})
expect(logger.debug).toBeCalledTimes(5)
expect(logger.debug).toHaveBeenNthCalledWith(
5,
expect(interactionLogger.debug).toBeCalledWith(
'use UnconfirmedContributionAdminAddMessageRole',
)
@ -399,13 +404,12 @@ describe('ContributionMessageResolver', () => {
})
it('logs the error "ContributionMessage was not sent successfully: Error: missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user"', () => {
expect(logger.debug).toBeCalledTimes(5)
expect(logger.error).toHaveBeenNthCalledWith(
expect(logErrorLogger.error).toHaveBeenNthCalledWith(
1,
'missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user',
expect.any(Number),
)
expect(logger.error).toHaveBeenNthCalledWith(
expect(logErrorLogger.error).toHaveBeenNthCalledWith(
2,
'ContributionMessage was not sent successfully: Error: missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user',
new Error('missing right ADMIN_CREATE_CONTRIBUTION_MESSAGE for user'),

View File

@ -1,10 +1,11 @@
import {
AppDatabase,
Contribution as DbContribution,
ContributionMessage as DbContributionMessage,
User as DbUser,
} from 'database'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
import { EntityManager, FindOptionsRelations, getConnection } from 'typeorm'
import { EntityManager, FindOptionsRelations } from 'typeorm'
import { ContributionMessageArgs } from '@arg/ContributionMessageArgs'
import { Paginated } from '@arg/Paginated'
@ -21,10 +22,15 @@ import {
import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context'
import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { contributionFrontendLink } from './util/contributions'
import { findContributionMessages } from './util/findContributionMessages'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.ContributionMessageResolver`)
@Resolver()
export class ContributionMessageResolver {
@Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE])
@ -43,9 +49,9 @@ export class ContributionMessageResolver {
let finalContributionMessage: DbContributionMessage | undefined
try {
await getConnection().transaction(
'REPEATABLE READ',
async (transactionalEntityManager: EntityManager) => {
await db
.getDataSource()
.transaction('REPEATABLE READ', async (transactionalEntityManager: EntityManager) => {
const { contribution, contributionMessage, contributionChanged } =
await updateUnconfirmedContributionContext.run(transactionalEntityManager)
@ -62,8 +68,7 @@ export class ContributionMessageResolver {
finalContribution = contribution
finalContributionMessage = contributionMessage
},
)
})
} catch (e) {
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
}
@ -123,7 +128,9 @@ export class ContributionMessageResolver {
@Args() contributionMessageArgs: ContributionMessageArgs,
@Ctx() context: Context,
): Promise<ContributionMessage> {
const logger = createLogger()
const { contributionId, messageType } = contributionMessageArgs
logger.addContext('contribution', contributionMessageArgs.contributionId)
const updateUnconfirmedContributionContext = new UpdateUnconfirmedContributionContext(
contributionId,
contributionMessageArgs,
@ -137,9 +144,9 @@ export class ContributionMessageResolver {
let finalContributionMessage: DbContributionMessage | undefined
try {
await getConnection().transaction(
'REPEATABLE READ',
async (transactionalEntityManager: EntityManager) => {
await db
.getDataSource()
.transaction('REPEATABLE READ', async (transactionalEntityManager: EntityManager) => {
const { contribution, contributionMessage, contributionChanged } =
await updateUnconfirmedContributionContext.run(transactionalEntityManager, relations)
if (contributionChanged) {
@ -159,8 +166,7 @@ export class ContributionMessageResolver {
}
finalContribution = contribution
finalContributionMessage = contributionMessage
},
)
})
} catch (e) {
throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e)
}
@ -179,6 +185,11 @@ export class ContributionMessageResolver {
senderFirstName: moderator.firstName,
senderLastName: moderator.lastName,
contributionMemo: finalContribution.memo,
contributionFrontendLink: await contributionFrontendLink(
finalContribution.id,
finalContribution.createdAt,
),
message: finalContributionMessage.message,
})
}

View File

@ -1,9 +1,8 @@
import { UserInputError } from 'apollo-server-express'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Contribution, Event as DbEvent, Transaction as DbTransaction, User } from 'database'
import { Decimal } from 'decimal.js-light'
import { GraphQLError } from 'graphql'
import { Connection, Equal } from 'typeorm'
import { DataSource, Equal } from 'typeorm'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { ContributionStatus } from '@enum/ContributionStatus'
@ -15,8 +14,9 @@ import {
resetToken,
testEnvironment,
} from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import {
sendContributionConfirmedEmail,
sendContributionDeletedEmail,
@ -51,17 +51,21 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getFirstDayOfPreviousNMonth } from '@/util/utilities'
import { clearLogs, getLogger, printLogs } from 'config-schema/test/testSetup'
import { getLogger as originalGetLogger } from 'log4js'
jest.mock('@/emails/sendEmailVariants')
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
let creation: Contribution | null
let admin: User
@ -73,7 +77,7 @@ let contributionToDelete: any
let bibiCreatedContribution: Contribution
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
testEnv = await testEnvironment(originalGetLogger('apollo'), localization)
mutate = testEnv.mutate
query = testEnv.query
con = testEnv.con
@ -82,7 +86,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('ContributionResolver', () => {
@ -877,6 +881,7 @@ describe('ContributionResolver', () => {
senderFirstName: 'Peter',
senderLastName: 'Lustig',
contributionMemo: 'Test contribution to deny',
contributionFrontendLink: `http://localhost/contributions/own-contributions/1#contributionListItem-${contributionToDeny.data.createContribution.id}`,
})
})
})
@ -1954,6 +1959,7 @@ describe('ContributionResolver', () => {
senderFirstName: 'Peter',
senderLastName: 'Lustig',
contributionMemo: 'Das war leider zu Viel!',
contributionFrontendLink: `http://localhost/contributions/own-contributions/1#contributionListItem-${creation?.id}`,
})
})
})
@ -2120,6 +2126,7 @@ describe('ContributionResolver', () => {
senderLastName: 'Lustig',
contributionMemo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
contributionAmount: expect.decimalEqual(450),
contributionFrontendLink: `http://localhost/contributions/own-contributions/1#contributionListItem-${creation?.id}`,
})
})

View File

@ -7,7 +7,7 @@ import {
import { Decimal } from 'decimal.js-light'
import { GraphQLResolveInfo } from 'graphql'
import { Arg, Args, Authorized, Ctx, Info, Int, Mutation, Query, Resolver } from 'type-graphql'
import { EntityManager, IsNull, getConnection } from 'typeorm'
import { EntityManager, IsNull } from 'typeorm'
import { AdminCreateContributionArgs } from '@arg/AdminCreateContributionArgs'
import { AdminUpdateContributionArgs } from '@arg/AdminUpdateContributionArgs'
@ -43,20 +43,28 @@ import {
import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { calculateDecay } from '@/util/decay'
import { fullName } from '@/util/utilities'
import { start } from 'repl'
import { ContributionMessageType } from '../enum/ContributionMessageType'
import { loadAllContributions, loadUserContributions } from './util/contributions'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '@/graphql/resolver'
import { ContributionMessageType } from '@enum/ContributionMessageType'
import { AppDatabase } from 'database'
import { getLogger } from 'log4js'
import {
contributionFrontendLink,
loadAllContributions,
loadUserContributions,
} from './util/contributions'
import { getOpenCreations, getUserCreation, validateContribution } from './util/creations'
import { extractGraphQLFields } from './util/extractGraphQLFields'
import { findContributions } from './util/findContributions'
import { getLastTransaction } from './util/getLastTransaction'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.ContributionResolver`)
@Resolver(() => Contribution)
export class ContributionResolver {
@Authorized([RIGHTS.ADMIN_LIST_CONTRIBUTIONS])
@ -79,6 +87,8 @@ export class ContributionResolver {
const user = getUser(context)
const creations = await getUserCreation(user.id, clientTimezoneOffset)
const logger = createLogger()
logger.addContext('user', user.id)
logger.trace('creations', creations)
const contributionDateObj = new Date(contributionDate)
validateContribution(creations, amount, contributionDateObj, clientTimezoneOffset)
@ -191,7 +201,7 @@ export class ContributionResolver {
context,
)
const { contribution, contributionMessage } = await updateUnconfirmedContributionContext.run()
await getConnection().transaction(async (transactionalEntityManager: EntityManager) => {
await db.getDataSource().transaction(async (transactionalEntityManager: EntityManager) => {
await transactionalEntityManager.save(contribution)
if (contributionMessage) {
await transactionalEntityManager.save(contributionMessage)
@ -209,6 +219,8 @@ export class ContributionResolver {
@Args() { email, amount, memo, creationDate }: AdminCreateContributionArgs,
@Ctx() context: Context,
): Promise<Decimal[]> {
const logger = createLogger()
logger.addContext('admin', context.user?.id)
logger.info(
`adminCreateContribution(email=${email}, amount=${amount.toString()}, memo=${memo}, creationDate=${creationDate})`,
)
@ -260,6 +272,8 @@ export class ContributionResolver {
@Args() adminUpdateContributionArgs: AdminUpdateContributionArgs,
@Ctx() context: Context,
): Promise<AdminUpdateContribution> {
const logger = createLogger()
logger.addContext('contribution', adminUpdateContributionArgs.id)
const updateUnconfirmedContributionContext = new UpdateUnconfirmedContributionContext(
adminUpdateContributionArgs.id,
adminUpdateContributionArgs,
@ -267,11 +281,10 @@ export class ContributionResolver {
)
const { contribution, contributionMessage, createdByUserChangedByModerator } =
await updateUnconfirmedContributionContext.run()
await getConnection().transaction(async (transactionalEntityManager: EntityManager) => {
await db.getDataSource().transaction(async (transactionalEntityManager: EntityManager) => {
await transactionalEntityManager.save(contribution)
// TODO: move into specialized view or formatting for logging class
logger.debug('saved changed contribution', {
id: contribution.id,
amount: contribution.amount.toString(),
memo: contribution.memo,
contributionDate: contribution.contributionDate.toString(),
@ -282,7 +295,6 @@ export class ContributionResolver {
await transactionalEntityManager.save(contributionMessage)
// TODO: move into specialized view or formatting for logging class
logger.debug('save new contributionMessage', {
contributionId: contributionMessage.contributionId,
type: contributionMessage.type,
message: contributionMessage.message,
isModerator: contributionMessage.isModerator,
@ -317,6 +329,10 @@ export class ContributionResolver {
senderLastName: moderator.lastName,
contributionMemo: updateUnconfirmedContributionContext.getOldMemo(),
contributionMemoUpdated: contribution.memo,
contributionFrontendLink: await contributionFrontendLink(
contribution.id,
contribution.createdAt,
),
})
}
@ -403,6 +419,10 @@ export class ContributionResolver {
senderFirstName: moderator.firstName,
senderLastName: moderator.lastName,
contributionMemo: contribution.memo,
contributionFrontendLink: await contributionFrontendLink(
contribution.id,
contribution.createdAt,
),
})
return !!res
@ -414,6 +434,9 @@ export class ContributionResolver {
@Arg('id', () => Int) id: number,
@Ctx() context: Context,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('contribution', id)
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
@ -449,7 +472,7 @@ export class ContributionResolver {
)
const receivedCallDate = new Date()
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED')
@ -510,6 +533,10 @@ export class ContributionResolver {
senderLastName: moderatorUser.lastName,
contributionMemo: contribution.memo,
contributionAmount: contribution.amount,
contributionFrontendLink: await contributionFrontendLink(
contribution.id,
contribution.createdAt,
),
})
} catch (e) {
await queryRunner.rollbackTransaction()
@ -593,6 +620,10 @@ export class ContributionResolver {
senderFirstName: moderator.firstName,
senderLastName: moderator.lastName,
contributionMemo: contributionToUpdate.memo,
contributionFrontendLink: await contributionFrontendLink(
contributionToUpdate.id,
contributionToUpdate.createdAt,
),
})
return !!res

View File

@ -1,7 +1,7 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { User as DbUser } from 'database'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers'
@ -12,11 +12,11 @@ import { queryOptIn } from '@/seeds/graphql/queries'
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
CONFIG.EMAIL_CODE_VALID_TIME = 1440
@ -33,7 +33,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('EmailOptinCodes', () => {
@ -101,6 +101,7 @@ describe('EmailOptinCodes', () => {
describe('forgotPassword', () => {
it('throws an error', async () => {
await mutate({ mutation: forgotPassword, variables: { email: 'peter@lustig.de' } })
await expect(
mutate({ mutation: forgotPassword, variables: { email: 'peter@lustig.de' } }),
).resolves.toMatchObject({

View File

@ -10,8 +10,10 @@ import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import { getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { backendLogger as logger } from '@/server/logger'
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.GdtResolver`)
@Resolver()
export class GdtResolver {

View File

@ -2,15 +2,19 @@ import { Event as DbEvent, UserContact } from 'database'
import { GraphQLError } from 'graphql'
import { cleanDB, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { EventType } from '@/event/Events'
import { userFactory } from '@/seeds/factory/user'
import { login, subscribeNewsletter, unsubscribeNewsletter } from '@/seeds/graphql/mutations'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.KlicktippResolver`)
let testEnv: any
let mutate: any
let con: any
@ -24,7 +28,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('KlicktippResolver', () => {

View File

@ -9,7 +9,10 @@ import { SpaceList } from '@model/SpaceList'
import { HumHubClient } from '@/apis/humhub/HumHubClient'
import { RIGHTS } from '@/auth/RIGHTS'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.ProjectBrandingResolver`)
@Resolver(() => ProjectBranding)
export class ProjectBrandingResolver {

View File

@ -1,13 +1,14 @@
import { Transaction as DbTransaction, User as DbUser } from 'database'
import { AppDatabase, Transaction as DbTransaction, User as DbUser } from 'database'
import { Decimal } from 'decimal.js-light'
import { Authorized, FieldResolver, Query, Resolver } from 'type-graphql'
import { getConnection } from 'typeorm'
import { CommunityStatistics, DynamicStatisticsFields } from '@model/CommunityStatistics'
import { RIGHTS } from '@/auth/RIGHTS'
import { calculateDecay } from '@/util/decay'
const db = AppDatabase.getInstance()
@Resolver(() => CommunityStatistics)
export class StatisticsResolver {
@Authorized([RIGHTS.COMMUNITY_STATISTICS])
@ -33,7 +34,7 @@ export class StatisticsResolver {
@FieldResolver()
async totalGradidoCreated(): Promise<Decimal> {
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
try {
await queryRunner.connect()
const { totalGradidoCreated } = await queryRunner.manager
@ -50,7 +51,7 @@ export class StatisticsResolver {
@FieldResolver()
async totalGradidoDecayed(): Promise<Decimal> {
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
try {
await queryRunner.connect()
const { totalGradidoDecayed } = await queryRunner.manager
@ -72,7 +73,7 @@ export class StatisticsResolver {
const receivedCallDate = new Date()
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
try {
await queryRunner.connect()

View File

@ -8,11 +8,10 @@ import {
} from 'database'
import { Decimal } from 'decimal.js-light'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
import { cleanDB, resetEntity, resetToken, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
import { EventType } from '@/event/Events'
import { creations } from '@/seeds/creation/index'
@ -35,8 +34,12 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'config-schema/test/testSetup'
import { transactionLinkCode } from './TransactionLinkResolver'
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
jest.mock('@/password/EncryptorUtils')
// mock semaphore to allow use fake timers
@ -45,11 +48,11 @@ TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn())
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
let user: User
@ -66,7 +69,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('TransactionLinkResolver', () => {
@ -221,7 +224,7 @@ describe('TransactionLinkResolver', () => {
})
})
it('logs the error "User has not enough GDD"', () => {
expect(logger.error).toBeCalledWith('User has not enough GDD', expect.any(Number))
expect(logErrorLogger.error).toBeCalledWith('User has not enough GDD', expect.any(Number))
})
})
})
@ -273,11 +276,11 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "No contribution link found to given code"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'No contribution link found to given code',
'CL-123456',
)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('No contribution link found to given code'),
)
@ -317,8 +320,11 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Contribution link is not valid yet"', () => {
expect(logger.error).toBeCalledWith('Contribution link is not valid yet', validFrom)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Contribution link is not valid yet',
validFrom,
)
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('Contribution link is not valid yet'),
)
@ -356,8 +362,11 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Contribution link has unknown cycle"', () => {
expect(logger.error).toBeCalledWith('Contribution link has unknown cycle', 'INVALID')
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Contribution link has unknown cycle',
'INVALID',
)
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('Contribution link has unknown cycle'),
)
@ -395,8 +404,11 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Contribution link is no longer valid"', () => {
expect(logger.error).toBeCalledWith('Contribution link is no longer valid', validTo)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Contribution link is no longer valid',
validTo,
)
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('Contribution link is no longer valid'),
)
@ -491,7 +503,7 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Creation from contribution link was not successful"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error(
'The amount to be created exceeds the amount still available for this month',
@ -566,7 +578,7 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Creation from contribution link was not successful"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('Contribution link already redeemed today'),
)
@ -618,7 +630,7 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Creation from contribution link was not successful"', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Creation from contribution link was not successful',
new Error('Contribution link already redeemed today'),
)
@ -652,7 +664,7 @@ describe('TransactionLinkResolver', () => {
).resolves.toMatchObject({
errors: [new GraphQLError('Transaction link not found')],
})
expect(logger.error).toBeCalledWith('Transaction link not found', 'not-valid')
expect(logErrorLogger.error).toBeCalledWith('Transaction link not found', 'not-valid')
})
})
@ -723,7 +735,7 @@ describe('TransactionLinkResolver', () => {
).resolves.toMatchObject({
errors: [new GraphQLError('Cannot redeem own transaction link')],
})
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Cannot redeem own transaction link',
expect.any(Number),
)
@ -927,7 +939,7 @@ describe('TransactionLinkResolver', () => {
})
it('logs the error "Could not find requested User"', () => {
expect(logger.error).toBeCalledWith('Could not find requested User', -1)
expect(logErrorLogger.error).toBeCalledWith('Could not find requested User', -1)
})
})

View File

@ -15,6 +15,7 @@ import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
import { User } from '@model/User'
import { QueryLinkResult } from '@union/QueryLinkResult'
import {
AppDatabase,
Contribution as DbContribution,
ContributionLink as DbContributionLink,
Transaction as DbTransaction,
@ -23,7 +24,6 @@ import {
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql'
import { getConnection } from 'typeorm'
import { RIGHTS } from '@/auth/RIGHTS'
import { decode, encode, verify } from '@/auth/jwt/JWT'
@ -36,7 +36,6 @@ import {
} from '@/event/Events'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { TRANSACTION_LINK_LOCK } from '@/util/TRANSACTION_LINK_LOCK'
import { calculateDecay } from '@/util/decay'
@ -44,6 +43,8 @@ import { fullName } from '@/util/utilities'
import { calculateBalance } from '@/util/validate'
import { DisburseJwtPayloadType } from '@/auth/jwt/payloadtypes/DisburseJwtPayloadType'
import { Logger, getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { executeTransaction } from './TransactionResolver'
import {
getAuthenticatedCommunities,
@ -55,6 +56,8 @@ import { getLastTransaction } from './util/getLastTransaction'
import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector'
import { transactionLinkList } from './util/transactionLinkList'
const createLogger = () => getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.TransactionLinkResolver`)
// TODO: do not export, test it inside the resolver
export const transactionLinkCode = (date: Date): string => {
const time = date.getTime().toString(16)
@ -66,6 +69,7 @@ export const transactionLinkCode = (date: Date): string => {
}
const CODE_VALID_DAYS_DURATION = 14
const db = AppDatabase.getInstance()
export const transactionLinkExpireDate = (date: Date): Date => {
const validUntil = new Date(date)
@ -147,7 +151,9 @@ export class TransactionLinkResolver {
@Authorized([RIGHTS.QUERY_TRANSACTION_LINK])
@Query(() => QueryLinkResult)
async queryTransactionLink(@Arg('code') code: string): Promise<typeof QueryLinkResult> {
logger.debug('TransactionLinkResolver.queryTransactionLink... code=', code)
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.queryTransactionLink...')
if (code.match(/^CL-/)) {
const contributionLink = await DbContributionLink.findOneOrFail({
where: { code: code.replace('CL-', '') },
@ -183,7 +189,7 @@ export class TransactionLinkResolver {
return new TransactionLink(dbTransactionLink, new User(user), redeemedBy, communities)
} else {
// redeem jwt-token
return await this.queryRedeemJwtLink(code)
return await this.queryRedeemJwtLink(code, logger)
}
}
}
@ -194,6 +200,8 @@ export class TransactionLinkResolver {
@Arg('code', () => String) code: string,
@Ctx() context: Context,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
const clientTimezoneOffset = getClientTimezoneOffset(context)
// const homeCom = await DbCommunity.findOneOrFail({ where: { foreign: false } })
const user = getUser(context)
@ -203,7 +211,7 @@ export class TransactionLinkResolver {
try {
logger.info('redeem contribution link...')
const now = new Date()
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
try {
@ -378,6 +386,7 @@ export class TransactionLinkResolver {
transactionLink.memo,
linkedUser,
user,
logger,
transactionLink,
)
await EVENT_TRANSACTION_LINK_REDEEM(
@ -407,6 +416,8 @@ export class TransactionLinkResolver {
@Arg('alias', { nullable: true }) alias?: string,
@Arg('validUntil', { nullable: true }) validUntil?: string,
): Promise<string> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.queryRedeemJwt... args=', {
gradidoId,
senderCommunityUuid,
@ -455,6 +466,8 @@ export class TransactionLinkResolver {
@Arg('validUntil', { nullable: true }) validUntil?: string,
@Arg('recipientAlias', { nullable: true }) recipientAlias?: string,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.disburseTransactionLink... args=', {
senderGradidoId,
senderCommunityUuid,
@ -526,7 +539,7 @@ export class TransactionLinkResolver {
return transactionLinkList(paginated, filters, user)
}
async queryRedeemJwtLink(code: string): Promise<RedeemJwtLink> {
async queryRedeemJwtLink(code: string, logger: Logger): Promise<RedeemJwtLink> {
logger.debug('TransactionLinkResolver.queryRedeemJwtLink... redeem jwt-token found')
// decode token first to get the senderCommunityUuid as input for verify token
const decodedPayload = decode(code)
@ -651,6 +664,8 @@ export class TransactionLinkResolver {
validUntil: string,
recipientAlias: string,
): Promise<string> {
const logger = createLogger()
logger.addContext('code', code.substring(0, 6))
logger.debug('TransactionLinkResolver.createDisburseJwt... args=', {
senderCommunityUuid,
senderGradidoId,

View File

@ -8,13 +8,13 @@ import {
User,
} from 'database'
import { GraphQLError } from 'graphql'
import { Connection, In } from 'typeorm'
import { DataSource, In } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { logger } from '@test/testSetup'
import { CONFIG } from '@/config'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { EventType } from '@/event/Events'
import { SendCoinsClient } from '@/federation/client/1_0/SendCoinsClient'
import { SendCoinsArgs } from '@/federation/client/1_0/model/SendCoinsArgs'
@ -32,16 +32,19 @@ import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { getLogger } from 'config-schema/test/testSetup'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -477,8 +480,6 @@ describe('send coins', () => {
})
it('has wait till sendTransactionsToDltConnector created all dlt-transactions', () => {
expect(logger.info).toBeCalledWith('sendTransactionsToDltConnector...')
expect(dltTransactions).toEqual(
expect.arrayContaining([
expect.objectContaining({

View File

@ -1,4 +1,5 @@
import {
AppDatabase,
Community as DbCommunity,
PendingTransaction as DbPendingTransaction,
Transaction as dbTransaction,
@ -7,7 +8,7 @@ import {
} from 'database'
import { Decimal } from 'decimal.js-light'
import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql'
import { In, IsNull, getConnection } from 'typeorm'
import { In, IsNull } from 'typeorm'
import { Paginated } from '@arg/Paginated'
import { TransactionSendArgs } from '@arg/TransactionSendArgs'
@ -28,13 +29,14 @@ import { EVENT_TRANSACTION_RECEIVE, EVENT_TRANSACTION_SEND } from '@/event/Event
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { LogError } from '@/server/LogError'
import { Context, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { communityUser } from '@/util/communityUser'
import { fullName } from '@/util/utilities'
import { calculateBalance } from '@/util/validate'
import { virtualDecayTransaction, virtualLinkTransaction } from '@/util/virtualTransactions'
import { Logger, getLogger } from 'log4js'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { BalanceResolver } from './BalanceResolver'
import { GdtResolver } from './GdtResolver'
import { getCommunityByIdentifier, getCommunityName, isHomeCommunity } from './util/communities'
@ -49,15 +51,20 @@ import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConn
import { storeForeignUser } from './util/storeForeignUser'
import { transactionLinkSummary } from './util/transactionLinkSummary'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.TransactionResolver`)
export const executeTransaction = async (
amount: Decimal,
memo: string,
sender: dbUser,
recipient: dbUser,
logger: Logger,
transactionLink?: dbTransactionLink | null,
): Promise<boolean> => {
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
logger.info('executeTransaction', amount, memo, sender, recipient)
@ -96,7 +103,7 @@ export const executeTransaction = async (
throw new LogError('User has not enough GDD or amount is < 0', sendBalance)
}
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
logger.debug(`open Transaction to write...`)
@ -225,9 +232,9 @@ export class TransactionResolver {
): Promise<TransactionList> {
const now = new Date()
const user = getUser(context)
const logger = createLogger()
logger.addContext('user', user.id)
logger.info(`transactionList(user=${user.firstName}.${user.lastName}, ${user.emailId})`)
logger.info(`transactionList`)
let balanceGDTPromise: Promise<number | null> = Promise.resolve(null)
if (CONFIG.GDT_ACTIVE) {
@ -237,7 +244,7 @@ export class TransactionResolver {
// find current balance
const lastTransaction = await getLastTransaction(user.id)
logger.debug(`lastTransaction=${lastTransaction}`)
logger.debug(`lastTransaction=${lastTransaction?.id}`)
const balanceResolver = new BalanceResolver()
context.lastTransaction = lastTransaction
@ -285,10 +292,10 @@ export class TransactionResolver {
},
],
})
logger.debug('found dbRemoteUser:', dbRemoteUser)
logger.debug(`found dbRemoteUser: ${dbRemoteUser?.id}`)
const remoteUser = new User(dbRemoteUser)
if (dbRemoteUser === null) {
logger.debug('no dbRemoteUser found, init from tx:', transaction)
logger.debug(`no dbRemoteUser found, init from tx: ${transaction.id}`)
if (transaction.linkedUserCommunityUuid !== null) {
remoteUser.communityUuid = transaction.linkedUserCommunityUuid
}
@ -309,7 +316,10 @@ export class TransactionResolver {
}
}
logger.debug(`involvedUserIds=`, involvedUserIds)
logger.debug(`involvedRemoteUsers=`, involvedRemoteUsers)
logger.debug(
`involvedRemoteUsers=`,
involvedRemoteUsers.map((u) => u.id),
)
// We need to show the name for deleted users for old transactions
const involvedDbUsers = await dbUser.find({
@ -318,7 +328,10 @@ export class TransactionResolver {
relations: ['emailContact'],
})
const involvedUsers = involvedDbUsers.map((u) => new User(u))
logger.debug(`involvedUsers=`, involvedUsers)
logger.debug(
`involvedUsers=`,
involvedUsers.map((u) => u.id),
)
const self = new User(user)
const transactions: Transaction[] = []
@ -329,11 +342,11 @@ export class TransactionResolver {
context.linkCount = transactionLinkcount
logger.debug(`transactionLinkcount=${transactionLinkcount}`)
context.sumHoldAvailableAmount = sumHoldAvailableAmount
logger.debug(`sumHoldAvailableAmount=${sumHoldAvailableAmount}`)
logger.debug(`sumHoldAvailableAmount=${sumHoldAvailableAmount.toString()}`)
// decay & link transactions
if (currentPage === 1 && order === Order.DESC) {
logger.debug(`currentPage == 1: transactions=${transactions}`)
logger.debug(`currentPage == 1: transactions=${transactions.map((t) => t.id)}`)
// The virtual decay is always on the booked amount, not including the generated, not yet booked links,
// since the decay is substantially different when the amount is less
transactions.push(
@ -345,7 +358,7 @@ export class TransactionResolver {
sumHoldAvailableAmount,
),
)
logger.debug(`transactions=${transactions}`)
logger.debug(`transactions=${transactions.map((t) => t.id)}`)
// virtual transaction for pending transaction-links sum
if (sumHoldAvailableAmount.isZero()) {
@ -370,7 +383,7 @@ export class TransactionResolver {
)
}
} else if (sumHoldAvailableAmount.greaterThan(0)) {
logger.debug(`sumHoldAvailableAmount > 0: transactions=${transactions}`)
logger.debug(`sumHoldAvailableAmount > 0: transactions=${transactions.map((t) => t.id)}`)
transactions.push(
virtualLinkTransaction(
lastTransaction.balance.minus(sumHoldAvailableAmount.toString()),
@ -383,7 +396,7 @@ export class TransactionResolver {
(userTransactions.length && userTransactions[0].balance) || new Decimal(0),
),
)
logger.debug(`transactions=`, transactions)
logger.debug(`transactions=${transactions.map((t) => t.id)}`)
}
}
@ -398,19 +411,22 @@ export class TransactionResolver {
let linkedUser: User | undefined
if ((userTransaction.typeId as TransactionTypeId) === TransactionTypeId.CREATION) {
linkedUser = communityUser
logger.debug('CREATION-linkedUser=', linkedUser)
logger.debug(`CREATION-linkedUser=${linkedUser.id}`)
} else if (userTransaction.linkedUserId) {
linkedUser = involvedUsers.find((u) => u.id === userTransaction.linkedUserId)
logger.debug('local linkedUser=', linkedUser)
logger.debug(`local linkedUser=${linkedUser?.id}`)
} else if (userTransaction.linkedUserCommunityUuid) {
linkedUser = involvedRemoteUsers.find(
(u) => u.gradidoID === userTransaction.linkedUserGradidoID,
)
logger.debug('remote linkedUser=', linkedUser)
logger.debug(`remote linkedUser=${linkedUser?.id}`)
}
transactions.push(new Transaction(userTransaction, self, linkedUser))
})
logger.debug(`TransactionTypeId.CREATION: transactions=`, transactions)
logger.debug(
`TransactionTypeId.CREATION: transactions=`,
transactions.map((t) => t.id),
)
transactions.forEach((transaction: Transaction) => {
if (transaction.typeId !== TransactionTypeId.DECAY) {
@ -436,6 +452,9 @@ export class TransactionResolver {
{ recipientCommunityIdentifier, recipientIdentifier, amount, memo }: TransactionSendArgs,
@Ctx() context: Context,
): Promise<boolean> {
const logger = createLogger()
logger.addContext('from', context.user?.id)
logger.addContext('amount', amount.toString())
logger.debug(
`sendCoins(recipientCommunityIdentifier=${recipientCommunityIdentifier}, recipientIdentifier=${recipientIdentifier}, amount=${amount}, memo=${memo})`,
)
@ -451,28 +470,28 @@ export class TransactionResolver {
if (!recipientUser) {
throw new LogError('The recipient user was not found', recipientUser)
}
logger.addContext('to', recipientUser?.id)
if (recipientUser.foreign) {
throw new LogError('Found foreign recipient user for a local transaction:', recipientUser)
}
await executeTransaction(amount, memo, senderUser, recipientUser)
logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser)
await executeTransaction(amount, memo, senderUser, recipientUser, logger)
logger.info('successful executeTransaction')
} else {
// processing a x-community sendCoins
logger.debug('X-Com: processing a x-community transaction...')
logger.info('X-Com: processing a x-community transaction...')
if (!CONFIG.FEDERATION_XCOM_SENDCOINS_ENABLED) {
throw new LogError('X-Community sendCoins disabled per configuration!')
}
const recipCom = await getCommunityByIdentifier(recipientCommunityIdentifier)
logger.debug('recipient commuity: ', recipCom)
logger.debug('recipient community: ', recipCom?.id)
if (recipCom === null) {
throw new LogError(
'no recipient commuity found for identifier:',
recipientCommunityIdentifier,
`no recipient community found for identifier: ${recipientCommunityIdentifier}`,
)
}
if (recipCom !== null && recipCom.authenticatedAt === null) {
throw new LogError('recipient commuity is connected, but still not authenticated yet!')
throw new LogError('recipient community is connected, but still not authenticated yet!')
}
let pendingResult: SendCoinsResult
let committingResult: SendCoinsResult

View File

@ -9,7 +9,7 @@ import {
UserRole,
} from 'database'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid'
import { GmsPublishLocationType } from '@enum/GmsPublishLocationType'
@ -20,7 +20,7 @@ import { UserContactType } from '@enum/UserContactType'
import { ContributionLink } from '@model/ContributionLink'
import { Location } from '@model/Location'
import { cleanDB, headerPushMock, resetToken, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController'
import { CONFIG } from '@/config'
@ -67,6 +67,9 @@ import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { printTimeDuration } from '@/util/time'
import { objectValuesToArray } from '@/util/utilities'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { clearLogs, getLogger, printLogs } from 'config-schema/test/testSetup'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '.'
import { Location2Point } from './util/Location2Point'
jest.mock('@/apis/humhub/HumHubClient')
@ -93,17 +96,20 @@ jest.mock('@/apis/KlicktippController', () => {
}
})
const logger = getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.UserResolver`)
const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
CONFIG.EMAIL_CODE_REQUEST_TIME = 10
let admin: User
let user: User
let mutate: ApolloServerTestClient['mutate']
let query: ApolloServerTestClient['query']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -117,7 +123,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('UserResolver', () => {
@ -275,7 +281,8 @@ describe('UserResolver', () => {
})
it('logs an info', () => {
expect(logger.info).toBeCalledWith('User already exists with this email=peter@lustig.de')
expect(logger.info).toBeCalledWith('User already exists')
expect(logger.addContext).toBeCalledWith('user', user[0].id)
})
it('sends an account multi registration email', () => {
@ -642,7 +649,7 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
)
})
@ -672,7 +679,7 @@ describe('UserResolver', () => {
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('Could not login with emailVerificationCode')
expect(logger.warn).toBeCalledWith('invalid emailVerificationCode=not valid')
})
})
})
@ -691,9 +698,13 @@ describe('UserResolver', () => {
})
describe('no users in database', () => {
beforeAll(() => {
clearLogs()
})
it('throws an error', async () => {
jest.clearAllMocks()
expect(await mutate({ mutation: login, variables })).toEqual(
const result = await mutate({ mutation: login, variables })
expect(result).toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
}),
@ -701,7 +712,10 @@ describe('UserResolver', () => {
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
printLogs()
expect(logger.warn).toBeCalledWith(
`findUserByEmail failed, user with email=${variables.email} not found`,
)
})
})
@ -782,8 +796,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('No user with this credentials', variables.email)
it('logs warning before error is thrown', () => {
expect(logger.warn).toBeCalledWith('login failed, wrong password')
})
})
@ -813,14 +827,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'This user was permanently deleted. Contact support for questions',
expect.objectContaining({
firstName: stephenHawking.firstName,
lastName: stephenHawking.lastName,
}),
)
it('logs warning before error is thrown', () => {
expect(logger.warn).toBeCalledWith('login failed, user was deleted')
})
})
@ -848,14 +856,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'The Users email is not validate yet',
expect.objectContaining({
firstName: garrickOllivander.firstName,
lastName: garrickOllivander.lastName,
}),
)
it('logs warning before error is thrown', () => {
expect(logger.warn).toBeCalledWith('login failed, user email not checked')
})
})
@ -881,14 +883,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'The User has not set a password yet',
expect.objectContaining({
firstName: bibiBloxberg.firstName,
lastName: bibiBloxberg.lastName,
}),
)
it('logs warning before error is thrown', () => {
expect(logger.warn).toBeCalledWith('login failed, user has not set a password yet')
})
})
})
@ -1114,7 +1110,7 @@ describe('UserResolver', () => {
})
describe('request reset password again', () => {
it('thows an error', async () => {
it('throws an error', async () => {
CONFIG.EMAIL_CODE_REQUEST_TIME = emailCodeRequestTime
await expect(mutate({ mutation: forgotPassword, variables })).resolves.toEqual(
expect.objectContaining({
@ -1123,8 +1119,10 @@ describe('UserResolver', () => {
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`Email already sent less than 10 minutes ago`)
it('logs warning before throwing error', () => {
expect(logger.warn).toBeCalledWith(
'email already sent 0 minutes ago, min wait time: 10 minutes',
)
})
})
})
@ -1374,13 +1372,13 @@ describe('UserResolver', () => {
}),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('Given language is not a valid language')],
errors: [new GraphQLError('Given language is not a valid language or not supported')],
}),
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith('Given language is not a valid language', 'not-valid')
expect(logger.warn).toBeCalledWith('try to set unsupported language', 'not-valid')
})
})
@ -1403,8 +1401,8 @@ describe('UserResolver', () => {
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(`Old password is invalid`)
it('logs if logger is in debug mode', () => {
expect(logger.debug).toBeCalledWith(`old password is invalid`)
})
})
@ -1430,10 +1428,8 @@ describe('UserResolver', () => {
)
})
it('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
)
it('logs warning', () => {
expect(logger.warn).toBeCalledWith('try to set invalid password')
})
})
@ -1490,10 +1486,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
)
it('log warning', () => {
expect(logger.warn).toBeCalledWith('login failed, wrong password')
})
})
})
@ -1776,7 +1770,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
expect(logErrorLogger.error).toBeCalledWith(
'Could not find user with given ID',
admin.id + 1,
)
})
})
@ -1892,7 +1889,9 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Administrator can not change his own role')
expect(logErrorLogger.error).toBeCalledWith(
'Administrator can not change his own role',
)
})
})
@ -1937,7 +1936,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('User already has role=', RoleNames.ADMIN)
expect(logErrorLogger.error).toBeCalledWith(
'User already has role=',
RoleNames.ADMIN,
)
})
})
@ -1961,7 +1963,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('User already has role=', RoleNames.MODERATOR)
expect(logErrorLogger.error).toBeCalledWith(
'User already has role=',
RoleNames.MODERATOR,
)
})
})
@ -1982,7 +1987,7 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('User is already an usual user')
expect(logErrorLogger.error).toBeCalledWith('User is already an usual user')
})
})
})
@ -2055,7 +2060,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
expect(logErrorLogger.error).toBeCalledWith(
'Could not find user with given ID',
admin.id + 1,
)
})
})
@ -2072,7 +2080,7 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Moderator can not delete his own account')
expect(logErrorLogger.error).toBeCalledWith('Moderator can not delete his own account')
})
})
@ -2125,7 +2133,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find user with given ID', user.id)
expect(logErrorLogger.error).toBeCalledWith(
'Could not find user with given ID',
user.id,
)
})
})
})
@ -2201,7 +2212,9 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('No user with this credentials', 'invalid')
expect(logger.warn).toBeCalledWith(
'findUserByEmail failed, user with email=invalid not found',
)
})
})
@ -2218,11 +2231,8 @@ describe('UserResolver', () => {
)
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith(
'User with given email contact is deleted',
'stephen@hawking.uk',
)
it('log warning', () => {
expect(logger.warn).toBeCalledWith('call for activation of deleted user')
})
})
@ -2348,7 +2358,10 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('Could not find user with given ID', admin.id + 1)
expect(logErrorLogger.error).toBeCalledWith(
'Could not find user with given ID',
admin.id + 1,
)
})
})
@ -2369,7 +2382,7 @@ describe('UserResolver', () => {
})
it('logs the error thrown', () => {
expect(logger.error).toBeCalledWith('User is not deleted')
expect(logErrorLogger.error).toBeCalledWith('User is not deleted')
})
describe('undelete deleted user', () => {
@ -2682,7 +2695,7 @@ describe('UserResolver', () => {
errors: [new GraphQLError('401 Unauthorized')],
}),
)
expect(logger.error).toBeCalledWith('401 Unauthorized')
expect(logErrorLogger.error).toBeCalledWith('401 Unauthorized')
})
})
@ -2720,7 +2733,7 @@ describe('UserResolver', () => {
errors: [new GraphQLError('Unknown identifier type')],
}),
)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'Unknown identifier type',
'identifier_is_no_valid_alias!',
)
@ -2742,7 +2755,7 @@ describe('UserResolver', () => {
errors: [new GraphQLError('No user found to given identifier(s)')],
}),
)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'No user found to given identifier(s)',
uuid,
homeCom1.communityUuid,
@ -2765,7 +2778,7 @@ describe('UserResolver', () => {
errors: [new GraphQLError('No user with this credentials')],
}),
)
expect(logger.error).toBeCalledWith(
expect(logErrorLogger.error).toBeCalledWith(
'No user with this credentials',
'bibi@bloxberg.de',
foreignCom1.communityUuid,

View File

@ -1,4 +1,5 @@
import {
AppDatabase,
ContributionLink as DbContributionLink,
TransactionLink as DbTransactionLink,
User as DbUser,
@ -22,7 +23,7 @@ import {
Root,
} from 'type-graphql'
import { IRestResponse } from 'typed-rest-client'
import { In, Point, getConnection } from 'typeorm'
import { EntityNotFoundError, In, Point } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { UserArgs } from '@arg//UserArgs'
@ -79,15 +80,16 @@ import { isValidPassword } from '@/password/EncryptorUtils'
import { encryptPassword, verifyPassword } from '@/password/PasswordEncryptor'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset, getUser } from '@/server/context'
import { backendLogger as logger } from '@/server/logger'
import { communityDbUser } from '@/util/communityUser'
import { hasElopageBuys } from '@/util/hasElopageBuys'
import { getTimeDurationObject, printTimeDuration } from '@/util/time'
import { durationInMinutesFromDates, getTimeDurationObject, printTimeDuration } from '@/util/time'
import { delay } from '@/util/utilities'
import random from 'random-bigint'
import { randombytes_random } from 'sodium-native'
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '@/graphql/resolver'
import { Logger, getLogger } from 'log4js'
import { FULL_CREATION_AVAILABLE } from './const/const'
import { Location2Point, Point2Location } from './util/Location2Point'
import { authenticateGmsUserPlayground } from './util/authenticateGmsUserPlayground'
@ -105,11 +107,13 @@ import { validateAlias } from './util/validateAlias'
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
const DEFAULT_LANGUAGE = 'de'
const db = AppDatabase.getInstance()
const createLogger = () => getLogger(`${LOG4JS_RESOLVER_CATEGORY_NAME}.UserResolver`)
const isLanguage = (language: string): boolean => {
return LANGUAGES.includes(language)
}
const newEmailContact = (email: string, userId: number): DbUserContact => {
const newEmailContact = (email: string, userId: number, logger: Logger): DbUserContact => {
logger.trace(`newEmailContact...`)
const emailContact = new DbUserContact()
emailContact.email = email
@ -122,12 +126,12 @@ const newEmailContact = (email: string, userId: number): DbUserContact => {
return emailContact
}
export const activationLink = (verificationCode: string): string => {
export const activationLink = (verificationCode: string, logger: Logger): string => {
logger.debug(`activationLink(${verificationCode})...`)
return CONFIG.EMAIL_LINK_SETPASSWORD + verificationCode.toString()
}
const newGradidoID = async (): Promise<string> => {
const newGradidoID = async (logger: Logger): Promise<string> => {
let gradidoId: string
let countIds: number
do {
@ -145,14 +149,16 @@ export class UserResolver {
@Authorized([RIGHTS.VERIFY_LOGIN])
@Query(() => User)
async verifyLogin(@Ctx() context: Context): Promise<User> {
const logger = createLogger()
logger.info('verifyLogin...')
// TODO refactor and do not have duplicate code with login(see below)
const userEntity = getUser(context)
logger.addContext('user', userEntity.id)
const user = new User(userEntity)
// Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage(context)
logger.debug(`verifyLogin... successful: ${user.firstName}.${user.lastName}`)
logger.debug(`verifyLogin... successful`)
user.klickTipp = await getKlicktippState(userEntity.emailContact.email)
return user
}
@ -163,31 +169,39 @@ export class UserResolver {
@Args() { email, password, publisherId, project }: UnsecureLoginArgs,
@Ctx() context: Context,
): Promise<User> {
logger.info(`login with ${email}, ***, ${publisherId}, project=${project} ...`)
const logger = createLogger()
logger.info(`login with ${email.substring(0, 3)}..., project=${project} ...`)
email = email.trim().toLowerCase()
let dbUser: DbUser
try {
dbUser = await findUserByEmail(email)
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
logger.addContext('user', dbUser.id)
logger.trace('user before login', new UserLoggingView(dbUser))
} catch (e) {
// simulate delay which occur on password encryption 650 ms +- 50 rnd
await delay(650 + Math.floor(Math.random() * 101) - 50)
throw e
}
// TODO: discuss need we logging all this cases?
if (dbUser.deletedAt) {
throw new LogError('This user was permanently deleted. Contact support for questions', dbUser)
logger.warn('login failed, user was deleted')
throw new Error('This user was permanently deleted. Contact support for questions')
}
if (!dbUser.emailContact.emailChecked) {
throw new LogError('The Users email is not validate yet', dbUser)
logger.warn('login failed, user email not checked')
throw new Error('The Users email is not validate yet')
}
// TODO: at least in test this does not work since `dbUser.password = 0` and `BigInto(0) = 0n`
if (dbUser.password === BigInt(0)) {
// TODO we want to catch this on the frontend and ask the user to check his emails or resend code
throw new LogError('The User has not set a password yet', dbUser)
logger.warn('login failed, user has not set a password yet')
throw new Error('The User has not set a password yet')
}
if (!(await verifyPassword(dbUser, password))) {
throw new LogError('No user with this credentials', dbUser)
logger.warn('login failed, wrong password')
throw new Error('No user with this credentials')
}
// request to humhub and klicktipp run in parallel
@ -215,17 +229,14 @@ export class UserResolver {
dbUser.password = await encryptPassword(dbUser, password)
await dbUser.save()
}
// add pubKey in logger-context for layout-pattern X{user} to print it in each logging message
logger.addContext('user', dbUser.id)
logger.debug('validation of login credentials successful...')
const user = new User(dbUser)
logger.debug(`user= ${JSON.stringify(user, null, 2)}`)
i18n.setLocale(user.language)
// Elopage Status & Stored PublisherId
user.hasElopage = await this.hasElopage({ ...context, user: dbUser })
logger.info('user.hasElopage', user.hasElopage)
logger.debug('user.hasElopage', user.hasElopage)
if (!user.hasElopage && publisherId) {
user.publisherId = publisherId
dbUser.publisherId = publisherId
@ -239,7 +250,7 @@ export class UserResolver {
await EVENT_USER_LOGIN(dbUser)
const projectBranding = await projectBrandingPromise
logger.debug('project branding: ', projectBranding)
logger.debug('project branding: ', projectBranding?.id)
// load humhub state
if (humhubUserPromise) {
try {
@ -258,7 +269,8 @@ export class UserResolver {
}
}
user.klickTipp = await klicktippStatePromise
logger.info(`successful Login: ${JSON.stringify(user, null, 2)}`)
logger.info('successful Login')
logger.trace('user after login', new UserLoggingView(dbUser))
return user
}
@ -266,8 +278,6 @@ export class UserResolver {
@Mutation(() => Boolean)
async logout(@Ctx() context: Context): Promise<boolean> {
await EVENT_USER_LOGOUT(getUser(context))
// remove user from logger context
logger.addContext('user', 'unknown')
return true
}
@ -286,10 +296,24 @@ export class UserResolver {
project = null,
}: CreateUserArgs,
): Promise<User> {
logger.addContext('user', 'unknown')
logger.info(
`createUser(email=${email}, firstName=${firstName}, lastName=${lastName}, language=${language}, publisherId=${publisherId}, redeemCode=${redeemCode}, project=${project})`,
)
const logger = createLogger()
const shortEmail = email.substring(0, 3)
logger.addContext('email', shortEmail)
const shortRedeemCode = redeemCode?.substring(0, 6)
const infos = []
infos.push(`language=${language}`)
if (publisherId) {
infos.push(`publisherId=${publisherId}`)
}
if (redeemCode) {
infos.push(`redeemCode=${shortRedeemCode}`)
}
if (project) {
infos.push(`project=${project}`)
}
logger.info(`createUser(${infos.join(', ')})`)
// TODO: wrong default value (should be null), how does graphql work here? Is it an required field?
// default int publisher_id = 0;
@ -303,15 +327,16 @@ export class UserResolver {
email = email.trim().toLowerCase()
if (await checkEmailExists(email)) {
const foundUser = await findUserByEmail(email)
logger.info('DbUser.findOne', email, foundUser)
logger.info('DbUser.findOne', foundUser.id)
if (foundUser) {
logger.addContext('user', foundUser.id)
logger.removeContext('email')
// ATTENTION: this logger-message will be exactly expected during tests, next line
logger.info(`User already exists with this email=${email}`)
logger.info(`User already exists`)
logger.info(
`Specified username when trying to register multiple times with this email: firstName=${firstName}, lastName=${lastName}`,
`Specified username when trying to register multiple times with this email: firstName=${firstName.substring(0, 4)}, lastName=${lastName.substring(0, 4)}`,
)
// TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent.
const user = new User(communityDbUser)
user.id = randombytes_random() % (2048 * 16) // TODO: for a better faking derive id from email so that it will be always the same id when the same email comes in?
@ -323,7 +348,7 @@ export class UserResolver {
if (alias && (await validateAlias(alias))) {
user.alias = alias
}
logger.debug('partly faked user', user)
logger.debug('partly faked user', { id: user.id, gradidoID: user.gradidoID })
await sendAccountMultiRegistrationEmail({
firstName: foundUser.firstName, // this is the real name of the email owner, but just "firstName" would be the name of the new registrant which shall not be passed to the outside
@ -333,9 +358,6 @@ export class UserResolver {
})
await EVENT_EMAIL_ACCOUNT_MULTIREGISTRATION(foundUser)
logger.info(
`sendAccountMultiRegistrationEmail by ${firstName} ${lastName} to ${foundUser.firstName} ${foundUser.lastName} <${email}>`,
)
/* uncomment this, when you need the activation link on the console */
// In case EMails are disabled log the activation link for the user
logger.info('createUser() faked and send multi registration mail...')
@ -350,7 +372,7 @@ export class UserResolver {
select: { logoUrl: true, spaceId: true },
})
}
const gradidoID = await newGradidoID()
const gradidoID = await newGradidoID(logger)
const eventRegisterRedeem = Event(
EventType.USER_REGISTER_REDEEM,
@ -373,28 +395,28 @@ export class UserResolver {
}
dbUser.publisherId = publisherId ?? 0
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
logger.debug('new dbUser', dbUser)
logger.debug('new dbUser', new UserLoggingView(dbUser))
if (redeemCode) {
if (redeemCode.match(/^CL-/)) {
const contributionLink = await DbContributionLink.findOne({
where: { code: redeemCode.replace('CL-', '') },
})
logger.info('redeemCode found contributionLink', contributionLink)
if (contributionLink) {
logger.info('redeemCode found contributionLink', contributionLink.id)
dbUser.contributionLinkId = contributionLink.id
eventRegisterRedeem.involvedContributionLink = contributionLink
}
} else {
const transactionLink = await DbTransactionLink.findOne({ where: { code: redeemCode } })
logger.info('redeemCode found transactionLink', transactionLink)
if (transactionLink) {
logger.info('redeemCode found transactionLink', transactionLink.id)
dbUser.referrerId = transactionLink.userId
eventRegisterRedeem.involvedTransactionLink = transactionLink
}
}
}
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
let projectBranding: ProjectBranding | null | undefined
@ -402,7 +424,7 @@ export class UserResolver {
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
throw new LogError('Error while saving dbUser', error)
})
let emailContact = newEmailContact(email, dbUser.id)
let emailContact = newEmailContact(email, dbUser.id, logger)
emailContact = await queryRunner.manager.save(emailContact).catch((error) => {
throw new LogError('Error while saving user email contact', error)
})
@ -429,7 +451,7 @@ export class UserResolver {
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
logoUrl: projectBranding?.logoUrl,
})
logger.info(`sendAccountActivationEmail of ${firstName}.${lastName} to ${email}`)
logger.info('sendAccountActivationEmail')
await EVENT_EMAIL_CONFIRMATION(dbUser)
@ -483,18 +505,33 @@ export class UserResolver {
@Authorized([RIGHTS.SEND_RESET_PASSWORD_EMAIL])
@Mutation(() => Boolean)
async forgotPassword(@Arg('email') email: string): Promise<boolean> {
logger.addContext('user', 'unknown')
logger.info(`forgotPassword(${email})...`)
const logger = createLogger()
const shortEmail = email.substring(0, 3)
logger.addContext('email', shortEmail)
logger.info('forgotPassword...')
email = email.trim().toLowerCase()
const user = await findUserByEmail(email).catch((error) => {
logger.warn(`fail on find UserContact per ${email} because: ${error}`)
})
let user: DbUser
try {
user = await findUserByEmail(email)
logger.removeContext('email')
logger.addContext('user', user.id)
} catch (_e) {
logger.warn(`fail on find UserContact`)
return true
}
if (!user || user.deletedAt) {
logger.warn(`no user found with ${email}`)
if (user.deletedAt) {
logger.warn(`user was deleted`)
return true
}
if (!canEmailResend(user.emailContact.updatedAt || user.emailContact.createdAt)) {
const diff = durationInMinutesFromDates(
user.emailContact.updatedAt || user.emailContact.createdAt,
new Date(),
)
logger.warn(
`email already sent ${printTimeDuration(diff)} ago, min wait time: ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)}`,
)
throw new LogError(
`Email already sent less than ${printTimeDuration(CONFIG.EMAIL_CODE_REQUEST_TIME)} ago`,
)
@ -505,21 +542,19 @@ export class UserResolver {
user.emailContact.emailVerificationCode = random(64).toString()
user.emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_RESET_PASSWORD
await user.emailContact.save().catch(() => {
throw new LogError('Unable to save email verification code', user.emailContact)
throw new LogError('Unable to save email verification code', user.emailContact.id)
})
logger.info('optInCode for', email, user.emailContact)
await sendResetPasswordEmail({
firstName: user.firstName,
lastName: user.lastName,
email,
language: user.language,
resetLink: activationLink(user.emailContact.emailVerificationCode),
resetLink: activationLink(user.emailContact.emailVerificationCode, logger),
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
})
logger.info(`forgotPassword(${email}) successful...`)
logger.info(`forgotPassword successful...`)
await EVENT_EMAIL_FORGOT_PASSWORD(user)
return true
@ -531,7 +566,8 @@ export class UserResolver {
@Arg('code') code: string,
@Arg('password') password: string,
): Promise<boolean> {
logger.info(`setPassword(${code}, ***)...`)
const logger = createLogger()
logger.info(`setPassword...`)
// Validate Password
if (!isValidPassword(password)) {
throw new LogError(
@ -543,8 +579,11 @@ export class UserResolver {
where: { emailVerificationCode: code },
relations: ['user'],
}).catch(() => {
throw new LogError('Could not login with emailVerificationCode')
// code wasn't in db, so we can write it into log without hesitation
logger.warn(`invalid emailVerificationCode=${code}`)
throw new Error('Could not login with emailVerificationCode')
})
logger.addContext('user', userContact.user.id)
logger.debug('userContact loaded...')
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
@ -566,7 +605,7 @@ export class UserResolver {
user.password = await encryptPassword(user, password)
logger.debug('User credentials updated ...')
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
@ -594,9 +633,7 @@ export class UserResolver {
if ((userContact.emailOptInTypeId as OptInType) === OptInType.EMAIL_OPT_IN_REGISTER) {
try {
await subscribe(userContact.email, user.language, user.firstName, user.lastName)
logger.debug(
`subscribe(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
)
logger.debug('Success subscribe to klicktipp')
} catch (e) {
logger.error('Error subscribing to klicktipp', e)
}
@ -609,18 +646,21 @@ export class UserResolver {
@Authorized([RIGHTS.QUERY_OPT_IN])
@Query(() => Boolean)
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
logger.info(`queryOptIn(${optIn})...`)
const logger = createLogger()
logger.addContext('optIn', optIn.substring(0, 4))
logger.info(`queryOptIn...`)
const userContact = await DbUserContact.findOneOrFail({
where: { emailVerificationCode: optIn },
})
logger.debug('found optInCode', userContact)
logger.addContext('user', userContact.userId)
logger.debug('found optInCode', userContact.id)
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
throw new LogError(
`Email was sent more than ${printTimeDuration(CONFIG.EMAIL_CODE_VALID_TIME)} ago`,
)
}
logger.info(`queryOptIn(${optIn}) successful...`)
logger.info(`queryOptIn successful...`)
return true
}
@ -657,10 +697,27 @@ export class UserResolver {
gmsLocation,
gmsPublishLocation,
} = updateUserInfosArgs
logger.info(
`updateUserInfos(${firstName}, ${lastName}, ${alias}, ${language}, ***, ***, ${hideAmountGDD}, ${hideAmountGDT}, ${gmsAllowed}, ${gmsPublishName}, ${gmsLocation}, ${gmsPublishLocation})...`,
)
const user = getUser(context)
const logger = createLogger()
logger.addContext('user', user.id)
// log only if a value is set
logger.info(`updateUserInfos...`, {
firstName: firstName !== undefined,
lastName: lastName !== undefined,
alias: alias !== undefined,
language: language !== undefined,
password: password !== undefined,
passwordNew: passwordNew !== undefined,
hideAmountGDD: hideAmountGDD !== undefined,
hideAmountGDT: hideAmountGDT !== undefined,
humhubAllowed: humhubAllowed !== undefined,
gmsAllowed: gmsAllowed !== undefined,
gmsPublishName: gmsPublishName !== undefined,
humhubPublishName: humhubPublishName !== undefined,
gmsLocation: gmsLocation !== undefined,
gmsPublishLocation: gmsPublishLocation !== undefined,
})
const updateUserInGMS = compareGmsRelevantUserSettings(user, updateUserInfosArgs)
const publishNameLogic = new PublishNameLogic(user)
const oldHumhubUsername = publishNameLogic.getUserIdentifier(
@ -683,7 +740,8 @@ export class UserResolver {
if (language) {
if (!isLanguage(language)) {
throw new LogError('Given language is not a valid language', language)
logger.warn('try to set unsupported language', language)
throw new LogError('Given language is not a valid language or not supported')
}
user.language = language
i18n.setLocale(language)
@ -692,12 +750,15 @@ export class UserResolver {
if (password && passwordNew) {
// Validate Password
if (!isValidPassword(passwordNew)) {
throw new LogError(
// TODO: log which rule(s) wasn't met
logger.warn('try to set invalid password')
throw new Error(
'Please enter a valid password with at least 8 characters, upper and lower case letters, at least one number and one special character!',
)
}
if (!(await verifyPassword(user, password))) {
logger.debug('old password is invalid')
throw new LogError(`Old password is invalid`)
}
@ -735,7 +796,7 @@ export class UserResolver {
// } catch (err) {
// console.log('error:', err)
// }
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
@ -761,7 +822,7 @@ export class UserResolver {
logger.debug(`changed user-settings relevant for gms-user update...`)
const homeCom = await getHomeCommunity()
if (homeCom.gmsApiKey !== null) {
logger.debug(`send User to Gms...`, user)
logger.debug(`send User to Gms...`)
await sendUserToGms(user, homeCom)
logger.debug(`sendUserToGms successfully.`)
}
@ -783,18 +844,22 @@ export class UserResolver {
@Authorized([RIGHTS.HAS_ELOPAGE])
@Query(() => Boolean)
async hasElopage(@Ctx() context: Context): Promise<boolean> {
logger.info(`hasElopage()...`)
const userEntity = getUser(context)
const elopageBuys = hasElopageBuys(userEntity.emailContact.email)
logger.debug('has ElopageBuys', elopageBuys)
const dbUser = getUser(context)
const logger = createLogger()
logger.addContext('user', dbUser.id)
const elopageBuys = await hasElopageBuys(dbUser.emailContact.email)
logger.info(`has Elopage (ablify): ${elopageBuys}`)
return elopageBuys
}
@Authorized([RIGHTS.GMS_USER_PLAYGROUND])
@Query(() => GmsUserAuthenticationResult)
async authenticateGmsUserSearch(@Ctx() context: Context): Promise<GmsUserAuthenticationResult> {
logger.info(`authenticateGmsUserSearch()...`)
const dbUser = getUser(context)
const logger = createLogger()
logger.addContext('user', dbUser.id)
logger.info(`authenticateGmsUserSearch()...`)
let result = new GmsUserAuthenticationResult()
if (context.token) {
const homeCom = await getHomeCommunity()
@ -813,8 +878,11 @@ export class UserResolver {
@Authorized([RIGHTS.GMS_USER_PLAYGROUND])
@Query(() => UserLocationResult)
async userLocation(@Ctx() context: Context): Promise<UserLocationResult> {
logger.info(`userLocation()...`)
const dbUser = getUser(context)
const logger = createLogger()
logger.addContext('user', dbUser.id)
logger.info(`userLocation()...`)
const result = new UserLocationResult()
if (context.token) {
const homeCom = await getHomeCommunity()
@ -833,8 +901,11 @@ export class UserResolver {
@Ctx() context: Context,
@Arg('project', () => String, { nullable: true }) project?: string | null,
): Promise<string> {
logger.info(`authenticateHumhubAutoLogin()...`)
const dbUser = getUser(context)
const logger = createLogger()
logger.addContext('user', dbUser.id)
logger.info(`authenticateHumhubAutoLogin()...`)
const humhubClient = HumHubClient.getInstance()
if (!humhubClient) {
throw new LogError('cannot create humhub client')
@ -1027,13 +1098,15 @@ export class UserResolver {
@Arg('email') email: string,
@Ctx() context: Context,
): Promise<boolean> {
const logger = createLogger()
email = email.trim().toLowerCase()
// const user = await dbUser.findOne({ id: emailContact.userId })
const user = await findUserByEmail(email)
logger.addContext('user', user.id)
logger.info('sendActivationEmail...')
if (user.deletedAt || user.emailContact.deletedAt) {
throw new LogError('User with given email contact is deleted', email)
logger.warn('call for activation of deleted user')
throw new Error('User with given email contact is deleted')
}
user.emailContact.emailResendCount++
await user.emailContact.save()
@ -1042,7 +1115,7 @@ export class UserResolver {
lastName: user.lastName,
email,
language: user.language,
activationLink: activationLink(user.emailContact.emailVerificationCode),
activationLink: activationLink(user.emailContact.emailVerificationCode, logger),
timeDurationObject: getTimeDurationObject(CONFIG.EMAIL_CODE_VALID_TIME),
})
@ -1088,16 +1161,25 @@ export class UserResolver {
}
export async function findUserByEmail(email: string): Promise<DbUser> {
const dbUser = await DbUser.findOneOrFail({
where: {
emailContact: { email },
},
withDeleted: true,
relations: { userRoles: true, emailContact: true },
}).catch(() => {
throw new LogError('No user with this credentials', email)
})
return dbUser
try {
const dbUser = await DbUser.findOneOrFail({
where: {
emailContact: { email },
},
withDeleted: true,
relations: { userRoles: true, emailContact: true },
})
return dbUser
} catch (e) {
const logger = createLogger()
if (e instanceof EntityNotFoundError || (e as Error).name === 'EntityNotFoundError') {
// TODO: discuss if it is ok to print email in log for this case
logger.warn(`findUserByEmail failed, user with email=${email} not found`)
} else {
logger.error(`findUserByEmail failed, unknown error: ${e}`)
}
throw new Error('No user with this credentials')
}
}
async function checkEmailExists(email: string): Promise<boolean> {

View File

@ -10,3 +10,5 @@ export const CONTRIBUTIONLINK_NAME_MAX_CHARS = 100
export const CONTRIBUTIONLINK_NAME_MIN_CHARS = 5
export const MEMO_MAX_CHARS = 255
export const MEMO_MIN_CHARS = 5
export const DEFAULT_PAGINATION_PAGE_SIZE = 25
export const FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX = 'contributionListItem-'

View File

@ -0,0 +1,3 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
export const LOG4JS_RESOLVER_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver`

View File

@ -2,7 +2,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing'
import { Community as DbCommunity } from 'database'
import { Decimal } from 'decimal.js-light'
import { GraphQLError } from 'graphql'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, contributionDateFormatter, testEnvironment } from '@test/helpers'
@ -25,11 +25,11 @@ import { peterLustig } from '@/seeds/users/peter-lustig'
jest.mock('@/password/EncryptorUtils')
let mutate: ApolloServerTestClient['mutate']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -41,7 +41,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('semaphore', () => {

View File

@ -3,8 +3,13 @@ import { User as DbUser } from 'database'
import { verifyAuthToken } from '@/apis/gms/GmsClient'
import { CONFIG } from '@/config'
import { GmsUserAuthenticationResult } from '@/graphql/model/GmsUserAuthenticationResult'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { ensureUrlEndsWithSlash } from '@/util/utilities'
import { getLogger } from 'log4js'
const logger = getLogger(
`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.authenticateGmsUserPlayground`,
)
export async function authenticateGmsUserPlayground(
_apiKey: string,

View File

@ -1,10 +1,13 @@
import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database'
import {
AppDatabase,
Community as DbCommunity,
FederatedCommunity as DbFederatedCommunity,
} from 'database'
import { FindOneOptions, IsNull, Not } from 'typeorm'
import { Paginated } from '@arg/Paginated'
import { LogError } from '@/server/LogError'
import { Connection } from '@/typeorm/connection'
function findWithCommunityIdentifier(communityIdentifier: string): FindOneOptions<DbCommunity> {
return {
@ -115,14 +118,15 @@ export async function getAllCommunities({
pageSize = 25,
currentPage = 1,
}: Paginated): Promise<DbCommunity[]> {
const connection = await Connection.getInstance()
if (!connection) {
const connection = AppDatabase.getInstance()
if (!connection.isConnected()) {
throw new LogError('Cannot connect to db')
}
// foreign: 'ASC',
// createdAt: 'DESC',
// lastAnnouncedAt: 'DESC',
const result = await connection
.getDataSource()
.getRepository(DbFederatedCommunity)
.createQueryBuilder('federatedCommunity')
.leftJoinAndSelect('federatedCommunity.community', 'community')

View File

@ -5,10 +5,15 @@ import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
import { GmsPublishLocationType } from '@/graphql/enum/GmsPublishLocationType'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '.'
import { Point2Location } from './Location2Point'
const logger = getLogger(
`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.compareGmsRelevantUserSettings`,
)
export function compareGmsRelevantUserSettings(
orgUser: DbUser,
updateUserInfosArgs: UpdateUserInfosArgs,

View File

@ -1,7 +1,12 @@
import { CONFIG } from '@/config'
import { Order } from '@/graphql/enum/Order'
import {
DEFAULT_PAGINATION_PAGE_SIZE,
FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX,
} from '@/graphql/resolver/const/const'
import { Paginated } from '@arg/Paginated'
import { Contribution as DbContribution } from 'database'
import { FindManyOptions, In } from 'typeorm'
import { FindManyOptions, In, MoreThan } from 'typeorm'
// TODO: combine with Pagination class for all queries to use
function buildPaginationOptions(paginated: Paginated): FindManyOptions<DbContribution> {
@ -91,3 +96,18 @@ export const loadAllContributions = async (
})
*/
}
export const contributionFrontendLink = async (
contributionId: number,
_createdAt: Date,
): Promise<string> => {
// TODO: page is sometimes wrong, use page 1 for now, and fix later with more time at hand
// simplified, don't account for order by id, so when the nearly impossible case occur that createdAt is the same for two contributions,
// maybe it is the wrong page
//const countBefore = await DbContribution.count({
// where: { createdAt: MoreThan(createdAt) },
//})
// const page = Math.floor(countBefore / DEFAULT_PAGINATION_PAGE_SIZE) + 1
const anchor = `${FRONTEND_CONTRIBUTIONS_ITEM_ANCHOR_PREFIX}${contributionId}`
return `${CONFIG.COMMUNITY_URL}/contributions/own-contributions/1#${anchor}`
}

View File

@ -1,6 +1,6 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Contribution, User } from 'database'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { cleanDB, contributionDateFormatter, testEnvironment } from '@test/helpers'
@ -17,11 +17,11 @@ jest.mock('@/password/EncryptorUtils')
CONFIG.HUMHUB_ACTIVE = false
let mutate: ApolloServerTestClient['mutate']
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -33,7 +33,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
const setZeroHours = (date: Date): Date => {

View File

@ -1,13 +1,17 @@
import { Contribution } from 'database'
import { Decimal } from 'decimal.js-light'
import { getConnection } from 'typeorm'
import { OpenCreation } from '@model/OpenCreation'
import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '@/graphql/resolver/const/const'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getFirstDayOfPreviousNMonth } from '@/util/utilities'
import { AppDatabase } from 'database'
import { getLogger } from 'log4js'
const db = AppDatabase.getInstance()
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.creations`)
interface CreationMap {
id: number
@ -46,7 +50,7 @@ export const getUserCreations = async (
const months = getCreationMonths(timezoneOffset)
logger.trace('getUserCreations months', months)
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day'

View File

@ -1,9 +1,8 @@
import { Contribution as DbContribution } from 'database'
import { AppDatabase, Contribution as DbContribution } from 'database'
import { Brackets, In, IsNull, LessThanOrEqual, Like, Not, SelectQueryBuilder } from 'typeorm'
import { Paginated } from '@arg/Paginated'
import { SearchContributionsFilterArgs } from '@arg/SearchContributionsFilterArgs'
import { Connection } from '@typeorm/connection'
import { LogError } from '@/server/LogError'
@ -32,11 +31,14 @@ export const findContributions = async (
relations: Relations | undefined = undefined,
countOnly = false,
): Promise<[DbContribution[], number]> => {
const connection = await Connection.getInstance()
if (!connection) {
const connection = AppDatabase.getInstance()
if (!connection.isConnected()) {
throw new LogError('Cannot connect to db')
}
const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution')
const queryBuilder = connection
.getDataSource()
.getRepository(DbContribution)
.createQueryBuilder('Contribution')
if (relations) {
joinRelationsRecursive(relations, queryBuilder, 'Contribution')
}

View File

@ -1,6 +1,6 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { Community as DbCommunity, User as DbUser } from 'database'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { cleanDB, testEnvironment } from '@test/helpers'
@ -14,11 +14,11 @@ import { findUserByIdentifier } from './findUserByIdentifier'
jest.mock('@/password/EncryptorUtils')
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -29,7 +29,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('graphql/resolver/util/findUserByIdentifier', () => {

View File

@ -1,7 +1,10 @@
import { KlickTipp } from '@model/KlickTipp'
import { getKlickTippUser } from '@/apis/KlicktippController'
import { klickTippLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.getKlicktippState`)
export const getKlicktippState = async (email: string): Promise<KlickTipp> => {
try {
@ -10,7 +13,7 @@ export const getKlicktippState = async (email: string): Promise<KlickTipp> => {
return new KlickTipp(klickTippUser.status === 'Subscribed')
}
} catch (err) {
logger.error('There is no klicktipp user for email', email, err)
logger.error('There is no klicktipp user for email', email.substring(0, 3), '...', err)
}
return new KlickTipp(false)
}

View File

@ -0,0 +1,3 @@
import { LOG4JS_RESOLVER_CATEGORY_NAME } from '@/graphql/resolver'
export const LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME = `${LOG4JS_RESOLVER_CATEGORY_NAME}.util`

View File

@ -14,13 +14,16 @@ import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { SendCoinsClientFactory } from '@/federation/client/SendCoinsClientFactory'
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
import { fullName } from '@/util/utilities'
import { getLogger } from 'log4js'
import { settlePendingSenderTransaction } from './settlePendingSenderTransaction'
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.processXComSendCoins`)
export async function processXComPendingSendCoins(
receiverCom: DbCommunity,
senderCom: DbCommunity,
@ -63,11 +66,11 @@ export async function processXComPendingSendCoins(
if (!senderBalance) {
throw new LogError('User has not enough GDD or amount is < 0', senderBalance)
}
logger.debug(`X-Com: calculated senderBalance = `, senderBalance)
logger.debug(`calculated senderBalance = `, senderBalance)
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
where: {
publicKey: receiverCom.publicKey,
publicKey: Buffer.from(receiverCom.publicKey),
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
},
})
@ -88,11 +91,11 @@ export async function processXComPendingSendCoins(
args.senderUserUuid = sender.gradidoID
args.senderUserName = fullName(sender.firstName, sender.lastName)
args.senderAlias = sender.alias
logger.debug(`X-Com: ready for voteForSendCoins with args=`, args)
logger.debug(`ready for voteForSendCoins with args=`, args)
voteResult = await client.voteForSendCoins(args)
logger.debug(`X-Com: returned from voteForSendCoins:`, voteResult)
logger.debug(`returned from voteForSendCoins:`, voteResult)
if (voteResult.vote) {
logger.debug(`X-Com: prepare pendingTransaction for sender...`)
logger.debug(`prepare pendingTransaction for sender...`)
// writing the pending transaction on receiver-side was successfull, so now write the sender side
try {
const pendingTx = DbPendingTransaction.create()
@ -120,20 +123,20 @@ export async function processXComPendingSendCoins(
pendingTx.userId = sender.id
pendingTx.userGradidoID = sender.gradidoID
pendingTx.userName = fullName(sender.firstName, sender.lastName)
logger.debug(`X-Com: initialized sender pendingTX=`, pendingTx)
logger.debug(`initialized sender pendingTX=`, pendingTx)
await DbPendingTransaction.insert(pendingTx)
logger.debug(`X-Com: sender pendingTx successfully inserted...`)
logger.debug(`sender pendingTx successfully inserted...`)
} catch (err) {
logger.error(`Error in writing sender pending transaction: `, err)
// revert the existing pending transaction on receiver side
let revertCount = 0
logger.debug(`X-Com: first try to revertSendCoins of receiver`)
logger.debug(`first try to revertSendCoins of receiver`)
do {
if (await client.revertSendCoins(args)) {
logger.debug(`revertSendCoins()-1_0... successfull after revertCount=`, revertCount)
// treat revertingSendCoins as an error of the whole sendCoins-process
throw new LogError('Error in writing sender pending transaction: `, err')
throw new LogError('Error in writing sender pending transaction: ', err)
}
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
throw new LogError(
@ -143,10 +146,7 @@ export async function processXComPendingSendCoins(
}
logger.debug(`voteForSendCoins()-1_0... successfull`)
} else {
logger.error(
`X-Com: break with error on writing pendingTransaction for recipient...`,
voteResult,
)
logger.error(`break with error on writing pendingTransaction for recipient...`, voteResult)
}
return voteResult
}
@ -168,7 +168,7 @@ export async function processXComCommittingSendCoins(
const sendCoinsResult = new SendCoinsResult()
try {
logger.debug(
`XCom: processXComCommittingSendCoins...`,
`processXComCommittingSendCoins...`,
receiverCom,
senderCom,
creationDate,
@ -191,10 +191,10 @@ export async function processXComCommittingSendCoins(
memo,
})
if (pendingTx) {
logger.debug(`X-Com: find pending Tx for settlement:`, pendingTx)
logger.debug('find pending Tx for settlement:', pendingTx)
const receiverFCom = await DbFederatedCommunity.findOneOrFail({
where: {
publicKey: receiverCom.publicKey,
publicKey: Buffer.from(receiverCom.publicKey),
apiVersion: CONFIG.FEDERATION_BACKEND_SEND_ON_API,
},
@ -218,9 +218,9 @@ export async function processXComCommittingSendCoins(
args.senderUserName = pendingTx.userName
}
args.senderAlias = sender.alias
logger.debug(`X-Com: ready for settleSendCoins with args=`, args)
logger.debug('ready for settleSendCoins with args=', args)
const acknowledge = await client.settleSendCoins(args)
logger.debug(`X-Com: returnd from settleSendCoins:`, acknowledge)
logger.debug('returnd from settleSendCoins:', acknowledge)
if (acknowledge) {
// settle the pending transaction on receiver-side was successfull, so now settle the sender side
try {
@ -244,22 +244,22 @@ export async function processXComCommittingSendCoins(
sendCoinsResult.recipAlias = recipient.recipAlias
}
} catch (err) {
logger.error(`Error in writing sender pending transaction: `, err)
logger.error('Error in writing sender pending transaction: ', err)
// revert the existing pending transaction on receiver side
let revertCount = 0
logger.debug(`X-Com: first try to revertSetteledSendCoins of receiver`)
logger.debug('first try to revertSetteledSendCoins of receiver')
do {
if (await client.revertSettledSendCoins(args)) {
logger.debug(
`revertSettledSendCoins()-1_0... successfull after revertCount=`,
'revertSettledSendCoins()-1_0... successfull after revertCount=',
revertCount,
)
// treat revertingSettledSendCoins as an error of the whole sendCoins-process
throw new LogError('Error in settle sender pending transaction: `, err')
throw new LogError('Error in settle sender pending transaction: ', err)
}
} while (CONFIG.FEDERATION_XCOM_MAXREPEAT_REVERTSENDCOINS > revertCount++)
throw new LogError(
`Error in reverting receiver pending transaction even after revertCount=`,
'Error in reverting receiver pending transaction even after revertCount=',
revertCount,
)
}
@ -267,7 +267,7 @@ export async function processXComCommittingSendCoins(
}
}
} catch (err) {
logger.error(`Error:`, err)
logger.error('Error: ', err)
sendCoinsResult.vote = false
}
return sendCoinsResult

View File

@ -5,11 +5,11 @@ import { Decimal } from 'decimal.js-light'
// import { Response } from 'graphql-request/dist/types'
import { GraphQLClient } from 'graphql-request'
import { Response } from 'graphql-request/dist/types'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { CONFIG } from '@/config'
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
@ -20,11 +20,17 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { bobBaumeister } from '@/seeds/users/bob-baumeister'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz'
import { getLogger } from 'config-schema/test/testSetup'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '.'
import { sendTransactionsToDltConnector } from './sendTransactionsToDltConnector'
jest.mock('@/password/EncryptorUtils')
const logger = getLogger(
`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.sendTransactionsToDltConnector`,
)
/*
// Mock the GraphQLClient
jest.mock('graphql-request', () => {
@ -328,11 +334,11 @@ async function createTxReceive1FromSend3(verified: boolean): Promise<Transaction
}
*/
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -343,7 +349,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('create and send Transactions to DltConnector', () => {
@ -417,7 +423,7 @@ describe('create and send Transactions to DltConnector', () => {
]),
)
expect(logger.info).nthCalledWith(3, 'sending to DltConnector currently not configured...')
expect(logger.info).nthCalledWith(2, 'sending to DltConnector currently not configured...')
})
})

View File

@ -3,8 +3,13 @@ import { IsNull } from 'typeorm'
import { DltConnectorClient } from '@dltConnector/DltConnectorClient'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { Monitor, MonitorNames } from '@/util/Monitor'
import { getLogger } from 'log4js'
const logger = getLogger(
`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.sendTransactionsToDltConnector`,
)
export async function sendTransactionsToDltConnector(): Promise<void> {
logger.info('sendTransactionsToDltConnector...')
@ -35,7 +40,7 @@ export async function sendTransactionsToDltConnector(): Promise<void> {
if (result) {
dltTx.messageId = 'sended'
await DltTransaction.save(dltTx)
logger.info('store messageId=%s in dltTx=%d', dltTx.messageId, dltTx.id)
logger.info(`store messageId=${dltTx.messageId} in dltTx=${dltTx.id}`)
}
} catch (e) {
logger.error(

View File

@ -3,8 +3,11 @@ import { Community as DbCommunity, User as DbUser } from 'database'
import { createGmsUser, updateGmsUser } from '@/apis/gms/GmsClient'
import { GmsUser } from '@/apis/gms/model/GmsUser'
import { CONFIG } from '@/config'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.sendUserToGms`)
export async function sendUserToGms(
user: DbUser,

View File

@ -1,20 +1,25 @@
import {
AppDatabase,
Community as DbCommunity,
PendingTransaction as DbPendingTransaction,
User as DbUser,
Transaction as dbTransaction,
} from 'database'
import { Decimal } from 'decimal.js-light'
import { getConnection } from 'typeorm'
import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
import { calculateSenderBalance } from '@/util/calculateSenderBalance'
import { getLogger } from 'log4js'
import { getLastTransaction } from './getLastTransaction'
const db = AppDatabase.getInstance()
const logger = getLogger(
`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.settlePendingSenderTransaction`,
)
export async function settlePendingSenderTransaction(
homeCom: DbCommunity,
senderUser: DbUser,
@ -23,13 +28,13 @@ export async function settlePendingSenderTransaction(
// TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!!
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
await queryRunner.connect()
await queryRunner.startTransaction('REPEATABLE READ')
logger.debug(`start Transaction for write-access...`)
try {
logger.info('X-Com: settlePendingSenderTransaction:', homeCom, senderUser, pendingTx)
logger.info('settlePendingSenderTransaction:', homeCom, senderUser, pendingTx)
// ensure that no other pendingTx with the same sender or recipient exists
const openSenderPendingTx = await DbPendingTransaction.count({

View File

@ -1,7 +1,10 @@
import { Community as DbCommunity, User as DbUser } from 'database'
import { SendCoinsResult } from '@/federation/client/1_0/model/SendCoinsResult'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.storeForeignUser`)
export async function storeForeignUser(
recipCom: DbCommunity,
@ -18,7 +21,7 @@ export async function storeForeignUser(
})
if (!user) {
logger.debug(
'X-Com: no foreignUser found for:',
'no foreignUser found for:',
recipCom.communityUuid,
committingResult.recipGradidoID,
)
@ -36,7 +39,7 @@ export async function storeForeignUser(
}
foreignUser.gradidoID = committingResult.recipGradidoID
foreignUser = await DbUser.save(foreignUser)
logger.debug('X-Com: new foreignUser inserted:', foreignUser)
logger.debug('new foreignUser inserted:', foreignUser)
return true
} else if (
@ -45,7 +48,7 @@ export async function storeForeignUser(
user.alias !== committingResult.recipAlias
) {
logger.warn(
'X-Com: foreignUser still exists, but with different name or alias:',
'foreignUser still exists, but with different name or alias:',
user,
committingResult,
)
@ -62,11 +65,11 @@ export async function storeForeignUser(
logger.debug('update recipient successful.', user)
return true
} else {
logger.debug('X-Com: foreignUser still exists...:', user)
logger.debug('foreignUser still exists...:', user)
return true
}
} catch (err) {
logger.error('X-Com: error in storeForeignUser;', err)
logger.error('error in storeForeignUser;', err)
return false
}
}

View File

@ -4,8 +4,9 @@ import { HumHubClient } from '@/apis/humhub/HumHubClient'
import { GetUser } from '@/apis/humhub/model/GetUser'
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { getLogger } from 'config-schema/test/testSetup'
import { syncHumhub } from './syncHumhub'
jest.mock('@/apis/humhub/HumHubClient')
@ -19,6 +20,8 @@ mockUser.humhubPublishName = PublishNameType.PUBLISH_NAME_FULL
const mockUpdateUserInfosArg = new UpdateUserInfosArgs()
const mockHumHubUser = new GetUser(mockUser, 1)
const logger = getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.syncHumhub`)
describe('syncHumhub', () => {
beforeEach(() => {
jest.spyOn(logger, 'debug').mockImplementation()
@ -33,8 +36,7 @@ describe('syncHumhub', () => {
it('Should not sync if no relevant changes', async () => {
await syncHumhub(mockUpdateUserInfosArg, new User(), 'username')
expect(HumHubClient.getInstance).not.toBeCalled()
// language logging from some other place
expect(logger.debug).toBeCalledTimes(5)
expect(logger.debug).toBeCalledTimes(1)
expect(logger.info).toBeCalledTimes(0)
})
@ -42,7 +44,7 @@ describe('syncHumhub', () => {
mockUpdateUserInfosArg.firstName = 'New' // Relevant changes
mockUser.firstName = 'New'
await syncHumhub(mockUpdateUserInfosArg, mockUser, 'username')
expect(logger.debug).toHaveBeenCalledTimes(8) // Four language logging calls, two debug calls in function, one for not syncing
expect(logger.debug).toHaveBeenCalledTimes(4) // Four language logging calls, two debug calls in function, one for not syncing
expect(logger.info).toHaveBeenLastCalledWith('finished sync user with humhub', {
localId: mockUser.id,
externId: mockHumHubUser.id,

View File

@ -7,7 +7,10 @@ import { ExecutedHumhubAction, syncUser } from '@/apis/humhub/syncUser'
import { PublishNameLogic } from '@/data/PublishName.logic'
import { UpdateUserInfosArgs } from '@/graphql/arg/UpdateUserInfosArgs'
import { PublishNameType } from '@/graphql/enum/PublishNameType'
import { backendLogger as logger } from '@/server/logger'
import { LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME } from '@/graphql/resolver/util'
import { getLogger } from 'log4js'
const createLogger = () => getLogger(`${LOG4JS_GRAPHQL_RESOLVER_UTIL_CATEGORY_NAME}.syncHumhub`)
/**
* Syncs the user with humhub
@ -21,6 +24,8 @@ export async function syncHumhub(
oldHumhubUsername: string,
spaceId?: number | null,
): Promise<GetUser | null | undefined> {
const logger = createLogger()
logger.addContext('user', user.id)
// check for humhub relevant changes
if (
updateUserInfosArg &&

View File

@ -1,9 +1,10 @@
import { TransactionLink as DbTransactionLink } from 'database'
import { AppDatabase, TransactionLink as DbTransactionLink } from 'database'
import { Decimal } from 'decimal.js-light'
import { getConnection } from 'typeorm'
import { LogError } from '@/server/LogError'
const db = AppDatabase.getInstance()
export const transactionLinkSummary = async (
userId: number,
date: Date,
@ -14,7 +15,7 @@ export const transactionLinkSummary = async (
firstDate: Date | null
transactionLinkcount: number
}> => {
const queryRunner = getConnection().createQueryRunner()
const queryRunner = db.getDataSource().createQueryRunner()
try {
await queryRunner.connect()
const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } =

View File

@ -1,31 +1,35 @@
import { ApolloServerTestClient } from 'apollo-server-testing'
import { User } from 'database'
import { Connection } from 'typeorm'
import { DataSource } from 'typeorm'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { cleanDB, testEnvironment } from '@test/helpers'
import { i18n as localization, logger } from '@test/testSetup'
import { i18n as localization } from '@test/testSetup'
import { getLogger } from 'config-schema/test/testSetup'
import { userFactory } from '@/seeds/factory/user'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { validateAlias } from './validateAlias'
let con: Connection
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
testEnv = await testEnvironment(logger, localization)
testEnv = await testEnvironment(getLogger('apollo'), localization)
con = testEnv.con
await cleanDB()
})
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('validate alias', () => {

View File

@ -1,10 +1,14 @@
import 'reflect-metadata'
import 'source-map-support/register'
import { getLogger } from 'log4js'
import { CONFIG } from './config'
import { startValidateCommunities } from './federation/validateCommunities'
import { createServer } from './server/createServer'
import { initLogging } from './server/logger'
async function main() {
const { app } = await createServer()
initLogging()
const { app } = await createServer(getLogger('apollo'))
app.listen(CONFIG.PORT, () => {
// biome-ignore lint/suspicious/noConsole: no need for logging the start message

View File

@ -0,0 +1,3 @@
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
export const LOG4JS_INTERACTION_CATEGORY_NAME = `${LOG4JS_BASE_CATEGORY_NAME}.interaction`

View File

@ -5,13 +5,16 @@ import { Role } from '@/auth/Role'
import { ContributionLogic } from '@/data/Contribution.logic'
import { ContributionMessageBuilder } from '@/data/ContributionMessage.builder'
import { ContributionStatus } from '@/graphql/enum/ContributionStatus'
import { LOG4JS_INTERACTION_CATEGORY_NAME } from '@/interactions'
import { LogError } from '@/server/LogError'
import { Context, getClientTimezoneOffset } from '@/server/context'
import { Logger, getLogger } from 'log4js'
export abstract class AbstractUnconfirmedContributionRole {
private availableCreationSums?: Decimal[]
protected changed = true
private currentStep = 0
protected logger: Logger
public constructor(
protected self: Contribution,
@ -21,6 +24,8 @@ export abstract class AbstractUnconfirmedContributionRole {
if (self.confirmedAt || self.deniedAt) {
throw new LogError("this contribution isn't unconfirmed!")
}
this.logger = getLogger(`${LOG4JS_INTERACTION_CATEGORY_NAME}.updateUnconfirmedContribution`)
this.logger.addContext('contribution', this.self.id)
}
public isChanged(): boolean {

View File

@ -6,8 +6,6 @@ import { ContributionMessageBuilder } from '@/data/ContributionMessage.builder'
import { AdminUpdateContributionArgs } from '@/graphql/arg/AdminUpdateContributionArgs'
import { ContributionStatus } from '@/graphql/enum/ContributionStatus'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { AbstractUnconfirmedContributionRole } from './AbstractUnconfirmedContribution.role'
/**
@ -25,7 +23,7 @@ export class UnconfirmedContributionAdminRole extends AbstractUnconfirmedContrib
updateData.amount ?? contribution.amount,
updateData.creationDate ? new Date(updateData.creationDate) : contribution.contributionDate,
)
logger.debug('use UnconfirmedContributionAdminRole')
this.logger.debug('use UnconfirmedContributionAdminRole')
}
/**

View File

@ -7,7 +7,6 @@ import { ContributionMessageArgs } from '@/graphql/arg/ContributionMessageArgs'
import { ContributionMessageType } from '@/graphql/enum/ContributionMessageType'
import { ContributionStatus } from '@/graphql/enum/ContributionStatus'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { AbstractUnconfirmedContributionRole } from './AbstractUnconfirmedContribution.role'
@ -21,7 +20,7 @@ export class UnconfirmedContributionAdminAddMessageRole extends AbstractUnconfir
private updateData: ContributionMessageArgs,
) {
super(contribution, contribution.amount, contribution.contributionDate)
logger.debug('use UnconfirmedContributionAdminAddMessageRole')
this.logger.debug('use UnconfirmedContributionAdminAddMessageRole')
}
protected update(): void {

View File

@ -4,7 +4,6 @@ import { ContributionMessageBuilder } from '@/data/ContributionMessage.builder'
import { ContributionArgs } from '@/graphql/arg/ContributionArgs'
import { ContributionStatus } from '@/graphql/enum/ContributionStatus'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { AbstractUnconfirmedContributionRole } from './AbstractUnconfirmedContribution.role'
@ -18,7 +17,7 @@ export class UnconfirmedContributionUserRole extends AbstractUnconfirmedContribu
private updateData: ContributionArgs,
) {
super(contribution, updateData.amount, new Date(updateData.contributionDate))
logger.debug('use UnconfirmedContributionUserRole')
this.logger.debug('use UnconfirmedContributionUserRole')
}
protected update(): void {

View File

@ -5,7 +5,6 @@ import { ContributionMessageArgs } from '@/graphql/arg/ContributionMessageArgs'
import { ContributionMessageType } from '@/graphql/enum/ContributionMessageType'
import { ContributionStatus } from '@/graphql/enum/ContributionStatus'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { AbstractUnconfirmedContributionRole } from './AbstractUnconfirmedContribution.role'
@ -19,7 +18,7 @@ export class UnconfirmedContributionUserAddMessageRole extends AbstractUnconfirm
private updateData: ContributionMessageArgs,
) {
super(contribution, contribution.amount, contribution.contributionDate)
logger.debug('use UnconfirmedContributionUserAddMessageRole')
this.logger.debug('use UnconfirmedContributionUserAddMessageRole')
}
protected update(): void {

View File

@ -21,7 +21,8 @@
"readMessage": "Nachricht lesen und beantworten",
"subject": "Nachricht zu deinem Gemeinwohl-Beitrag",
"title": "Nachricht zu deinem Gemeinwohl-Beitrag",
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."
"message": "„{message}“",
"toSeeAndAnswerMessage": "Um auf die Nachricht zu antworten, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."
},
"contribution": {
"toSeeContributionsAndMessages": "Um deine Gemeinwohl-Beiträge und dazugehörige Nachrichten zu sehen, gehe in deinem Gradido-Konto ins Menü „Schöpfen“ auf den Tab „Meine Beiträge“."

View File

@ -21,7 +21,8 @@
"readMessage": "Read and reply to message",
"subject": "Message about your common good contribution",
"title": "Message about your common good contribution",
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."
"message": "„{message}“",
"toSeeAndAnswerMessage": "To reply to the message, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."
},
"contribution": {
"toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab."

View File

@ -8,7 +8,6 @@ import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { crypto_shorthash_KEYBYTES } from 'sodium-native'
@ -40,7 +39,6 @@ export const SecretKeyCryptographyCreateKey = async (
password: string,
): Promise<bigint> => {
try {
logger.trace('call worker for: SecretKeyCryptographyCreateKey')
if (configLoginServerKey.length !== crypto_shorthash_KEYBYTES) {
throw new LogError(
'ServerKey has an invalid size',

View File

@ -1,6 +1,5 @@
import { User } from 'database'
// import { logger } from '@test/testSetup' getting error "jest is not defined"
import { SecretKeyCryptographyCreateKey, getUserCryptographicSalt } from './EncryptorUtils'
export const encryptPassword = async (dbUser: User, password: string): Promise<bigint> => {

View File

@ -4,8 +4,9 @@ import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
import { CONFIG } from '@/config'
import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import {
crypto_box_SEEDBYTES,
crypto_hash_sha512_BYTES,
@ -22,6 +23,8 @@ import {
crypto_shorthash_KEYBYTES,
} from 'sodium-native'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.password.EncryptorUtils`)
const SecretKeyCryptographyCreateKeyMock = (
salt: string,
password: string,

View File

@ -4,8 +4,9 @@ import { datatype, internet, name } from 'faker'
import { CONFIG } from '@/config'
import { createServer } from '@/server/createServer'
import { backendLogger as logger } from '@/server/logger'
import { initLogging } from '@/server/logger'
import { getLogger } from 'log4js'
import { writeHomeCommunityEntry } from './community'
import { contributionLinks } from './contributionLink/index'
import { creations } from './creation/index'
@ -17,6 +18,7 @@ import { transactionLinks } from './transactionLink/index'
import { users } from './users/index'
CONFIG.EMAIL = false
const logger = getLogger('seed')
const context = {
token: '',
@ -48,7 +50,8 @@ const resetEntity = async (entity: any) => {
}
const run = async () => {
const server = await createServer(context)
initLogging()
const server = await createServer(getLogger('apollo'), context)
const seedClient = createTestClient(server.apollo)
const { con } = server
await cleanDB()
@ -93,7 +96,7 @@ const run = async () => {
}
logger.info('##seed## seeding all contributionLinks successful...')
await con.close()
await con.destroy()
}
run().catch((err) => {

View File

@ -1,7 +1,10 @@
import { logger } from '@test/testSetup'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'config-schema/test/testSetup'
import { LogError } from './LogError'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
describe('LogError', () => {
it('logs an Error when created', () => {
new LogError('new LogError')

View File

@ -1,5 +1,21 @@
import { backendLogger as logger } from './logger'
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
import { getLogger } from 'log4js'
const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`)
/**
* A custom Error that logs itself immediately upon instantiation.
*
* TODO: Anti-pattern warning:
* Logging inside the constructor introduces side effects during object creation,
* which breaks separation of concerns and can lead to duplicate or unwanted logs.
* It is generally better to log errors where they are caught, not where they are thrown.
*
* @class LogError
* @extends {Error}
* @param {string} msg - The error message.
* @param {...any} details - Additional details passed to the logger.
*/
export class LogError extends Error {
constructor(msg: string, ...details: any[]) {
super(msg)

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