Merge branch 'master' into alias-update-user-info

This commit is contained in:
Moriz Wahl 2023-05-10 17:37:48 +02:00 committed by GitHub
commit e83c06a419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 198 additions and 203 deletions

View File

@ -1,3 +1,3 @@
node_modules/ node_modules/
dist/ build/
coverage/ coverage/

2
admin/.gitignore vendored
View File

@ -1,5 +1,5 @@
node_modules/ node_modules/
dist/ build/
.cache/ .cache/
/.env /.env

View File

@ -84,7 +84,7 @@ CMD /bin/sh -c "yarn run dev"
FROM base as production FROM base as production
# Copy "binary"-files from build image # Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/dist ./dist COPY --from=build ${DOCKER_WORKDIR}/build ./build
# We also copy the node_modules express and serve-static for the run script # We also copy the node_modules express and serve-static for the run script
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy static files # Copy static files

View File

@ -11,7 +11,7 @@
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"dev": "yarn run serve", "dev": "yarn run serve",
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer build/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "cross-env TZ=UTC jest", "test": "cross-env TZ=UTC jest",

View File

@ -9,10 +9,10 @@ const port = process.env.PORT || 8080
// Express Server // Express Server
const app = express() const app = express()
// Serve files // Serve files
app.use(express.static(path.join(__dirname, '../dist'))) app.use(express.static(path.join(__dirname, '../build')))
// Default to index.html // Default to index.html
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../dist/index.html')) res.sendFile(path.join(__dirname, '../build/index.html'))
}) })
app.listen(port, hostname, () => { app.listen(port, hostname, () => {

View File

@ -37,6 +37,7 @@ export const actions = {
const store = new Vuex.Store({ const store = new Vuex.Store({
plugins: [ plugins: [
createPersistedState({ createPersistedState({
key: 'gradido-admin',
storage: window.localStorage, storage: window.localStorage,
}), }),
], ],

View File

@ -49,5 +49,5 @@ module.exports = {
// Enable CSS source maps. // Enable CSS source maps.
sourceMap: CONFIG.NODE_ENV !== 'production', sourceMap: CONFIG.NODE_ENV !== 'production',
}, },
outputDir: path.resolve(__dirname, './dist'), outputDir: path.resolve(__dirname, './build'),
} }

View File

@ -1,3 +1,4 @@
node_modules node_modules
**/*.min.js **/*.min.js
build build
coverage

View File

@ -12,6 +12,8 @@ module.exports = {
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:import/recommended', 'plugin:import/recommended',
'plugin:import/typescript', 'plugin:import/typescript',
'plugin:security/recommended',
'plugin:@eslint-community/eslint-comments/recommended',
], ],
settings: { settings: {
'import/parsers': { 'import/parsers': {
@ -151,6 +153,11 @@ module.exports = {
'promise/valid-params': 'warn', 'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error', 'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error', 'promise/no-multiple-resolved': 'error',
// eslint comments
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
'@eslint-community/eslint-comments/no-restricted-disable': 'error',
'@eslint-community/eslint-comments/no-use': 'off',
'@eslint-community/eslint-comments/require-description': 'off',
}, },
overrides: [ overrides: [
// only for ts files // only for ts files
@ -159,6 +166,7 @@ module.exports = {
extends: [ extends: [
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
'plugin:type-graphql/recommended', 'plugin:type-graphql/recommended',
], ],
rules: { rules: {
@ -169,6 +177,8 @@ module.exports = {
'@typescript-eslint/prefer-regexp-exec': 'off', '@typescript-eslint/prefer-regexp-exec': 'off',
// this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486 // this should not run on ts files: https://github.com/import-js/eslint-plugin-import/issues/2215#issuecomment-911245486
'import/unambiguous': 'off', 'import/unambiguous': 'off',
// this is not compatible with typeorm, due to joined tables can be null, but are not defined as nullable
'@typescript-eslint/no-unnecessary-condition': 'off',
}, },
parserOptions: { parserOptions: {
tsconfigRootDir: __dirname, tsconfigRootDir: __dirname,

View File

@ -7,7 +7,7 @@ module.exports = {
collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'],
coverageThreshold: { coverageThreshold: {
global: { global: {
lines: 85, lines: 86,
}, },
}, },
setupFiles: ['<rootDir>/test/testSetup.ts'], setupFiles: ['<rootDir>/test/testSetup.ts'],

View File

@ -46,6 +46,7 @@
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^3.2.1",
"@types/email-templates": "^10.0.1", "@types/email-templates": "^10.0.1",
"@types/express": "^4.17.12", "@types/express": "^4.17.12",
"@types/faker": "^5.5.9", "@types/faker": "^5.5.9",
@ -68,6 +69,7 @@
"eslint-plugin-n": "^15.7.0", "eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-security": "^1.7.1",
"eslint-plugin-type-graphql": "^1.0.0", "eslint-plugin-type-graphql": "^1.0.0",
"faker": "^5.5.3", "faker": "^5.5.3",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",

View File

@ -7,7 +7,6 @@ import axios from 'axios'
import { LogError } from '@/server/LogError' import { LogError } from '@/server/LogError'
import { backendLogger as logger } from '@/server/logger' import { backendLogger as logger } from '@/server/logger'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiPost = async (url: string, payload: unknown): Promise<any> => { export const apiPost = async (url: string, payload: unknown): Promise<any> => {
logger.trace('POST', url, payload) logger.trace('POST', url, payload)
try { try {
@ -25,7 +24,6 @@ export const apiPost = async (url: string, payload: unknown): Promise<any> => {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const apiGet = async (url: string): Promise<any> => { export const apiGet = async (url: string): Promise<any> => {
logger.trace('GET: url=' + url) logger.trace('GET: url=' + url)
try { try {

View File

@ -12,7 +12,7 @@ import KlicktippConnector from 'klicktipp-api'
const klicktippConnector = new KlicktippConnector() const klicktippConnector = new KlicktippConnector()
export const klicktippSignIn = async ( export const subscribe = async (
email: string, email: string,
language: string, language: string,
firstName?: string, firstName?: string,
@ -28,13 +28,6 @@ export const klicktippSignIn = async (
return result return result
} }
export const signout = async (email: string, language: string): Promise<boolean> => {
if (!CONFIG.KLICKTIPP) return true
const apiKey = language === 'de' ? CONFIG.KLICKTIPP_APIKEY_DE : CONFIG.KLICKTIPP_APIKEY_EN
const result = await klicktippConnector.signoff(apiKey, email)
return result
}
export const unsubscribe = async (email: string): Promise<boolean> => { export const unsubscribe = async (email: string): Promise<boolean> => {
if (!CONFIG.KLICKTIPP) return true if (!CONFIG.KLICKTIPP) return true
const isLogin = await loginKlicktippUser() const isLogin = await loginKlicktippUser()
@ -60,38 +53,6 @@ export const loginKlicktippUser = async (): Promise<boolean> => {
return await klicktippConnector.login(CONFIG.KLICKTIPP_USER, CONFIG.KLICKTIPP_PASSWORD) return await klicktippConnector.login(CONFIG.KLICKTIPP_USER, CONFIG.KLICKTIPP_PASSWORD)
} }
export const logoutKlicktippUser = async (): Promise<boolean> => {
if (!CONFIG.KLICKTIPP) return true
return await klicktippConnector.logout()
}
export const untagUser = async (email: string, tagId: string): Promise<boolean> => {
if (!CONFIG.KLICKTIPP) return true
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.untag(email, tagId)
}
return false
}
export const tagUser = async (email: string, tagIds: string): Promise<boolean> => {
if (!CONFIG.KLICKTIPP) return true
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tag(email, tagIds)
}
return false
}
export const getKlicktippTagMap = async () => {
if (!CONFIG.KLICKTIPP) return true
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tagIndex()
}
return ''
}
export const addFieldsToSubscriber = async ( export const addFieldsToSubscriber = async (
email: string, email: string,
fields: any = {}, fields: any = {},

View File

@ -8,7 +8,7 @@ import { CustomJwtPayload } from './CustomJwtPayload'
export const decode = (token: string): CustomJwtPayload | null => { export const decode = (token: string): CustomJwtPayload | null => {
if (!token) throw new LogError('401 Unauthorized') if (!token) throw new LogError('401 Unauthorized')
try { try {
return <CustomJwtPayload>verify(token, CONFIG.JWT_SECRET) return verify(token, CONFIG.JWT_SECRET) as CustomJwtPayload
} catch (err) { } catch (err) {
return null return null
} }

View File

@ -16,7 +16,7 @@ const constants = {
DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0
LOG4JS_CONFIG: 'log4js-config.json', LOG4JS_CONFIG: 'log4js-config.json',
// default log level on production should be info // default log level on production should be info
LOG_LEVEL: process.env.LOG_LEVEL || 'info', LOG_LEVEL: process.env.LOG_LEVEL ?? 'info',
CONFIG_VERSION: { CONFIG_VERSION: {
DEFAULT: 'DEFAULT', DEFAULT: 'DEFAULT',
EXPECTED: 'v15.2023-02-07', EXPECTED: 'v15.2023-02-07',
@ -25,67 +25,67 @@ const constants = {
} }
const server = { const server = {
PORT: process.env.PORT || 4000, PORT: process.env.PORT ?? 4000,
JWT_SECRET: process.env.JWT_SECRET || 'secret123', JWT_SECRET: process.env.JWT_SECRET ?? 'secret123',
JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m', JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN ?? '10m',
GRAPHIQL: process.env.GRAPHIQL === 'true' || false, GRAPHIQL: process.env.GRAPHIQL === 'true' || false,
GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net', GDT_API_URL: process.env.GDT_API_URL ?? 'https://gdt.gradido.net',
PRODUCTION: process.env.NODE_ENV === 'production' || false, PRODUCTION: process.env.NODE_ENV === 'production' || false,
} }
const database = { const database = {
DB_HOST: process.env.DB_HOST || 'localhost', DB_HOST: process.env.DB_HOST ?? 'localhost',
DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306, DB_PORT: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
DB_USER: process.env.DB_USER || 'root', DB_USER: process.env.DB_USER ?? 'root',
DB_PASSWORD: process.env.DB_PASSWORD || '', DB_PASSWORD: process.env.DB_PASSWORD ?? '',
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community', DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community',
TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH || 'typeorm.backend.log', TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.backend.log',
} }
const klicktipp = { const klicktipp = {
KLICKTIPP: process.env.KLICKTIPP === 'true' || false, KLICKTIPP: process.env.KLICKTIPP === 'true' || false,
KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL || 'https://api.klicktipp.com', KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL ?? 'https://api.klicktipp.com',
KLICKTIPP_USER: process.env.KLICKTIPP_USER || 'gradido_test', KLICKTIPP_USER: process.env.KLICKTIPP_USER ?? 'gradido_test',
KLICKTIPP_PASSWORD: process.env.KLICKTIPP_PASSWORD || 'secret321', KLICKTIPP_PASSWORD: process.env.KLICKTIPP_PASSWORD ?? 'secret321',
KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE || 'SomeFakeKeyDE', KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE ?? 'SomeFakeKeyDE',
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN', KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN ?? 'SomeFakeKeyEN',
} }
const community = { const community = {
COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung', COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung',
COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/', COMMUNITY_URL: process.env.COMMUNITY_URL ?? 'http://localhost/',
COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register', COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL ?? 'http://localhost/register',
COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL || 'http://localhost/redeem/{code}', COMMUNITY_REDEEM_URL: process.env.COMMUNITY_REDEEM_URL ?? 'http://localhost/redeem/{code}',
COMMUNITY_REDEEM_CONTRIBUTION_URL: COMMUNITY_REDEEM_CONTRIBUTION_URL:
process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL || 'http://localhost/redeem/CL-{code}', process.env.COMMUNITY_REDEEM_CONTRIBUTION_URL ?? 'http://localhost/redeem/CL-{code}',
COMMUNITY_DESCRIPTION: COMMUNITY_DESCRIPTION:
process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.', process.env.COMMUNITY_DESCRIPTION ?? 'Die lokale Entwicklungsumgebung von Gradido.',
COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL || 'support@supportmail.com', COMMUNITY_SUPPORT_MAIL: process.env.COMMUNITY_SUPPORT_MAIL ?? 'support@supportmail.com',
} }
const loginServer = { const loginServer = {
LOGIN_APP_SECRET: process.env.LOGIN_APP_SECRET || '21ffbbc616fe', LOGIN_APP_SECRET: process.env.LOGIN_APP_SECRET ?? '21ffbbc616fe',
LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY || 'a51ef8ac7ef1abf162fb7a65261acd7a', LOGIN_SERVER_KEY: process.env.LOGIN_SERVER_KEY ?? 'a51ef8ac7ef1abf162fb7a65261acd7a',
} }
const email = { const email = {
EMAIL: process.env.EMAIL === 'true' || false, EMAIL: process.env.EMAIL === 'true' || false,
EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false, EMAIL_TEST_MODUS: process.env.EMAIL_TEST_MODUS === 'true' || false,
EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER || 'stage1@gradido.net', EMAIL_TEST_RECEIVER: process.env.EMAIL_TEST_RECEIVER ?? 'stage1@gradido.net',
EMAIL_USERNAME: process.env.EMAIL_USERNAME || '', EMAIL_USERNAME: process.env.EMAIL_USERNAME ?? '',
EMAIL_SENDER: process.env.EMAIL_SENDER || 'info@gradido.net', EMAIL_SENDER: process.env.EMAIL_SENDER ?? 'info@gradido.net',
EMAIL_PASSWORD: process.env.EMAIL_PASSWORD || '', EMAIL_PASSWORD: process.env.EMAIL_PASSWORD ?? '',
EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL || 'mailserver', EMAIL_SMTP_URL: process.env.EMAIL_SMTP_URL ?? 'mailserver',
EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025, EMAIL_SMTP_PORT: Number(process.env.EMAIL_SMTP_PORT) || 1025,
// eslint-disable-next-line no-unneeded-ternary // eslint-disable-next-line no-unneeded-ternary
EMAIL_TLS: process.env.EMAIL_TLS === 'false' ? false : true, EMAIL_TLS: process.env.EMAIL_TLS === 'false' ? false : true,
EMAIL_LINK_VERIFICATION: EMAIL_LINK_VERIFICATION:
process.env.EMAIL_LINK_VERIFICATION || 'http://localhost/checkEmail/{optin}{code}', process.env.EMAIL_LINK_VERIFICATION ?? 'http://localhost/checkEmail/{optin}{code}',
EMAIL_LINK_SETPASSWORD: EMAIL_LINK_SETPASSWORD:
process.env.EMAIL_LINK_SETPASSWORD || 'http://localhost/reset-password/{optin}', process.env.EMAIL_LINK_SETPASSWORD ?? 'http://localhost/reset-password/{optin}',
EMAIL_LINK_FORGOTPASSWORD: EMAIL_LINK_FORGOTPASSWORD:
process.env.EMAIL_LINK_FORGOTPASSWORD || 'http://localhost/forgot-password', process.env.EMAIL_LINK_FORGOTPASSWORD ?? 'http://localhost/forgot-password',
EMAIL_LINK_OVERVIEW: process.env.EMAIL_LINK_OVERVIEW || 'http://localhost/overview', EMAIL_LINK_OVERVIEW: process.env.EMAIL_LINK_OVERVIEW ?? 'http://localhost/overview',
// time in minutes a optin code is valid // time in minutes a optin code is valid
EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME EMAIL_CODE_VALID_TIME: process.env.EMAIL_CODE_VALID_TIME
? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 1440 ? parseInt(process.env.EMAIL_CODE_VALID_TIME) || 1440
@ -98,14 +98,14 @@ const email = {
const webhook = { const webhook = {
// Elopage // Elopage
WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET || 'secret', WEBHOOK_ELOPAGE_SECRET: process.env.WEBHOOK_ELOPAGE_SECRET ?? 'secret',
} }
// This is needed by graphql-directive-auth // This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET process.env.APP_SECRET = server.JWT_SECRET
// Check config version // Check config version
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION ?? constants.CONFIG_VERSION.DEFAULT
if ( if (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes( ![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT, constants.CONFIG_VERSION.CURRENT,

View File

@ -1,7 +1,7 @@
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
import { PatchedRequestInit } from 'graphql-request/dist/types' import { PatchedRequestInit } from 'graphql-request/dist/types'
type ClientInstance = { interface ClientInstance {
url: string url: string
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
client: GraphQLGetClient client: GraphQLGetClient

View File

@ -54,7 +54,7 @@ export async function validateCommunities(): Promise<void> {
} else { } else {
logger.warn( logger.warn(
`Federation: received not matching publicKey -> received: ${ `Federation: received not matching publicKey -> received: ${
pubKey || 'null' pubKey ?? 'null'
}, expected: ${dbCom.publicKey.toString()} `, }, expected: ${dbCom.publicKey.toString()} `,
) )
// DbCommunity.delete({ id: dbCom.id }) // DbCommunity.delete({ id: dbCom.id })

View File

@ -12,7 +12,7 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
context.role = ROLE_UNAUTHORIZED // unauthorized user context.role = ROLE_UNAUTHORIZED // unauthorized user
// is rights an inalienable right? // is rights an inalienable right?
if ((<RIGHTS[]>rights).reduce((acc, right) => acc && INALIENABLE_RIGHTS.includes(right), true)) if ((rights as RIGHTS[]).reduce((acc, right) => acc && INALIENABLE_RIGHTS.includes(right), true))
return true return true
// Do we have a token? // Do we have a token?
@ -43,7 +43,7 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
} }
// check for correct rights // check for correct rights
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role?.hasRight(right)) const missingRights = (rights as RIGHTS[]).filter((right) => !context.role?.hasRight(right))
if (missingRights.length !== 0) { if (missingRights.length !== 0) {
throw new LogError('401 Unauthorized') throw new LogError('401 Unauthorized')
} }

View File

@ -10,7 +10,7 @@ export class Balance {
linkCount: number linkCount: number
}) { }) {
this.balance = data.balance this.balance = data.balance
this.balanceGDT = data.balanceGDT || null this.balanceGDT = data.balanceGDT ?? null
this.count = data.count this.count = data.count
this.linkCount = data.linkCount this.linkCount = data.linkCount
} }

View File

@ -43,13 +43,12 @@ export class Transaction {
this.memo = transaction.memo this.memo = transaction.memo
this.creationDate = transaction.creationDate this.creationDate = transaction.creationDate
this.linkedUser = linkedUser this.linkedUser = linkedUser
this.linkedTransactionId = transaction.linkedTransactionId || null this.linkedTransactionId = transaction.linkedTransactionId ?? null
this.linkId = transaction.contribution this.linkId = transaction.contribution
? transaction.contribution.contributionLinkId ? transaction.contribution.contributionLinkId
: transaction.transactionLinkId || null : transaction.transactionLinkId ?? null
this.previousBalance = this.previousBalance =
(transaction.previousTransaction && transaction.previousTransaction?.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN) ??
transaction.previousTransaction.balance.toDecimalPlaces(2, Decimal.ROUND_DOWN)) ||
new Decimal(0) new Decimal(0)
} }

View File

@ -70,7 +70,10 @@ export class BalanceResolver {
now, now,
) )
logger.info( logger.info(
`calculatedDecay(balance=${lastTransaction.balance}, balanceDate=${lastTransaction.balanceDate})=${calculatedDecay}`, 'calculatedDecay',
lastTransaction.balance,
lastTransaction.balanceDate,
calculatedDecay,
) )
// The final balance is reduced by the link amount withheld // The final balance is reduced by the link amount withheld
@ -96,9 +99,7 @@ export class BalanceResolver {
count, count,
linkCount, linkCount,
}) })
logger.info( logger.info('new Balance', balance, balanceGDT, count, linkCount, newBalance)
`new Balance(balance=${balance}, balanceGDT=${balanceGDT}, count=${count}, linkCount=${linkCount}) = ${newBalance}`,
)
return newBalance return newBalance
} }

View File

@ -66,7 +66,7 @@ let testEnv: {
query: ApolloServerTestClient['query'] query: ApolloServerTestClient['query']
con: Connection con: Connection
} }
let creation: Contribution | void let creation: Contribution | null
let admin: User let admin: User
let pendingContribution: any let pendingContribution: any
let inProgressContribution: any let inProgressContribution: any
@ -2071,7 +2071,7 @@ describe('ContributionResolver', () => {
mutate({ mutate({
mutation: updateContribution, mutation: updateContribution,
variables: { variables: {
contributionId: (adminContribution && adminContribution.id) || -1, contributionId: adminContribution?.id ?? -1,
amount: 100.0, amount: 100.0,
memo: 'Test Test Test', memo: 'Test Test Test',
creationDate: new Date().toString(), creationDate: new Date().toString(),
@ -2565,8 +2565,8 @@ describe('ContributionResolver', () => {
}) })
describe('confirm two creations one after the other quickly', () => { describe('confirm two creations one after the other quickly', () => {
let c1: Contribution | void let c1: Contribution | null
let c2: Contribution | void let c2: Contribution | null
beforeAll(async () => { beforeAll(async () => {
const now = new Date() const now = new Date()

View File

@ -269,7 +269,7 @@ export class ContributionResolver {
withDeleted: true, withDeleted: true,
relations: ['user'], relations: ['user'],
}) })
if (!emailContact || !emailContact.user) { if (!emailContact?.user) {
throw new LogError('Could not find user', email) throw new LogError('Could not find user', email)
} }
if (emailContact.deletedAt || emailContact.user.deletedAt) { if (emailContact.deletedAt || emailContact.user.deletedAt) {

View File

@ -1,6 +1,6 @@
import { Resolver, Authorized, Mutation, Ctx } from 'type-graphql' import { Resolver, Authorized, Mutation, Ctx } from 'type-graphql'
import { unsubscribe, klicktippSignIn } from '@/apis/KlicktippController' import { unsubscribe, subscribe } from '@/apis/KlicktippController'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { EVENT_NEWSLETTER_SUBSCRIBE, EVENT_NEWSLETTER_UNSUBSCRIBE } from '@/event/Events' import { EVENT_NEWSLETTER_SUBSCRIBE, EVENT_NEWSLETTER_UNSUBSCRIBE } from '@/event/Events'
import { Context, getUser } from '@/server/context' import { Context, getUser } from '@/server/context'
@ -20,6 +20,6 @@ export class KlicktippResolver {
async subscribeNewsletter(@Ctx() context: Context): Promise<boolean> { async subscribeNewsletter(@Ctx() context: Context): Promise<boolean> {
const user = getUser(context) const user = getUser(context)
await EVENT_NEWSLETTER_SUBSCRIBE(user) await EVENT_NEWSLETTER_SUBSCRIBE(user)
return klicktippSignIn(user.emailContact.email, user.language) return subscribe(user.emailContact.email, user.language)
} }
} }

View File

@ -817,8 +817,8 @@ describe('TransactionLinkResolver', () => {
const bibisTransaktionLinks = transactionLinks.filter( const bibisTransaktionLinks = transactionLinks.filter(
(transactionLink) => transactionLink.email === 'bibi@bloxberg.de', (transactionLink) => transactionLink.email === 'bibi@bloxberg.de',
) )
for (let i = 0; i < bibisTransaktionLinks.length; i++) { for (const bibisTransaktionLink of bibisTransaktionLinks) {
await transactionLinkFactory(testEnv, bibisTransaktionLinks[i]) await transactionLinkFactory(testEnv, bibisTransaktionLink)
} }
// admin: only now log in // admin: only now log in
@ -1040,6 +1040,7 @@ describe('TransactionLinkResolver', () => {
}) })
it('returns a string that ends with the hex value of date', () => { it('returns a string that ends with the hex value of date', () => {
// eslint-disable-next-line security/detect-non-literal-regexp
const regexp = new RegExp(date.getTime().toString(16) + '$') const regexp = new RegExp(date.getTime().toString(16) + '$')
expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp))
}) })

View File

@ -146,7 +146,7 @@ export class TransactionLinkResolver {
const transactionLink = await DbTransactionLink.findOneOrFail({ code }, { withDeleted: true }) const transactionLink = await DbTransactionLink.findOneOrFail({ code }, { withDeleted: true })
const user = await DbUser.findOneOrFail({ id: transactionLink.userId }) const user = await DbUser.findOneOrFail({ id: transactionLink.userId })
let redeemedBy: User | null = null let redeemedBy: User | null = null
if (transactionLink && transactionLink.redeemedBy) { if (transactionLink?.redeemedBy) {
redeemedBy = new User(await DbUser.findOneOrFail({ id: transactionLink.redeemedBy })) redeemedBy = new User(await DbUser.findOneOrFail({ id: transactionLink.redeemedBy }))
} }
return new TransactionLink(transactionLink, new User(user), redeemedBy) return new TransactionLink(transactionLink, new User(user), redeemedBy)

View File

@ -48,9 +48,7 @@ export const executeTransaction = async (
// acquire lock // acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire() const releaseLock = await TRANSACTIONS_LOCK.acquire()
try { try {
logger.info( logger.info('executeTransaction', amount, memo, sender, recipient)
`executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`,
)
if (sender.id === recipient.id) { if (sender.id === recipient.id) {
throw new LogError('Sender and Recipient are the same', sender.id) throw new LogError('Sender and Recipient are the same', sender.id)
@ -119,10 +117,10 @@ export const executeTransaction = async (
// Save linked transaction id for send // Save linked transaction id for send
transactionSend.linkedTransactionId = transactionReceive.id transactionSend.linkedTransactionId = transactionReceive.id
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend) await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
logger.debug(`send Transaction updated: ${transactionSend}`) logger.debug('send Transaction updated', transactionSend)
if (transactionLink) { if (transactionLink) {
logger.info(`transactionLink: ${transactionLink}`) logger.info('transactionLink', transactionLink)
transactionLink.redeemedAt = receivedCallDate transactionLink.redeemedAt = receivedCallDate
transactionLink.redeemedBy = recipient.id transactionLink.redeemedBy = recipient.id
await queryRunner.manager.update( await queryRunner.manager.update(
@ -271,8 +269,8 @@ export class TransactionResolver {
sumAmount.mul(-1), sumAmount.mul(-1),
sumHoldAvailableAmount.mul(-1), sumHoldAvailableAmount.mul(-1),
sumHoldAvailableAmount.minus(sumAmount.toString()).mul(-1), sumHoldAvailableAmount.minus(sumAmount.toString()).mul(-1),
firstDate || now, firstDate ?? now,
lastDate || now, lastDate ?? now,
self, self,
(userTransactions.length && userTransactions[0].balance) || new Decimal(0), (userTransactions.length && userTransactions[0].balance) || new Decimal(0),
), ),
@ -325,9 +323,7 @@ export class TransactionResolver {
} }
await executeTransaction(amount, memo, senderUser, recipientUser) await executeTransaction(amount, memo, senderUser, recipientUser)
logger.info( logger.info('successful executeTransaction', amount, memo, senderUser, recipientUser)
`successful executeTransaction(amount=${amount}, memo=${memo}, senderUser=${senderUser}, recipientUser=${recipientUser})`,
)
return true return true
} }
} }

View File

@ -20,6 +20,7 @@ import { ContributionLink } from '@model/ContributionLink'
import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers' import { testEnvironment, headerPushMock, resetToken, cleanDB } from '@test/helpers'
import { logger, i18n as localization } from '@test/testSetup' import { logger, i18n as localization } from '@test/testSetup'
import { subscribe } from '@/apis/KlicktippController'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
import { import {
sendAccountActivationEmail, sendAccountActivationEmail,
@ -61,8 +62,6 @@ import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { printTimeDuration } from '@/util/time' import { printTimeDuration } from '@/util/time'
import { objectValuesToArray } from '@/util/utilities' import { objectValuesToArray } from '@/util/utilities'
// import { klicktippSignIn } from '@/apis/KlicktippController'
jest.mock('@/emails/sendEmailVariants', () => { jest.mock('@/emails/sendEmailVariants', () => {
const originalModule = jest.requireActual('@/emails/sendEmailVariants') const originalModule = jest.requireActual('@/emails/sendEmailVariants')
return { return {
@ -76,15 +75,13 @@ jest.mock('@/emails/sendEmailVariants', () => {
} }
}) })
/*
jest.mock('@/apis/KlicktippController', () => { jest.mock('@/apis/KlicktippController', () => {
return { return {
__esModule: true, __esModule: true,
klicktippSignIn: jest.fn(), subscribe: jest.fn(),
getKlickTippUser: jest.fn(),
} }
}) })
*/
let admin: User let admin: User
let user: User let user: User
@ -556,16 +553,14 @@ describe('UserResolver', () => {
expect(newUser.password.toString()).toEqual(encryptedPass.toString()) expect(newUser.password.toString()).toEqual(encryptedPass.toString())
}) })
/*
it('calls the klicktipp API', () => { it('calls the klicktipp API', () => {
expect(klicktippSignIn).toBeCalledWith( expect(subscribe).toBeCalledWith(
user[0].email, newUser.emailContact.email,
user[0].language, newUser.language,
user[0].firstName, newUser.firstName,
user[0].lastName, newUser.lastName,
) )
}) })
*/
it('returns true', () => { it('returns true', () => {
expect(result).toBeTruthy() expect(result).toBeTruthy()

View File

@ -35,7 +35,7 @@ import { User } from '@model/User'
import { UserAdmin, SearchUsersResult } from '@model/UserAdmin' import { UserAdmin, SearchUsersResult } from '@model/UserAdmin'
import { UserRepository } from '@repository/User' import { UserRepository } from '@repository/User'
import { klicktippSignIn } from '@/apis/KlicktippController' import { subscribe } from '@/apis/KlicktippController'
import { encode } from '@/auth/JWT' import { encode } from '@/auth/JWT'
import { RIGHTS } from '@/auth/RIGHTS' import { RIGHTS } from '@/auth/RIGHTS'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
@ -95,7 +95,7 @@ const newEmailContact = (email: string, userId: number): DbUserContact => {
emailContact.emailChecked = false emailContact.emailChecked = false
emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER emailContact.emailOptInTypeId = OptInType.EMAIL_OPT_IN_REGISTER
emailContact.emailVerificationCode = random(64) emailContact.emailVerificationCode = random(64)
logger.debug(`newEmailContact...successful: ${emailContact}`) logger.debug('newEmailContact...successful', emailContact)
return emailContact return emailContact
} }
@ -226,7 +226,7 @@ export class UserResolver {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
if (await checkEmailExists(email)) { if (await checkEmailExists(email)) {
const foundUser = await findUserByEmail(email) const foundUser = await findUserByEmail(email)
logger.info(`DbUser.findOne(email=${email}) = ${foundUser}`) logger.info('DbUser.findOne', email, foundUser)
if (foundUser) { if (foundUser) {
// ATTENTION: this logger-message will be exactly expected during tests, next line // ATTENTION: this logger-message will be exactly expected during tests, next line
@ -276,7 +276,7 @@ export class UserResolver {
dbUser.firstName = firstName dbUser.firstName = firstName
dbUser.lastName = lastName dbUser.lastName = lastName
dbUser.language = language dbUser.language = language
dbUser.publisherId = publisherId || 0 dbUser.publisherId = publisherId ?? 0
dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD dbUser.passwordEncryptionType = PasswordEncryptionType.NO_PASSWORD
logger.debug('new dbUser', dbUser) logger.debug('new dbUser', dbUser)
if (redeemCode) { if (redeemCode) {
@ -383,7 +383,7 @@ export class UserResolver {
throw new LogError('Unable to save email verification code', user.emailContact) throw new LogError('Unable to save email verification code', user.emailContact)
}) })
logger.info(`optInCode for ${email}=${user.emailContact}`) logger.info('optInCode for', email, user.emailContact)
void sendResetPasswordEmail({ void sendResetPasswordEmail({
firstName: user.firstName, firstName: user.firstName,
@ -469,9 +469,9 @@ export class UserResolver {
// TODO do we always signUp the user? How to handle things with old users? // TODO do we always signUp the user? How to handle things with old users?
if (userContact.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) { if (userContact.emailOptInTypeId === OptInType.EMAIL_OPT_IN_REGISTER) {
try { try {
await klicktippSignIn(userContact.email, user.language, user.firstName, user.lastName) await subscribe(userContact.email, user.language, user.firstName, user.lastName)
logger.debug( logger.debug(
`klicktippSignIn(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`, `subscribe(${userContact.email}, ${user.language}, ${user.firstName}, ${user.lastName})`,
) )
} catch (e) { } catch (e) {
logger.error('Error subscribing to klicktipp', e) logger.error('Error subscribing to klicktipp', e)
@ -487,7 +487,7 @@ export class UserResolver {
async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> { async queryOptIn(@Arg('optIn') optIn: string): Promise<boolean> {
logger.info(`queryOptIn(${optIn})...`) logger.info(`queryOptIn(${optIn})...`)
const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: optIn }) const userContact = await DbUserContact.findOneOrFail({ emailVerificationCode: optIn })
logger.debug(`found optInCode=${userContact}`) logger.debug('found optInCode', userContact)
// Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes // Code is only valid for `CONFIG.EMAIL_CODE_VALID_TIME` minutes
if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) { if (!isEmailVerificationCodeValid(userContact.updatedAt || userContact.createdAt)) {
throw new LogError( throw new LogError(
@ -593,7 +593,7 @@ export class UserResolver {
logger.info(`hasElopage()...`) logger.info(`hasElopage()...`)
const userEntity = getUser(context) const userEntity = getUser(context)
const elopageBuys = hasElopageBuys(userEntity.emailContact.email) const elopageBuys = hasElopageBuys(userEntity.emailContact.email)
logger.debug(`has ElopageBuys = ${elopageBuys}`) logger.debug('has ElopageBuys', elopageBuys)
return elopageBuys return elopageBuys
} }
@ -650,7 +650,7 @@ export class UserResolver {
return 'user.' + fieldName return 'user.' + fieldName
}), }),
searchText, searchText,
filters || null, filters ?? null,
currentPage, currentPage,
pageSize, pageSize,
) )
@ -716,14 +716,14 @@ export class UserResolver {
// change isAdmin // change isAdmin
switch (user.isAdmin) { switch (user.isAdmin) {
case null: case null:
if (isAdmin === true) { if (isAdmin) {
user.isAdmin = new Date() user.isAdmin = new Date()
} else { } else {
throw new LogError('User is already an usual user') throw new LogError('User is already an usual user')
} }
break break
default: default:
if (isAdmin === false) { if (!isAdmin) {
user.isAdmin = null user.isAdmin = null
} else { } else {
throw new LogError('User is already admin') throw new LogError('User is already admin')

View File

@ -29,10 +29,12 @@ export const validateContribution = (
throw new LogError('No information for available creations for the given date', creationDate) throw new LogError('No information for available creations for the given date', creationDate)
} }
// eslint-disable-next-line security/detect-object-injection
if (amount.greaterThan(creations[index].toString())) { if (amount.greaterThan(creations[index].toString())) {
throw new LogError( throw new LogError(
'The amount to be created exceeds the amount still available for this month', 'The amount to be created exceeds the amount still available for this month',
amount, amount,
// eslint-disable-next-line security/detect-object-injection
creations[index], creations[index],
) )
} }
@ -151,6 +153,7 @@ export const updateCreations = (
if (index < 0) { if (index < 0) {
throw new LogError('You cannot create GDD for a month older than the last three months') throw new LogError('You cannot create GDD for a month older than the last three months')
} }
// eslint-disable-next-line security/detect-object-injection
creations[index] = creations[index].plus(contribution.amount.toString()) creations[index] = creations[index].plus(contribution.amount.toString())
return creations return creations
} }
@ -169,6 +172,7 @@ export const getOpenCreations = async (
return { return {
month: date.getMonth(), month: date.getMonth(),
year: date.getFullYear(), year: date.getFullYear(),
// eslint-disable-next-line security/detect-object-injection
amount: creations[index], amount: creations[index],
} }
}) })

View File

@ -24,7 +24,7 @@ export const findContributions = async (
} }
return DbContribution.findAndCount({ return DbContribution.findAndCount({
where: { where: {
...(statusFilter && statusFilter.length && { contributionStatus: In(statusFilter) }), ...(statusFilter?.length && { contributionStatus: In(statusFilter) }),
...(userId && { userId }), ...(userId && { userId }),
}, },
withDeleted, withDeleted,

View File

@ -14,7 +14,7 @@ export async function transactionLinkList(
filters: TransactionLinkFilters | null, filters: TransactionLinkFilters | null,
user: DbUser, user: DbUser,
): Promise<TransactionLinkResult> { ): Promise<TransactionLinkResult> {
const { withDeleted, withExpired, withRedeemed } = filters || { const { withDeleted, withExpired, withRedeemed } = filters ?? {
withDeleted: false, withDeleted: false,
withExpired: false, withExpired: false,
withRedeemed: false, withRedeemed: false,

View File

@ -13,7 +13,7 @@ async function main() {
console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`) console.log(`GraphIQL available at http://localhost:${CONFIG.PORT}`)
} }
}) })
void startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER)) startValidateCommunities(Number(CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER))
} }
main().catch((e) => { main().catch((e) => {

View File

@ -10,19 +10,6 @@ import { KlickTipp } from '@model/KlickTipp'
import { getKlickTippUser } from '@/apis/KlicktippController' import { getKlickTippUser } from '@/apis/KlicktippController'
import { klickTippLogger as logger } from '@/server/logger' import { klickTippLogger as logger } from '@/server/logger'
// export const klicktippRegistrationMiddleware: MiddlewareFn = async (
// // Only for demo
// /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
// { root, args, context, info },
// next,
// ) => {
// // Do Something here before resolver is called
// const result = await next()
// // Do Something here after resolver is completed
// await klicktippSignIn(result.email, result.language, result.firstName, result.lastName)
// return result
// }
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async ( export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
{ root, args, context, info }, { root, args, context, info },

View File

@ -31,8 +31,8 @@ const context = {
export const cleanDB = async () => { export const cleanDB = async () => {
// this only works as long we do not have foreign key constraints // this only works as long we do not have foreign key constraints
for (let i = 0; i < entities.length; i++) { for (const entity of entities) {
await resetEntity(entities[i]) await resetEntity(entity)
} }
} }
@ -54,9 +54,8 @@ const run = async () => {
logger.info('##seed## clean database successful...') logger.info('##seed## clean database successful...')
// seed the standard users // seed the standard users
for (let i = 0; i < users.length; i++) { for (const user of users) {
const dbUser = await userFactory(seedClient, users[i]) await userFactory(seedClient, user)
logger.info(`##seed## seed standard users[ ${i} ]= ${JSON.stringify(dbUser, null, 2)}`)
} }
logger.info('##seed## seeding all standard users successful...') logger.info('##seed## seeding all standard users successful...')
@ -73,20 +72,20 @@ const run = async () => {
logger.info('##seed## seeding all random users successful...') logger.info('##seed## seeding all random users successful...')
// create GDD // create GDD
for (let i = 0; i < creations.length; i++) { for (const creation of creations) {
await creationFactory(seedClient, creations[i]) await creationFactory(seedClient, creation)
} }
logger.info('##seed## seeding all creations successful...') logger.info('##seed## seeding all creations successful...')
// create Transaction Links // create Transaction Links
for (let i = 0; i < transactionLinks.length; i++) { for (const transactionLink of transactionLinks) {
await transactionLinkFactory(seedClient, transactionLinks[i]) await transactionLinkFactory(seedClient, transactionLink)
} }
logger.info('##seed## seeding all transactionLinks successful...') logger.info('##seed## seeding all transactionLinks successful...')
// create Contribution Links // create Contribution Links
for (let i = 0; i < contributionLinks.length; i++) { for (const contributionLink of contributionLinks) {
await contributionLinkFactory(seedClient, contributionLinks[i]) await contributionLinkFactory(seedClient, contributionLink)
} }
logger.info('##seed## seeding all contributionLinks successful...') logger.info('##seed## seeding all contributionLinks successful...')

View File

@ -21,7 +21,11 @@ import { plugins } from './plugins'
// TODO implement // TODO implement
// import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity";
type ServerDef = { apollo: ApolloServer; app: Express; con: Connection } interface ServerDef {
apollo: ApolloServer
app: Express
con: Connection
}
export const createServer = async ( export const createServer = async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -34,7 +38,7 @@ export const createServer = async (
// open mysql connection // open mysql connection
const con = await connection() const con = await connection()
if (!con || !con.isConnected) { if (!con?.isConnected) {
logger.fatal(`Couldn't open connection to database!`) logger.fatal(`Couldn't open connection to database!`)
throw new Error(`Fatal: Couldn't open connection to database`) throw new Error(`Fatal: Couldn't open connection to database`)
} }

View File

@ -7,6 +7,7 @@ import { configure, getLogger } from 'log4js'
import { CONFIG } from '@/config' import { CONFIG } from '@/config'
// eslint-disable-next-line security/detect-non-literal-fs-filename
const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8')) const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8'))
options.categories.backend.level = CONFIG.LOG_LEVEL options.categories.backend.level = CONFIG.LOG_LEVEL

View File

@ -12,7 +12,7 @@ const setHeadersPlugin = {
return { return {
willSendResponse(requestContext: any) { willSendResponse(requestContext: any) {
const { setHeaders = [] } = requestContext.context const { setHeaders = [] } = requestContext.context
setHeaders.forEach(({ key, value }: { [key: string]: string }) => { setHeaders.forEach(({ key, value }: Record<string, string>) => {
if (requestContext.response.http.headers.get(key)) { if (requestContext.response.http.headers.get(key)) {
requestContext.response.http.headers.set(key, value) requestContext.response.http.headers.set(key, value)
} else { } else {
@ -27,8 +27,8 @@ const setHeadersPlugin = {
const filterVariables = (variables: any) => { const filterVariables = (variables: any) => {
const vars = clonedeep(variables) const vars = clonedeep(variables)
if (vars && vars.password) vars.password = '***' if (vars?.password) vars.password = '***'
if (vars && vars.passwordNew) vars.passwordNew = '***' if (vars?.passwordNew) vars.passwordNew = '***'
return vars return vars
} }

View File

@ -14,10 +14,10 @@ const getDBVersion = async (): Promise<string | null> => {
const checkDBVersion = async (DB_VERSION: string): Promise<boolean> => { const checkDBVersion = async (DB_VERSION: string): Promise<boolean> => {
const dbVersion = await getDBVersion() const dbVersion = await getDBVersion()
if (!dbVersion || dbVersion.indexOf(DB_VERSION) === -1) { if (!dbVersion?.includes(DB_VERSION)) {
logger.error( logger.error(
`Wrong database version detected - the backend requires '${DB_VERSION}' but found '${ `Wrong database version detected - the backend requires '${DB_VERSION}' but found '${
dbVersion || 'None' dbVersion ?? 'None'
}`, }`,
) )
return false return false

View File

@ -11,8 +11,7 @@ export async function retrieveNotRegisteredEmails(): Promise<string[]> {
} }
const users = await User.find({ relations: ['emailContact'] }) const users = await User.find({ relations: ['emailContact'] })
const notRegisteredUser = [] const notRegisteredUser = []
for (let i = 0; i < users.length; i++) { for (const user of users) {
const user = users[i]
try { try {
await getKlickTippUser(user.emailContact.email) await getKlickTippUser(user.emailContact.email)
} catch (err) { } catch (err) {

View File

@ -1,11 +1,9 @@
import { Decimal } from 'decimal.js-light' import { Decimal } from 'decimal.js-light'
import i18n from 'i18n' import i18n from 'i18n'
export const objectValuesToArray = (obj: { [x: string]: string }): Array<string> => { export const objectValuesToArray = (obj: Record<string, string>): string[] =>
return Object.keys(obj).map(function (key) { // eslint-disable-next-line security/detect-object-injection
return obj[key] Object.keys(obj).map((key) => obj[key])
})
}
export const decimalSeparatorByLanguage = (a: Decimal, language: string): string => { export const decimalSeparatorByLanguage = (a: Decimal, language: string): string => {
const rememberLocaleToRestore = i18n.getLocale() const rememberLocaleToRestore = i18n.getLocale()

View File

@ -115,6 +115,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
) { ) {
const email = loginElopageBuy.payerEmail const email = loginElopageBuy.payerEmail
// eslint-disable-next-line security/detect-unsafe-regex
const VALIDATE_EMAIL = /^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/ const VALIDATE_EMAIL = /^[a-zA-Z0-9.!#$%&?*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
const VALIDATE_NAME = /^<>&;]{2,}$/ const VALIDATE_NAME = /^<>&;]{2,}$/
@ -146,7 +147,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
email, email,
firstName, firstName,
lastName, lastName,
publisherId: loginElopageBuy.publisherId || 0, // This seemed to be the default value if not set publisherId: loginElopageBuy.publisherId ?? 0, // This seemed to be the default value if not set
}) })
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

View File

@ -22,8 +22,8 @@ const context = {
export const cleanDB = async () => { export const cleanDB = async () => {
// this only works as lond we do not have foreign key constraints // this only works as lond we do not have foreign key constraints
for (let i = 0; i < entities.length; i++) { for (const entity of entities) {
await resetEntity(entities[i]) await resetEntity(entity)
} }
} }

View File

@ -382,6 +382,14 @@
dependencies: dependencies:
"@cspotcode/source-map-consumer" "0.8.0" "@cspotcode/source-map-consumer" "0.8.0"
"@eslint-community/eslint-plugin-eslint-comments@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.1.tgz#3c65061e27f155eae3744c3b30c5a8253a959040"
integrity sha512-/HZbjIGaVO2zLlWX3gRgiHmKRVvvqrC0zVu3eXnIj1ORxoyfGSj50l0PfDfqihyZAqrDYzSMdJesXzFjvAoiLQ==
dependencies:
escape-string-regexp "^1.0.5"
ignore "^5.2.4"
"@eslint-community/eslint-utils@^4.2.0": "@eslint-community/eslint-utils@^4.2.0":
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz#a831e6e468b4b2b5ae42bf658bea015bf10bc518" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz#a831e6e468b4b2b5ae42bf658bea015bf10bc518"
@ -3005,6 +3013,13 @@ eslint-plugin-promise@^6.1.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz#269a3e2772f62875661220631bd4dafcb4083816"
integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig== integrity sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==
eslint-plugin-security@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-security/-/eslint-plugin-security-1.7.1.tgz#0e9c4a471f6e4d3ca16413c7a4a51f3966ba16e4"
integrity sha512-sMStceig8AFglhhT2LqlU5r+/fn9OwsA72O5bBuQVTssPCdQAOQzL+oMn/ZcpeUY6KcNfLJArgcrsSULNjYYdQ==
dependencies:
safe-regex "^2.1.1"
eslint-plugin-type-graphql@^1.0.0: eslint-plugin-type-graphql@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-type-graphql/-/eslint-plugin-type-graphql-1.0.0.tgz#d348560ed628d6ca1dfcea35a02891432daafe6b" resolved "https://registry.yarnpkg.com/eslint-plugin-type-graphql/-/eslint-plugin-type-graphql-1.0.0.tgz#d348560ed628d6ca1dfcea35a02891432daafe6b"
@ -3649,7 +3664,7 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0:
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
"gradido-database@file:../database": "gradido-database@file:../database":
version "1.19.1" version "1.20.0"
dependencies: dependencies:
"@types/uuid" "^8.3.4" "@types/uuid" "^8.3.4"
cross-env "^7.0.3" cross-env "^7.0.3"
@ -3977,7 +3992,7 @@ ignore@^5.1.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
ignore@^5.2.0: ignore@^5.2.0, ignore@^5.2.4:
version "5.2.4" version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
@ -6140,6 +6155,11 @@ reflect-metadata@^0.1.13:
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
regexp-tree@~0.1.1:
version "0.1.27"
resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd"
integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==
regexp.prototype.flags@^1.4.3: regexp.prototype.flags@^1.4.3:
version "1.4.3" version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
@ -6279,6 +6299,13 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3" get-intrinsic "^1.1.3"
is-regex "^1.1.4" is-regex "^1.1.4"
safe-regex@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2"
integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==
dependencies:
regexp-tree "~0.1.1"
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"

View File

@ -117,7 +117,7 @@ server {
# TODO this could be a performance optimization # TODO this could be a performance optimization
#location /vue { #location /vue {
# alias /var/www/html/gradido/frontend/dist; # alias /var/www/html/gradido/frontend/build;
# index index.html; # index index.html;
# #
# location ~* \.(png)$ { # location ~* \.(png)$ {

View File

@ -103,7 +103,7 @@ server {
# TODO this could be a performance optimization # TODO this could be a performance optimization
#location /vue { #location /vue {
# alias /var/www/html/gradido/frontend/dist; # alias /var/www/html/gradido/frontend/build;
# index index.html; # index index.html;
# #
# location ~* \.(png)$ { # location ~* \.(png)$ {

View File

@ -15,6 +15,6 @@ export NVM_DIR="/root/.nvm"
$NPM_BIN install $NPM_BIN install
$NPM_BIN run build $NPM_BIN run build
# prezip for faster deliver throw nginx # prezip for faster deliver throw nginx
cd dist cd build
find . -type f -name "*.css" -exec gzip -9 -k {} \; find . -type f -name "*.css" -exec gzip -9 -k {} \;
find . -type f -name "*.js" -exec gzip -9 -k {} \; find . -type f -name "*.js" -exec gzip -9 -k {} \;

View File

@ -130,6 +130,15 @@ rm -Rf $PROJECT_ROOT/admin/node_modules
rm -Rf $PROJECT_ROOT/dht-node/node_modules rm -Rf $PROJECT_ROOT/dht-node/node_modules
rm -Rf $PROJECT_ROOT/federation/node_modules rm -Rf $PROJECT_ROOT/federation/node_modules
# Remove build folders
# we had problems with corrupted incremtal builds
rm -Rf $PROJECT_ROOT/database/build
rm -Rf $PROJECT_ROOT/backend/build
rm -Rf $PROJECT_ROOT/frontend/build
rm -Rf $PROJECT_ROOT/admin/build
rm -Rf $PROJECT_ROOT/dht-node/build
rm -Rf $PROJECT_ROOT/federation/build
# Regenerate .env files # Regenerate .env files
cp -f $PROJECT_ROOT/database/.env $PROJECT_ROOT/database/.env.bak cp -f $PROJECT_ROOT/database/.env $PROJECT_ROOT/database/.env.bak
cp -f $PROJECT_ROOT/backend/.env $PROJECT_ROOT/backend/.env.bak cp -f $PROJECT_ROOT/backend/.env $PROJECT_ROOT/backend/.env.bak

View File

@ -35,6 +35,6 @@ Cypress.Commands.add('login', (email, password) => {
} }
cy.visit('/') cy.visit('/')
window.localStorage.setItem('vuex', JSON.stringify(vuexToken)) window.localStorage.setItem('gradido-frontend', JSON.stringify(vuexToken))
}) })
}) })

View File

@ -1,3 +1,3 @@
node_modules/ node_modules/
dist/ build/
coverage/ coverage/

2
frontend/.gitignore vendored
View File

@ -1,6 +1,6 @@
.DS_Store .DS_Store
node_modules/ node_modules/
dist/ build/
.cache/ .cache/
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*

View File

@ -84,7 +84,7 @@ CMD /bin/sh -c "yarn run dev"
FROM base as production FROM base as production
# Copy "binary"-files from build image # Copy "binary"-files from build image
COPY --from=build ${DOCKER_WORKDIR}/dist ./dist COPY --from=build ${DOCKER_WORKDIR}/build ./build
# We also copy the node_modules express and serve-static for the run script # We also copy the node_modules express and serve-static for the run script
COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules
# Copy static files # Copy static files

View File

@ -7,7 +7,7 @@
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"dev": "yarn run serve", "dev": "yarn run serve",
"analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "analyse-bundle": "yarn build && webpack-bundle-analyzer build/webpack.stats.json",
"lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .",
"stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'",
"test": "cross-env TZ=UTC jest", "test": "cross-env TZ=UTC jest",

View File

@ -9,10 +9,10 @@ const port = process.env.PORT || 3000
// Express Server // Express Server
const app = express() const app = express()
// Serve files // Serve files
app.use(express.static(path.join(__dirname, '../dist'))) app.use(express.static(path.join(__dirname, '../build')))
// Default to index.html // Default to index.html
app.get('*', (req, res) => { app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../dist/index.html')) res.sendFile(path.join(__dirname, '../build/index.html'))
}) })
app.listen(port, hostname, () => { app.listen(port, hostname, () => {

View File

@ -91,6 +91,7 @@ try {
store = new Vuex.Store({ store = new Vuex.Store({
plugins: [ plugins: [
createPersistedState({ createPersistedState({
key: 'gradido-frontend',
storage: window.localStorage, storage: window.localStorage,
}), }),
], ],

View File

@ -64,5 +64,5 @@ module.exports = {
// Enable CSS source maps. // Enable CSS source maps.
sourceMap: CONFIG.NODE_ENV !== 'production', sourceMap: CONFIG.NODE_ENV !== 'production',
}, },
outputDir: path.resolve(__dirname, './dist'), outputDir: path.resolve(__dirname, './build'),
} }

View File

@ -71,7 +71,7 @@ server {
# TODO this could be a performance optimization # TODO this could be a performance optimization
#location /vue { #location /vue {
# alias /var/www/html/gradido/frontend/dist; # alias /var/www/html/gradido/frontend/build;
# index index.html; # index index.html;
# #
# location ~* \.(png)$ { # location ~* \.(png)$ {