diff --git a/backend/.eslintignore b/backend/.eslintignore index f6b255e92..1ae86fe5e 100644 --- a/backend/.eslintignore +++ b/backend/.eslintignore @@ -1,3 +1,4 @@ node_modules **/*.min.js -build \ No newline at end of file +build +coverage \ No newline at end of file diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 6a7d74b38..798bef1e6 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -12,6 +12,8 @@ module.exports = { 'plugin:prettier/recommended', 'plugin:import/recommended', 'plugin:import/typescript', + 'plugin:security/recommended', + 'plugin:@eslint-community/eslint-comments/recommended', ], settings: { 'import/parsers': { @@ -151,6 +153,11 @@ module.exports = { 'promise/valid-params': 'warn', 'promise/prefer-await-to-callbacks': '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: [ // only for ts files diff --git a/backend/package.json b/backend/package.json index c5e0df3c5..8a8d14e00 100644 --- a/backend/package.json +++ b/backend/package.json @@ -46,6 +46,7 @@ "uuid": "^8.3.2" }, "devDependencies": { + "@eslint-community/eslint-plugin-eslint-comments": "^3.2.1", "@types/email-templates": "^10.0.1", "@types/express": "^4.17.12", "@types/faker": "^5.5.9", @@ -68,6 +69,7 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-security": "^1.7.1", "eslint-plugin-type-graphql": "^1.0.0", "faker": "^5.5.3", "graphql-tag": "^2.12.6", diff --git a/backend/src/apis/HttpRequest.ts b/backend/src/apis/HttpRequest.ts index f40d577bd..27578463a 100644 --- a/backend/src/apis/HttpRequest.ts +++ b/backend/src/apis/HttpRequest.ts @@ -7,7 +7,6 @@ import axios from 'axios' import { LogError } from '@/server/LogError' 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 => { logger.trace('POST', url, payload) try { @@ -25,7 +24,6 @@ export const apiPost = async (url: string, payload: unknown): Promise => { } } -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const apiGet = async (url: string): Promise => { logger.trace('GET: url=' + url) try { diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index ea3b5c19e..8d1ed8ae6 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -12,7 +12,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0065-refactor_communities_table', + DB_VERSION: '0066-x-community-sendcoins-transactions_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/backend/src/federation/client/1_0/FederationClient.ts b/backend/src/federation/client/1_0/FederationClient.ts deleted file mode 100644 index 743d17348..000000000 --- a/backend/src/federation/client/1_0/FederationClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { LogError } from '@/server/LogError' -import { backendLogger as logger } from '@/server/logger' - -export async function requestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - throw new LogError(`Request-Error:`, err) - } -} diff --git a/backend/src/federation/client/1_1/FederationClient.ts b/backend/src/federation/client/1_1/FederationClient.ts deleted file mode 100644 index 35c88bf3b..000000000 --- a/backend/src/federation/client/1_1/FederationClient.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { gql } from 'graphql-request' - -import { GraphQLGetClient } from '@/federation/client/GraphQLGetClient' -import { LogError } from '@/server/LogError' -import { backendLogger as logger } from '@/server/logger' - -export async function requestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - let endpoint = dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/' - endpoint = `${endpoint}${dbCom.apiVersion}/` - logger.info(`requestGetPublicKey with endpoint='${endpoint}'...`) - - const graphQLClient = GraphQLGetClient.getInstance(endpoint) - logger.debug(`graphQLClient=${JSON.stringify(graphQLClient)}`) - const query = gql` - query { - getPublicKey { - publicKey - } - } - ` - const variables = {} - - try { - const { data, errors, extensions, headers, status } = await graphQLClient.rawRequest( - query, - variables, - ) - logger.debug(`Response-Data:`, data, errors, extensions, headers, status) - if (data) { - logger.debug(`Response-PublicKey:`, data.getPublicKey.publicKey) - logger.info(`requestGetPublicKey processed successfully`) - return data.getPublicKey.publicKey - } - logger.warn(`requestGetPublicKey processed without response data`) - } catch (err) { - throw new LogError(`Request-Error:`, err) - } -} diff --git a/backend/src/federation/client/Client.ts b/backend/src/federation/client/Client.ts new file mode 100644 index 000000000..98f63c127 --- /dev/null +++ b/backend/src/federation/client/Client.ts @@ -0,0 +1,58 @@ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' + +import { ApiVersionType } from '@/federation/enum/apiVersionType' + +// eslint-disable-next-line camelcase +import { Client_1_0 } from './Client_1_0' +// eslint-disable-next-line camelcase +import { Client_1_1 } from './Client_1_1' + +// eslint-disable-next-line camelcase +type FederationClient = Client_1_0 | Client_1_1 + +interface ClientInstance { + id: number + // eslint-disable-next-line no-use-before-define + client: FederationClient +} + +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class Client { + private static instanceArray: ClientInstance[] = [] + + /** + * The Singleton's constructor should always be private to prevent direct + * construction calls with the `new` operator. + */ + // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function + private constructor() {} + + private static createFederationClient = (dbCom: DbFederatedCommunity) => { + switch (dbCom.apiVersion) { + case ApiVersionType.V1_0: + return new Client_1_0(dbCom) + case ApiVersionType.V1_1: + return new Client_1_1(dbCom) + default: + return null + } + } + + /** + * The static method that controls the access to the singleton instance. + * + * This implementation let you subclass the Singleton class while keeping + * just one instance of each subclass around. + */ + public static getInstance(dbCom: DbFederatedCommunity): FederationClient | null { + const instance = Client.instanceArray.find((instance) => instance.id === dbCom.id) + if (instance) { + return instance.client + } + const client = Client.createFederationClient(dbCom) + if (client) { + Client.instanceArray.push({ id: dbCom.id, client } as ClientInstance) + } + return client + } +} diff --git a/backend/src/federation/client/Client_1_0.ts b/backend/src/federation/client/Client_1_0.ts new file mode 100644 index 000000000..0c0d458c8 --- /dev/null +++ b/backend/src/federation/client/Client_1_0.ts @@ -0,0 +1,49 @@ +import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' +import { GraphQLClient } from 'graphql-request' + +import { getPublicKey } from '@/federation/query/getPublicKey' +import { backendLogger as logger } from '@/server/logger' + +// eslint-disable-next-line camelcase +export class Client_1_0 { + dbCom: DbFederatedCommunity + endpoint: string + client: GraphQLClient + + constructor(dbCom: DbFederatedCommunity) { + this.dbCom = dbCom + this.endpoint = `${dbCom.endPoint.endsWith('/') ? dbCom.endPoint : dbCom.endPoint + '/'}${ + dbCom.apiVersion + }/` + this.client = new GraphQLClient(this.endpoint, { + method: 'GET', + jsonSerializer: { + parse: JSON.parse, + stringify: JSON.stringify, + }, + }) + } + + getPublicKey = async (): Promise => { + logger.info('Federation: getPublicKey from endpoint', this.endpoint) + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { data } = await this.client.rawRequest(getPublicKey, {}) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (!data?.getPublicKey?.publicKey) { + logger.warn('Federation: getPublicKey without response data from endpoint', this.endpoint) + return + } + logger.info( + 'Federation: getPublicKey successful from endpoint', + this.endpoint, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + data.getPublicKey.publicKey, + ) + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access + return data.getPublicKey.publicKey + } catch (err) { + logger.warn('Federation: getPublicKey failed for endpoint', this.endpoint) + } + } +} diff --git a/backend/src/federation/client/Client_1_1.ts b/backend/src/federation/client/Client_1_1.ts new file mode 100644 index 000000000..8525acc5d --- /dev/null +++ b/backend/src/federation/client/Client_1_1.ts @@ -0,0 +1,5 @@ +// eslint-disable-next-line camelcase +import { Client_1_0 } from './Client_1_0' + +// eslint-disable-next-line camelcase +export class Client_1_1 extends Client_1_0 {} diff --git a/backend/src/federation/client/GraphQLGetClient.ts b/backend/src/federation/client/GraphQLGetClient.ts deleted file mode 100644 index e2d3e6ed3..000000000 --- a/backend/src/federation/client/GraphQLGetClient.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { GraphQLClient } from 'graphql-request' -import { PatchedRequestInit } from 'graphql-request/dist/types' - -interface ClientInstance { - url: string - // eslint-disable-next-line no-use-before-define - client: GraphQLGetClient -} - -export class GraphQLGetClient extends GraphQLClient { - private static instanceArray: ClientInstance[] = [] - - /** - * The Singleton's constructor should always be private to prevent direct - * construction calls with the `new` operator. - */ - // eslint-disable-next-line no-useless-constructor - private constructor(url: string, options?: PatchedRequestInit) { - super(url, options) - } - - /** - * The static method that controls the access to the singleton instance. - * - * This implementation let you subclass the Singleton class while keeping - * just one instance of each subclass around. - */ - public static getInstance(url: string): GraphQLGetClient { - const instance = GraphQLGetClient.instanceArray.find((instance) => instance.url === url) - if (instance) { - return instance.client - } - const client = new GraphQLGetClient(url, { - method: 'GET', - jsonSerializer: { - parse: JSON.parse, - stringify: JSON.stringify, - }, - }) - GraphQLGetClient.instanceArray.push({ url, client } as ClientInstance) - return client - } -} diff --git a/backend/src/federation/query/getPublicKey.ts b/backend/src/federation/query/getPublicKey.ts new file mode 100644 index 000000000..a772a0cf1 --- /dev/null +++ b/backend/src/federation/query/getPublicKey.ts @@ -0,0 +1,9 @@ +import { gql } from 'graphql-request' + +export const getPublicKey = gql` + query { + getPublicKey { + publicKey + } + } +` diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index ed4897e09..77d0cc2ad 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -84,7 +84,8 @@ describe('validate Communities', () => { }) it('logs requestGetPublicKey for community api 1_0 ', () => { expect(logger.info).toBeCalledWith( - `requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`, + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_0/', ) }) }) @@ -114,12 +115,14 @@ describe('validate Communities', () => { }) it('logs requestGetPublicKey for community api 1_0 ', () => { expect(logger.info).toBeCalledWith( - `requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`, + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_0/', ) }) it('logs requestGetPublicKey for community api 1_1 ', () => { expect(logger.info).toBeCalledWith( - `requestGetPublicKey with endpoint='http//localhost:5001/api/1_1/'...`, + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_1/', ) }) }) @@ -152,18 +155,21 @@ describe('validate Communities', () => { }) it('logs requestGetPublicKey for community api 1_0 ', () => { expect(logger.info).toBeCalledWith( - `requestGetPublicKey with endpoint='http//localhost:5001/api/1_0/'...`, + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_0/', ) }) it('logs requestGetPublicKey for community api 1_1 ', () => { expect(logger.info).toBeCalledWith( - `requestGetPublicKey with endpoint='http//localhost:5001/api/1_1/'...`, + 'Federation: getPublicKey from endpoint', + 'http//localhost:5001/api/1_1/', ) }) it('logs unsupported api for community with api 2_0 ', () => { expect(logger.warn).toBeCalledWith( - `Federation: dbCom: ${dbCom.id} with unsupported apiVersion=2_0; supported versions`, - ['1_0', '1_1'], + 'Federation: dbCom with unsupported apiVersion', + dbCom.endPoint, + '2_0', ) }) }) diff --git a/backend/src/federation/validateCommunities.ts b/backend/src/federation/validateCommunities.ts index 073a8eded..4b337eda9 100644 --- a/backend/src/federation/validateCommunities.ts +++ b/backend/src/federation/validateCommunities.ts @@ -3,13 +3,9 @@ import { IsNull } from '@dbTools/typeorm' import { FederatedCommunity as DbFederatedCommunity } from '@entity/FederatedCommunity' -import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' -// eslint-disable-next-line camelcase -import { requestGetPublicKey as v1_0_requestGetPublicKey } from './client/1_0/FederationClient' -// eslint-disable-next-line camelcase -import { requestGetPublicKey as v1_1_requestGetPublicKey } from './client/1_1/FederationClient' +import { Client } from './client/Client' import { ApiVersionType } from './enum/apiVersionType' export function startValidateCommunities(timerInterval: number): void { @@ -36,56 +32,25 @@ export async function validateCommunities(): Promise { logger.debug('Federation: dbCom', dbCom) const apiValueStrings: string[] = Object.values(ApiVersionType) logger.debug(`suppported ApiVersions=`, apiValueStrings) - if (apiValueStrings.includes(dbCom.apiVersion)) { - logger.debug( - `Federation: validate publicKey for dbCom: ${dbCom.id} with apiVersion=${dbCom.apiVersion}`, - ) - try { - const pubKey = await invokeVersionedRequestGetPublicKey(dbCom) - logger.info( - 'Federation: received publicKey from endpoint', + if (!apiValueStrings.includes(dbCom.apiVersion)) { + logger.warn('Federation: dbCom with unsupported apiVersion', dbCom.endPoint, dbCom.apiVersion) + continue + } + try { + const client = Client.getInstance(dbCom) + const pubKey = await client?.getPublicKey() + if (pubKey && pubKey === dbCom.publicKey.toString()) { + await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) + logger.info('Federation: verified community', dbCom) + } else { + logger.warn( + 'Federation: received not matching publicKey:', pubKey, - `${dbCom.endPoint}/${dbCom.apiVersion}`, + dbCom.publicKey.toString(), ) - if (pubKey && pubKey === dbCom.publicKey.toString()) { - logger.info(`Federation: matching publicKey: ${pubKey}`) - await DbFederatedCommunity.update({ id: dbCom.id }, { verifiedAt: new Date() }) - logger.debug(`Federation: updated dbCom: ${JSON.stringify(dbCom)}`) - } else { - logger.warn( - `Federation: received not matching publicKey -> received: ${ - pubKey ?? 'null' - }, expected: ${dbCom.publicKey.toString()} `, - ) - // DbCommunity.delete({ id: dbCom.id }) - } - } catch (err) { - if (!isLogError(err)) { - logger.error(`Error:`, err) - } } - } else { - logger.warn( - `Federation: dbCom: ${dbCom.id} with unsupported apiVersion=${dbCom.apiVersion}; supported versions`, - apiValueStrings, - ) + } catch (err) { + logger.error(`Error:`, err) } } } - -function isLogError(err: unknown) { - return err instanceof LogError -} - -async function invokeVersionedRequestGetPublicKey( - dbCom: DbFederatedCommunity, -): Promise { - switch (dbCom.apiVersion) { - case ApiVersionType.V1_0: - return v1_0_requestGetPublicKey(dbCom) - case ApiVersionType.V1_1: - return v1_1_requestGetPublicKey(dbCom) - default: - return undefined - } -} diff --git a/backend/src/graphql/arg/UpdateUserInfosArgs.ts b/backend/src/graphql/arg/UpdateUserInfosArgs.ts index 2f9df8dd7..e57d4ec82 100644 --- a/backend/src/graphql/arg/UpdateUserInfosArgs.ts +++ b/backend/src/graphql/arg/UpdateUserInfosArgs.ts @@ -8,6 +8,9 @@ export class UpdateUserInfosArgs { @Field({ nullable: true }) lastName?: string + @Field({ nullable: true }) + alias?: string + @Field({ nullable: true }) language?: string diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index d2203dac3..fa1590523 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -43,6 +43,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateDecay } from '@/util/decay' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' +import { fullName } from '@/util/utilities' import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { @@ -500,6 +501,8 @@ export class ContributionResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + transaction.userGradidoID = user.gradidoID + transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null transaction.amount = contribution.amount transaction.creationDate = contribution.contributionDate diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 9d39a80ae..3c6ba31ab 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -1040,6 +1040,7 @@ describe('TransactionLinkResolver', () => { }) 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) + '$') expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) }) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index ca638d0be..d6649814a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -34,6 +34,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { calculateDecay } from '@/util/decay' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' +import { fullName } from '@/util/utilities' import { calculateBalance } from '@/util/validate' import { executeTransaction } from './TransactionResolver' @@ -266,6 +267,8 @@ export class TransactionLinkResolver { transaction.typeId = TransactionTypeId.CREATION transaction.memo = contribution.memo transaction.userId = contribution.userId + transaction.userGradidoID = user.gradidoID + transaction.userName = fullName(user.firstName, user.lastName) transaction.previous = lastTransaction ? lastTransaction.id : null transaction.amount = contribution.amount transaction.creationDate = contribution.contributionDate diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 80c8be070..3c540b1f6 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -29,6 +29,7 @@ import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { communityUser } from '@/util/communityUser' import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' +import { fullName } from '@/util/utilities' import { calculateBalance } from '@/util/validate' import { virtualLinkTransaction, virtualDecayTransaction } from '@/util/virtualTransactions' @@ -85,7 +86,11 @@ export const executeTransaction = async ( transactionSend.typeId = TransactionTypeId.SEND transactionSend.memo = memo transactionSend.userId = sender.id + transactionSend.userGradidoID = sender.gradidoID + transactionSend.userName = fullName(sender.firstName, sender.lastName) transactionSend.linkedUserId = recipient.id + transactionSend.linkedUserGradidoID = recipient.gradidoID + transactionSend.linkedUserName = fullName(recipient.firstName, recipient.lastName) transactionSend.amount = amount.mul(-1) transactionSend.balance = sendBalance.balance transactionSend.balanceDate = receivedCallDate @@ -101,7 +106,11 @@ export const executeTransaction = async ( transactionReceive.typeId = TransactionTypeId.RECEIVE transactionReceive.memo = memo transactionReceive.userId = recipient.id + transactionReceive.userGradidoID = recipient.gradidoID + transactionReceive.userName = fullName(recipient.firstName, recipient.lastName) transactionReceive.linkedUserId = sender.id + transactionReceive.linkedUserGradidoID = sender.gradidoID + transactionReceive.linkedUserName = fullName(sender.firstName, sender.lastName) transactionReceive.amount = amount const receiveBalance = await calculateBalance(recipient.id, amount, receivedCallDate) transactionReceive.balance = receiveBalance ? receiveBalance.balance : amount diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index ddfcf173a..7d71d74b1 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -1198,6 +1198,28 @@ describe('UserResolver', () => { }) }) + describe('alias', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('valid alias', () => { + it('updates the user in DB', async () => { + await mutate({ + mutation: updateUserInfos, + variables: { + alias: 'bibi_Bloxberg', + }, + }) + await expect(User.findOne()).resolves.toEqual( + expect.objectContaining({ + alias: 'bibi_Bloxberg', + }), + ) + }) + }) + }) + describe('language is not valid', () => { it('throws an error', async () => { jest.clearAllMocks() diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index ca38ae769..0afbfcc5a 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -73,6 +73,7 @@ import { getTimeDurationObject, printTimeDuration } from '@/util/time' import { FULL_CREATION_AVAILABLE } from './const/const' import { getUserCreations } from './util/creations' import { findUserByIdentifier } from './util/findUserByIdentifier' +import { validateAlias } from './util/validateAlias' // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-commonjs const random = require('random-bigint') @@ -504,6 +505,7 @@ export class UserResolver { { firstName, lastName, + alias, language, password, passwordNew, @@ -523,6 +525,10 @@ export class UserResolver { user.lastName = lastName } + if (alias && (await validateAlias(alias))) { + user.alias = alias + } + if (language) { if (!isLanguage(language)) { throw new LogError('Given language is not a valid language', language) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 1c0c0735e..d6f0e9af4 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -29,10 +29,12 @@ export const validateContribution = ( 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())) { throw new LogError( 'The amount to be created exceeds the amount still available for this month', amount, + // eslint-disable-next-line security/detect-object-injection creations[index], ) } @@ -151,6 +153,7 @@ export const updateCreations = ( if (index < 0) { 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()) return creations } @@ -169,6 +172,7 @@ export const getOpenCreations = async ( return { month: date.getMonth(), year: date.getFullYear(), + // eslint-disable-next-line security/detect-object-injection amount: creations[index], } }) diff --git a/backend/src/graphql/resolver/util/validateAlias.test.ts b/backend/src/graphql/resolver/util/validateAlias.test.ts new file mode 100644 index 000000000..0cb790edb --- /dev/null +++ b/backend/src/graphql/resolver/util/validateAlias.test.ts @@ -0,0 +1,125 @@ +import { Connection } from '@dbTools/typeorm' +import { User } from '@entity/User' +import { ApolloServerTestClient } from 'apollo-server-testing' + +import { testEnvironment, cleanDB } from '@test/helpers' +import { logger, i18n as localization } from '@test/testSetup' + +import { userFactory } from '@/seeds/factory/user' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' + +import { validateAlias } from './validateAlias' + +let con: Connection +let testEnv: { + mutate: ApolloServerTestClient['mutate'] + query: ApolloServerTestClient['query'] + con: Connection +} + +beforeAll(async () => { + testEnv = await testEnvironment(logger, localization) + con = testEnv.con + await cleanDB() +}) + +afterAll(async () => { + await cleanDB() + await con.close() +}) + +describe('validate alias', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + describe('alias too short', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Bi')).rejects.toEqual(new Error('Given alias is too short')) + expect(logger.error).toBeCalledWith('Given alias is too short', 'Bi') + }) + }) + + describe('alias too long', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('BibiBloxbergHexHexHex')).rejects.toEqual( + new Error('Given alias is too long'), + ) + expect(logger.error).toBeCalledWith('Given alias is too long', 'BibiBloxbergHexHexHex') + }) + }) + + describe('alias contains invalid characters', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Bibi.Bloxberg')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', 'Bibi.Bloxberg') + }) + }) + + describe('alias is a reserved word', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('admin')).rejects.toEqual(new Error('Alias is not allowed')) + expect(logger.error).toBeCalledWith('Alias is not allowed', 'admin') + }) + }) + + describe('alias is a reserved word with uppercase characters', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('Admin')).rejects.toEqual(new Error('Alias is not allowed')) + expect(logger.error).toBeCalledWith('Alias is not allowed', 'Admin') + }) + }) + + describe('hyphens and underscore', () => { + describe('alias starts with underscore', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('_bibi')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', '_bibi') + }) + }) + + describe('alias contains two following hyphens', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('bi--bi')).rejects.toEqual( + new Error('Invalid characters in alias'), + ) + expect(logger.error).toBeCalledWith('Invalid characters in alias', 'bi--bi') + }) + }) + }) + + describe('test against existing alias in database', () => { + beforeAll(async () => { + const bibi = await userFactory(testEnv, bibiBloxberg) + const user = await User.findOne({ id: bibi.id }) + if (user) { + user.alias = 'b-b' + await user.save() + } + }) + + describe('alias exists in database', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('b-b')).rejects.toEqual(new Error('Alias already in use')) + expect(logger.error).toBeCalledWith('Alias already in use', 'b-b') + }) + }) + + describe('alias exists in database with in lower-case', () => { + it('throws and logs an error', async () => { + await expect(validateAlias('b-B')).rejects.toEqual(new Error('Alias already in use')) + expect(logger.error).toBeCalledWith('Alias already in use', 'b-B') + }) + }) + + describe('valid alias', () => { + it('resolves to true', async () => { + await expect(validateAlias('bibi')).resolves.toEqual(true) + }) + }) + }) +}) diff --git a/backend/src/graphql/resolver/util/validateAlias.ts b/backend/src/graphql/resolver/util/validateAlias.ts new file mode 100644 index 000000000..dcea7824c --- /dev/null +++ b/backend/src/graphql/resolver/util/validateAlias.ts @@ -0,0 +1,38 @@ +import { Raw } from '@dbTools/typeorm' +import { User as DbUser } from '@entity/User' + +import { LogError } from '@/server/LogError' + +const reservedAlias = [ + 'admin', + 'email', + 'gast', + 'gdd', + 'gradido', + 'guest', + 'home', + 'root', + 'support', + 'temp', + 'tmp', + 'tmp', + 'user', + 'usr', + 'var', +] + +export const validateAlias = async (alias: string): Promise => { + if (alias.length < 3) throw new LogError('Given alias is too short', alias) + if (alias.length > 20) throw new LogError('Given alias is too long', alias) + /* eslint-disable-next-line security/detect-unsafe-regex */ + if (!alias.match(/^[0-9A-Za-z]([_-]?[A-Za-z0-9])+$/)) + throw new LogError('Invalid characters in alias', alias) + if (reservedAlias.includes(alias.toLowerCase())) throw new LogError('Alias is not allowed', alias) + const aliasInUse = await DbUser.find({ + where: { alias: Raw((a) => `LOWER(${a}) = "${alias.toLowerCase()}"`) }, + }) + if (aliasInUse.length !== 0) { + throw new LogError('Alias already in use', alias) + } + return true +} diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 2dfe96766..22e0b1b09 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -28,6 +28,7 @@ export const updateUserInfos = gql` mutation ( $firstName: String $lastName: String + $alias: String $password: String $passwordNew: String $locale: String @@ -37,6 +38,7 @@ export const updateUserInfos = gql` updateUserInfos( firstName: $firstName lastName: $lastName + alias: $alias password: $password passwordNew: $passwordNew language: $locale diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index fab81eb95..77fa51990 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -54,9 +54,8 @@ const run = async () => { logger.info('##seed## clean database successful...') // seed the standard users - for (let i = 0; i < users.length; i++) { - const dbUser = await userFactory(seedClient, users[i]) - logger.info(`##seed## seed standard users[ ${i} ]= ${JSON.stringify(dbUser, null, 2)}`) + for (const user of users) { + await userFactory(seedClient, user) } logger.info('##seed## seeding all standard users successful...') diff --git a/backend/src/server/logger.ts b/backend/src/server/logger.ts index d1edbd8fb..0f146b7f7 100644 --- a/backend/src/server/logger.ts +++ b/backend/src/server/logger.ts @@ -7,6 +7,7 @@ import { configure, getLogger } from 'log4js' import { CONFIG } from '@/config' +// eslint-disable-next-line security/detect-non-literal-fs-filename const options = JSON.parse(readFileSync(CONFIG.LOG4JS_CONFIG, 'utf-8')) options.categories.backend.level = CONFIG.LOG_LEVEL diff --git a/backend/src/util/utilities.ts b/backend/src/util/utilities.ts index e9935bbd4..904c86226 100644 --- a/backend/src/util/utilities.ts +++ b/backend/src/util/utilities.ts @@ -1,11 +1,9 @@ import { Decimal } from 'decimal.js-light' import i18n from 'i18n' -export const objectValuesToArray = (obj: Record): string[] => { - return Object.keys(obj).map(function (key) { - return obj[key] - }) -} +export const objectValuesToArray = (obj: Record): string[] => + // eslint-disable-next-line security/detect-object-injection + Object.keys(obj).map((key) => obj[key]) export const decimalSeparatorByLanguage = (a: Decimal, language: string): string => { const rememberLocaleToRestore = i18n.getLocale() @@ -14,3 +12,6 @@ export const decimalSeparatorByLanguage = (a: Decimal, language: string): string i18n.setLocale(rememberLocaleToRestore) return result } + +export const fullName = (firstName: string, lastName: string): string => + [firstName, lastName].filter(Boolean).join(' ') diff --git a/backend/src/util/virtualTransactions.ts b/backend/src/util/virtualTransactions.ts index 5d1fbbfd3..a10e566d1 100644 --- a/backend/src/util/virtualTransactions.ts +++ b/backend/src/util/virtualTransactions.ts @@ -54,6 +54,10 @@ const virtualLinkTransaction = ( creationDate: null, contribution: null, ...defaultModelFunctions, + userGradidoID: '', + userName: null, + linkedUserGradidoID: null, + linkedUserName: null, } return new Transaction(linkDbTransaction, user) } @@ -84,6 +88,10 @@ const virtualDecayTransaction = ( creationDate: null, contribution: null, ...defaultModelFunctions, + userGradidoID: '', + userName: null, + linkedUserGradidoID: null, + linkedUserName: null, } return new Transaction(decayDbTransaction, user) } diff --git a/backend/src/webhook/elopage.ts b/backend/src/webhook/elopage.ts index 7a779fadd..07e7d4ecf 100644 --- a/backend/src/webhook/elopage.ts +++ b/backend/src/webhook/elopage.ts @@ -115,6 +115,7 @@ export const elopageWebhook = async (req: any, res: any): Promise => { ) { 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_NAME = /^<>&;]{2,}$/ diff --git a/backend/yarn.lock b/backend/yarn.lock index 1bc8c64fd..237a265e3 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -382,6 +382,14 @@ dependencies: "@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": version "4.2.0" 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" 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: version "1.0.0" 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== "gradido-database@file:../database": - version "1.19.1" + version "1.20.0" dependencies: "@types/uuid" "^8.3.4" 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" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.2.0: +ignore@^5.2.0, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" 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" 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: version "1.4.3" 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" 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": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" diff --git a/database/entity/0066-x-community-sendcoins-transactions_table/Transaction.ts b/database/entity/0066-x-community-sendcoins-transactions_table/Transaction.ts new file mode 100644 index 000000000..4220cfadc --- /dev/null +++ b/database/entity/0066-x-community-sendcoins-transactions_table/Transaction.ts @@ -0,0 +1,139 @@ +import Decimal from 'decimal.js-light' +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm' +import { DecimalTransformer } from '../../src/typeorm/DecimalTransformer' +import { Contribution } from '../Contribution' + +@Entity('transactions') +export class Transaction extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ type: 'int', unsigned: true, unique: true, nullable: true, default: null }) + previous: number | null + + @Column({ name: 'type_id', unsigned: true, nullable: false }) + typeId: number + + @Column({ + name: 'transaction_link_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + transactionLinkId?: number | null + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + amount: Decimal + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + balance: Decimal + + @Column({ + name: 'balance_date', + type: 'datetime', + default: () => 'CURRENT_TIMESTAMP', + nullable: false, + }) + balanceDate: Date + + @Column({ + type: 'decimal', + precision: 40, + scale: 20, + nullable: false, + transformer: DecimalTransformer, + }) + decay: Decimal + + @Column({ + name: 'decay_start', + type: 'datetime', + nullable: true, + default: null, + }) + decayStart: Date | null + + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + memo: string + + @Column({ name: 'creation_date', type: 'datetime', nullable: true, default: null }) + creationDate: Date | null + + @Column({ name: 'user_id', unsigned: true, nullable: false }) + userId: number + + @Column({ + name: 'user_gradido_id', + type: 'varchar', + length: 36, + nullable: false, + collation: 'utf8mb4_unicode_ci', + }) + userGradidoID: string + + @Column({ + name: 'user_name', + type: 'varchar', + length: 512, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + userName: string | null + + @Column({ + name: 'linked_user_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + linkedUserId?: number | null + + @Column({ + name: 'linked_user_gradido_id', + type: 'varchar', + length: 36, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + linkedUserGradidoID: string | null + + @Column({ + name: 'linked_user_name', + type: 'varchar', + length: 512, + nullable: true, + collation: 'utf8mb4_unicode_ci', + }) + linkedUserName: string | null + + @Column({ + name: 'linked_transaction_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + linkedTransactionId?: number | null + + @OneToOne(() => Contribution, (contribution) => contribution.transaction) + @JoinColumn({ name: 'id', referencedColumnName: 'transactionId' }) + contribution?: Contribution | null + + @OneToOne(() => Transaction) + @JoinColumn({ name: 'previous' }) + previousTransaction?: Transaction | null +} diff --git a/database/entity/Transaction.ts b/database/entity/Transaction.ts index 5365b0f70..4000e3c85 100644 --- a/database/entity/Transaction.ts +++ b/database/entity/Transaction.ts @@ -1 +1 @@ -export { Transaction } from './0036-unique_previous_in_transactions/Transaction' +export { Transaction } from './0066-x-community-sendcoins-transactions_table/Transaction' diff --git a/database/migrations/0066-x-community-sendcoins-transactions_table.ts b/database/migrations/0066-x-community-sendcoins-transactions_table.ts new file mode 100644 index 000000000..2a90f297a --- /dev/null +++ b/database/migrations/0066-x-community-sendcoins-transactions_table.ts @@ -0,0 +1,76 @@ +/* MIGRATION TO add users that have a transaction but do not exist */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `previous` int(10) unsigned DEFAULT NULL NULL AFTER `id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `type_id` int(10) DEFAULT NULL NULL AFTER `previous`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `transaction_link_id` int(10) unsigned DEFAULT NULL NULL AFTER `type_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `amount` decimal(40,20) DEFAULT NULL NULL AFTER `transaction_link_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `balance` decimal(40,20) DEFAULT NULL NULL AFTER `amount`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `balance_date` datetime(3) DEFAULT current_timestamp(3) NOT NULL AFTER `balance`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `decay` decimal(40,20) DEFAULT NULL NULL AFTER `balance_date`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `decay_start` datetime(3) DEFAULT NULL NULL AFTER `decay`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL AFTER `decay_start`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `creation_date` datetime(3) DEFAULT NULL NULL AFTER `memo`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `user_id` int(10) unsigned NOT NULL AFTER `creation_date`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `user_gradido_id` char(36) DEFAULT NULL NULL AFTER `user_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `user_name` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL NULL AFTER `user_gradido_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `linked_user_id` int(10) unsigned DEFAULT NULL NULL AFTER `user_name`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `linked_user_gradido_id` char(36) DEFAULT NULL NULL AFTER `linked_user_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `linked_user_name` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL NULL AFTER `linked_user_gradido_id`;', + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `linked_transaction_id` int(10) DEFAULT NULL NULL AFTER `linked_user_name`;', + ) + await queryFn( + `UPDATE transactions t, users u SET t.user_gradido_id = u.gradido_id, t.user_name = concat(u.first_name, ' ', u.last_name) WHERE t.user_id = u.id and t.user_gradido_id is null;`, + ) + await queryFn( + 'ALTER TABLE `transactions` MODIFY COLUMN `user_gradido_id` char(36) NOT NULL AFTER `user_id`;', + ) + await queryFn( + `UPDATE transactions t, users u SET t.linked_user_gradido_id = u.gradido_id, t.linked_user_name = concat(u.first_name, ' ', u.last_name) WHERE t.linked_user_id = u.id and t.linked_user_gradido_id is null;`, + ) +} + +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_gradido_id`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_name`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `linked_user_gradido_id`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `linked_user_name`;') +} diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 5c4676337..eca5dbbb5 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -3,7 +3,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0065-refactor_communities_table', + DB_VERSION: '0066-x-community-sendcoins-transactions_table', LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL || 'info', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 70a155d63..66d8a056c 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -11,7 +11,7 @@ Decimal.set({ */ const constants = { - DB_VERSION: '0065-refactor_communities_table', + DB_VERSION: '0066-x-community-sendcoins-transactions_table', // DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info diff --git a/federation/src/server/plugins.ts b/federation/src/server/plugins.ts index 541c68ca2..38fdfbe9f 100644 --- a/federation/src/server/plugins.ts +++ b/federation/src/server/plugins.ts @@ -23,8 +23,8 @@ const setHeadersPlugin = { const filterVariables = (variables: any) => { const vars = clonedeep(variables) - if (vars.password) vars.password = '***' - if (vars.passwordNew) vars.passwordNew = '***' + if (vars && vars.password) vars.password = '***' + if (vars && vars.passwordNew) vars.passwordNew = '***' return vars }