mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2878-trigger-export-klicktipp
This commit is contained in:
commit
013ea81614
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
**/*.min.js
|
||||
build
|
||||
build
|
||||
coverage
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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<any> => {
|
||||
logger.trace('POST', url, payload)
|
||||
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> => {
|
||||
logger.trace('GET: url=' + url)
|
||||
try {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<string | undefined> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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<string | undefined> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
58
backend/src/federation/client/Client.ts
Normal file
58
backend/src/federation/client/Client.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
49
backend/src/federation/client/Client_1_0.ts
Normal file
49
backend/src/federation/client/Client_1_0.ts
Normal file
@ -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<string | undefined> => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
5
backend/src/federation/client/Client_1_1.ts
Normal file
5
backend/src/federation/client/Client_1_1.ts
Normal file
@ -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 {}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
9
backend/src/federation/query/getPublicKey.ts
Normal file
9
backend/src/federation/query/getPublicKey.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { gql } from 'graphql-request'
|
||||
|
||||
export const getPublicKey = gql`
|
||||
query {
|
||||
getPublicKey {
|
||||
publicKey
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -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',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -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<void> {
|
||||
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<string | undefined> {
|
||||
switch (dbCom.apiVersion) {
|
||||
case ApiVersionType.V1_0:
|
||||
return v1_0_requestGetPublicKey(dbCom)
|
||||
case ApiVersionType.V1_1:
|
||||
return v1_1_requestGetPublicKey(dbCom)
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ export class UpdateUserInfosArgs {
|
||||
@Field({ nullable: true })
|
||||
lastName?: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
alias?: string
|
||||
|
||||
@Field({ nullable: true })
|
||||
language?: string
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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],
|
||||
}
|
||||
})
|
||||
|
||||
125
backend/src/graphql/resolver/util/validateAlias.test.ts
Normal file
125
backend/src/graphql/resolver/util/validateAlias.test.ts
Normal file
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
38
backend/src/graphql/resolver/util/validateAlias.ts
Normal file
38
backend/src/graphql/resolver/util/validateAlias.ts
Normal file
@ -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<boolean> => {
|
||||
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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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...')
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { Decimal } from 'decimal.js-light'
|
||||
import i18n from 'i18n'
|
||||
|
||||
export const objectValuesToArray = (obj: Record<string, string>): string[] => {
|
||||
return Object.keys(obj).map(function (key) {
|
||||
return obj[key]
|
||||
})
|
||||
}
|
||||
export const objectValuesToArray = (obj: Record<string, string>): 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(' ')
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -115,6 +115,7 @@ export const elopageWebhook = async (req: any, res: any): Promise<void> => {
|
||||
) {
|
||||
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,}$/
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -1 +1 @@
|
||||
export { Transaction } from './0036-unique_previous_in_transactions/Transaction'
|
||||
export { Transaction } from './0066-x-community-sendcoins-transactions_table/Transaction'
|
||||
|
||||
@ -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<Array<any>>) {
|
||||
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<Array<any>>) {
|
||||
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`;')
|
||||
}
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user