diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c7f9b7c2..c1e363b52 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -431,7 +431,7 @@ jobs: unit_test_backend: name: Unit tests - Backend runs-on: ubuntu-latest - needs: [build_test_backend,build_test_mariadb] + needs: [build_test_mariadb] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -448,13 +448,6 @@ jobs: path: /tmp - name: Load Docker Image run: docker load < /tmp/mariadb.tar - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v2 - with: - name: docker-backend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/backend.tar ########################################################################## # UNIT TESTS BACKEND ##################################################### ########################################################################## @@ -469,7 +462,7 @@ jobs: run: sleep 30s shell: bash - name: backend Unit tests | test - run: cd database && yarn && yarn build && cd ../backend && yarn && yarn CI_workflow_test + run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test ########################################################################## # COVERAGE CHECK BACKEND ################################################# @@ -480,7 +473,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 38 + min_coverage: 48 token: ${{ github.token }} ########################################################################## diff --git a/backend/jest.config.js b/backend/jest.config.js index 75b674cb1..0e4643c63 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,21 +1,18 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ -module.exports = async () => { - process.env.TZ = 'UTC' - return { - verbose: true, - preset: 'ts-jest', - collectCoverage: true, - collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], - moduleNameMapper: { - '@entity/(.*)': '/../database/build/entity/$1', - // This is hack to fix a problem with the library `ts-mysql-migrate` which does differentiate between its ts/js state - '@dbTools/(.*)': '/../database/src/$1', - /* - '@dbTools/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/src/$1' - : '/../database/build/src/$1', - */ - }, - } +module.exports = { + verbose: true, + preset: 'ts-jest', + collectCoverage: true, + collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**'], + setupFiles: ['/test/testSetup.ts'], + moduleNameMapper: { + '@entity/(.*)': + process.env.NODE_ENV === 'development' + ? '/../database/entity/$1' + : '/../database/build/entity/$1', + '@dbTools/(.*)': + process.env.NODE_ENV === 'development' + ? '/../database/src/$1' + : '/../database/build/src/$1', + }, } diff --git a/backend/package.json b/backend/package.json index 7f4cfc239..93a954d3f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,8 +13,7 @@ "start": "node build/index.js", "dev": "nodemon -w src --ext ts --exec ts-node src/index.ts", "lint": "eslint . --ext .js,.ts", - "CI_workflow_test": "jest --runInBand --coverage ", - "test": "NODE_ENV=development jest --runInBand --coverage " + "test": "TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles" }, "dependencies": { "@types/jest": "^27.0.2", diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 990cde078..8961fc358 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv' dotenv.config() const constants = { - DB_VERSION: '0023-users_disabled_soft_delete', + DB_VERSION: '0024-combine_transaction_tables', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 } diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 49d7740fc..fd12af0f8 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -2,27 +2,27 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql' -import { getCustomRepository, Raw } from '@dbTools/typeorm' +import { getCustomRepository, ObjectLiteral, Raw } from '@dbTools/typeorm' import { UserAdmin, SearchUsersResult } from '../model/UserAdmin' import { PendingCreation } from '../model/PendingCreation' import { CreatePendingCreations } from '../model/CreatePendingCreations' import { UpdatePendingCreation } from '../model/UpdatePendingCreation' import { RIGHTS } from '../../auth/RIGHTS' -import { TransactionRepository } from '../../typeorm/repository/Transaction' import { UserRepository } from '../../typeorm/repository/User' import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs' import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs' import SearchUsersArgs from '../arg/SearchUsersArgs' import moment from 'moment' import { Transaction } from '@entity/Transaction' -import { TransactionCreation } from '@entity/TransactionCreation' import { UserTransaction } from '@entity/UserTransaction' import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction' -import { BalanceRepository } from '../../typeorm/repository/Balance' import { calculateDecay } from '../../util/decay' import { AdminPendingCreation } from '@entity/AdminPendingCreation' import { hasElopageBuys } from '../../util/hasElopageBuys' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' +import { User } from '@entity/User' +import { TransactionTypeId } from '../enum/TransactionTypeId' +import { Balance } from '@entity/Balance' // const EMAIL_OPT_IN_REGISTER = 1 // const EMAIL_OPT_UNKNOWN = 3 // elopage? @@ -35,8 +35,26 @@ export class AdminResolver { @Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs, ): Promise { const userRepository = getCustomRepository(UserRepository) - const users = await userRepository.findBySearchCriteria(searchText) - let adminUsers = await Promise.all( + + const filterCriteria: ObjectLiteral[] = [] + if (notActivated) { + filterCriteria.push({ emailChecked: false }) + } + // prevent overfetching data from db, select only needed columns + // prevent reading and transmitting data from db at least 300 Bytes + // one of my example dataset shrink down from 342 Bytes to 42 Bytes, that's ~88% saved db bandwith + const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked'] + const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered( + userFields.map((fieldName) => { + return 'user.' + fieldName + }), + searchText, + filterCriteria, + currentPage, + pageSize, + ) + + const adminUsers = await Promise.all( users.map(async (user) => { const adminUser = new UserAdmin() adminUser.userId = user.id @@ -57,6 +75,7 @@ export class AdminResolver { updatedAt: 'DESC', createdAt: 'DESC', }, + select: ['updatedAt', 'createdAt'], }, ) if (emailOptIn) { @@ -70,11 +89,9 @@ export class AdminResolver { return adminUser }), ) - if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked) - const first = (currentPage - 1) * pageSize return { - userCount: adminUsers.length, - userList: adminUsers.slice(first, first + pageSize), + userCount: count, + userList: adminUsers, } } @@ -83,8 +100,13 @@ export class AdminResolver { async createPendingCreation( @Args() { email, amount, memo, creationDate, moderator }: CreatePendingCreationArgs, ): Promise { - const userRepository = getCustomRepository(UserRepository) - const user = await userRepository.findByEmail(email) + const user = await User.findOne({ email }, { withDeleted: true }) + if (!user) { + throw new Error(`Could not find user with email: ${email}`) + } + if (user.deletedAt) { + throw new Error('This user was deleted. Cannot make a creation.') + } if (!user.emailChecked) { throw new Error('Creation could not be saved, Email is not activated') } @@ -135,8 +157,13 @@ export class AdminResolver { async updatePendingCreation( @Args() { id, email, amount, memo, creationDate, moderator }: UpdatePendingCreationArgs, ): Promise { - const userRepository = getCustomRepository(UserRepository) - const user = await userRepository.findByEmail(email) + const user = await User.findOne({ email }, { withDeleted: true }) + if (!user) { + throw new Error(`Could not find user with email: ${email}`) + } + if (user.deletedAt) { + throw new Error(`User was deleted (${email})`) + } const pendingCreationToUpdate = await AdminPendingCreation.findOneOrFail({ id }) @@ -213,23 +240,17 @@ export class AdminResolver { if (moderatorUser.id === pendingCreation.userId) throw new Error('Moderator can not confirm own pending creation') - const transactionRepository = getCustomRepository(TransactionRepository) const receivedCallDate = new Date() let transaction = new Transaction() - transaction.transactionTypeId = 1 + transaction.transactionTypeId = TransactionTypeId.CREATION transaction.memo = pendingCreation.memo transaction.received = receivedCallDate - transaction = await transactionRepository.save(transaction) + transaction.userId = pendingCreation.userId + transaction.amount = BigInt(parseInt(pendingCreation.amount.toString())) + transaction.creationDate = pendingCreation.date + transaction = await transaction.save() if (!transaction) throw new Error('Could not create transaction') - let transactionCreation = new TransactionCreation() - transactionCreation.transactionId = transaction.id - transactionCreation.userId = pendingCreation.userId - transactionCreation.amount = parseInt(pendingCreation.amount.toString()) - transactionCreation.targetDate = pendingCreation.date - transactionCreation = await TransactionCreation.save(transactionCreation) - if (!transactionCreation) throw new Error('Could not create transactionCreation') - const userTransactionRepository = getCustomRepository(UserTransactionRepository) const lastUserTransaction = await userTransactionRepository.findLastForUser( pendingCreation.userId, @@ -257,15 +278,15 @@ export class AdminResolver { throw new Error('Error saving user transaction: ' + error) }) - const balanceRepository = getCustomRepository(BalanceRepository) - let userBalance = await balanceRepository.findByUser(pendingCreation.userId) - - if (!userBalance) userBalance = balanceRepository.create() - userBalance.userId = pendingCreation.userId + let userBalance = await Balance.findOne({ userId: pendingCreation.userId }) + if (!userBalance) { + userBalance = new Balance() + userBalance.userId = pendingCreation.userId + } userBalance.amount = Number(newBalance) userBalance.modified = receivedCallDate userBalance.recordDate = receivedCallDate - await balanceRepository.save(userBalance) + await userBalance.save() await AdminPendingCreation.delete(pendingCreation) return true @@ -279,12 +300,13 @@ async function getUserCreations(id: number): Promise { const lastMonthNumber = moment().subtract(1, 'month').format('M') const currentMonthNumber = moment().format('M') - const createdAmountsQuery = await TransactionCreation.createQueryBuilder('transaction_creations') - .select('MONTH(transaction_creations.target_date)', 'target_month') - .addSelect('SUM(transaction_creations.amount)', 'sum') - .where('transaction_creations.state_user_id = :id', { id }) + const createdAmountsQuery = await Transaction.createQueryBuilder('transactions') + .select('MONTH(transactions.creation_date)', 'target_month') + .addSelect('SUM(transactions.amount)', 'sum') + .where('transactions.user_id = :id', { id }) + .andWhere('transactions.transaction_type_id = :type', { type: TransactionTypeId.CREATION }) .andWhere({ - targetDate: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, { + creationDate: Raw((alias) => `${alias} >= :date and ${alias} < :endDate`, { date: dateBeforeLastMonth, endDate: dateNextMonth, }), diff --git a/backend/src/graphql/resolver/BalanceResolver.ts b/backend/src/graphql/resolver/BalanceResolver.ts index 0ecfbb7cd..fff073b99 100644 --- a/backend/src/graphql/resolver/BalanceResolver.ts +++ b/backend/src/graphql/resolver/BalanceResolver.ts @@ -4,11 +4,11 @@ import { Resolver, Query, Ctx, Authorized } from 'type-graphql' import { getCustomRepository } from '@dbTools/typeorm' import { Balance } from '../model/Balance' -import { BalanceRepository } from '../../typeorm/repository/Balance' import { UserRepository } from '../../typeorm/repository/User' import { calculateDecay } from '../../util/decay' import { roundFloorFrom4 } from '../../util/round' import { RIGHTS } from '../../auth/RIGHTS' +import { Balance as dbBalance } from '@entity/Balance' @Resolver() export class BalanceResolver { @@ -16,11 +16,10 @@ export class BalanceResolver { @Query(() => Balance) async balance(@Ctx() context: any): Promise { // load user and balance - const balanceRepository = getCustomRepository(BalanceRepository) const userRepository = getCustomRepository(UserRepository) const userEntity = await userRepository.findByPubkeyHex(context.pubKey) - const balanceEntity = await balanceRepository.findByUser(userEntity.id) + const balanceEntity = await dbBalance.findOne({ userId: userEntity.id }) const now = new Date() // No balance found diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 622bf37fe..7227f9621 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql' -import { getCustomRepository, getConnection, QueryRunner } from '@dbTools/typeorm' +import { getCustomRepository, getConnection, QueryRunner, In } from '@dbTools/typeorm' import CONFIG from '../../config' import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail' @@ -16,15 +16,12 @@ import Paginated from '../arg/Paginated' import { Order } from '../enum/Order' -import { BalanceRepository } from '../../typeorm/repository/Balance' import { UserRepository } from '../../typeorm/repository/User' import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction' -import { TransactionRepository } from '../../typeorm/repository/Transaction' import { User as dbUser } from '@entity/User' import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction' import { Transaction as dbTransaction } from '@entity/Transaction' -import { TransactionSendCoin as dbTransactionSendCoin } from '@entity/TransactionSendCoin' import { Balance as dbBalance } from '@entity/Balance' import { apiPost } from '../../apis/HttpRequest' @@ -50,15 +47,13 @@ async function calculateAndAddDecayTransactions( transactionIds.push(userTransaction.transactionId) }) - const transactionRepository = getCustomRepository(TransactionRepository) - const transactions = await transactionRepository.joinFullTransactionsByIds(transactionIds) - + const transactions = await dbTransaction.find({ where: { id: In(transactionIds) } }) const transactionIndiced: dbTransaction[] = [] transactions.forEach((transaction: dbTransaction) => { transactionIndiced[transaction.id] = transaction + involvedUserIds.push(transaction.userId) if (transaction.transactionTypeId === TransactionTypeId.SEND) { - involvedUserIds.push(transaction.transactionSendCoin.userId) - involvedUserIds.push(transaction.transactionSendCoin.recipiantUserId) + involvedUserIds.push(transaction.sendReceiverUserId!) // TODO ensure not null properly } }) // remove duplicates @@ -108,24 +103,21 @@ async function calculateAndAddDecayTransactions( // balance if (userTransaction.transactionTypeId === TransactionTypeId.CREATION) { // creation - const creation = transaction.transactionCreation - finalTransaction.name = 'Gradido Akademie' finalTransaction.type = TransactionType.CREATION // finalTransaction.targetDate = creation.targetDate - finalTransaction.balance = roundFloorFrom4(creation.amount) + finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion } else if (userTransaction.transactionTypeId === TransactionTypeId.SEND) { // send coin - const sendCoin = transaction.transactionSendCoin let otherUser: dbUser | undefined - finalTransaction.balance = roundFloorFrom4(sendCoin.amount) - if (sendCoin.userId === user.id) { + finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion + if (transaction.userId === user.id) { finalTransaction.type = TransactionType.SEND - otherUser = userIndiced[sendCoin.recipiantUserId] + otherUser = userIndiced.find((u) => u.id === transaction.sendReceiverUserId) // finalTransaction.pubkey = sendCoin.recipiantPublic - } else if (sendCoin.recipiantUserId === user.id) { + } else if (transaction.sendReceiverUserId === user.id) { finalTransaction.type = TransactionType.RECIEVE - otherUser = userIndiced[sendCoin.userId] + otherUser = userIndiced.find((u) => u.id === transaction.userId) // finalTransaction.pubkey = sendCoin.senderPublic } else { throw new Error('invalid transaction') @@ -153,61 +145,9 @@ async function calculateAndAddDecayTransactions( finalTransactions.push(decayTransaction) } } - return finalTransactions } -// Helper function -async function listTransactions( - currentPage: number, - pageSize: number, - order: Order, - user: dbUser, - onlyCreations: boolean, -): Promise { - let limit = pageSize - let offset = 0 - let skipFirstTransaction = false - if (currentPage > 1) { - offset = (currentPage - 1) * pageSize - 1 - limit++ - } - - if (offset && order === Order.ASC) { - offset-- - } - const userTransactionRepository = getCustomRepository(UserTransactionRepository) - let [userTransactions, userTransactionsCount] = await userTransactionRepository.findByUserPaged( - user.id, - limit, - offset, - order, - onlyCreations, - ) - skipFirstTransaction = userTransactionsCount > offset + limit - const decay = !(currentPage > 1) - let transactions: Transaction[] = [] - if (userTransactions.length) { - if (order === Order.DESC) { - userTransactions = userTransactions.reverse() - } - transactions = await calculateAndAddDecayTransactions( - userTransactions, - user, - decay, - skipFirstTransaction, - ) - if (order === Order.DESC) { - transactions = transactions.reverse() - } - } - - const transactionList = new TransactionList() - transactionList.count = userTransactionsCount - transactionList.transactions = transactions - return transactionList -} - // helper helper function async function updateStateBalance( user: dbUser, @@ -215,8 +155,7 @@ async function updateStateBalance( received: Date, queryRunner: QueryRunner, ): Promise { - const balanceRepository = getCustomRepository(BalanceRepository) - let balance = await balanceRepository.findByUser(user.id) + let balance = await dbBalance.findOne({ userId: user.id }) if (!balance) { balance = new dbBalance() balance.userId = user.id @@ -272,16 +211,6 @@ async function addUserTransaction( }) } -async function getPublicKey(email: string): Promise { - const user = await dbUser.findOne({ email: email }) - // User not found - if (!user) { - return null - } - - return user.pubKey.toString('hex') -} - @Resolver() export class TransactionResolver { @Authorized([RIGHTS.TRANSACTION_LIST]) @@ -299,43 +228,66 @@ export class TransactionResolver { ): Promise { // load user const userRepository = getCustomRepository(UserRepository) - let userEntity: dbUser | undefined - if (userId) { - userEntity = await userRepository.findOneOrFail({ id: userId }) - } else { - userEntity = await userRepository.findByPubkeyHex(context.pubKey) + const user = userId + ? await userRepository.findOneOrFail({ id: userId }, { withDeleted: true }) + : await userRepository.findByPubkeyHex(context.pubKey) + let limit = pageSize + let offset = 0 + let skipFirstTransaction = false + if (currentPage > 1) { + offset = (currentPage - 1) * pageSize - 1 + limit++ + } + if (offset && order === Order.ASC) { + offset-- + } + const userTransactionRepository = getCustomRepository(UserTransactionRepository) + const [userTransactions, userTransactionsCount] = + await userTransactionRepository.findByUserPaged(user.id, limit, offset, order, onlyCreations) + skipFirstTransaction = userTransactionsCount > offset + limit + const decay = !(currentPage > 1) + let transactions: Transaction[] = [] + if (userTransactions.length) { + if (order === Order.DESC) { + userTransactions.reverse() + } + transactions = await calculateAndAddDecayTransactions( + userTransactions, + user, + decay, + skipFirstTransaction, + ) + if (order === Order.DESC) { + transactions.reverse() + } } - const transactions = await listTransactions( - currentPage, - pageSize, - order, - userEntity, - onlyCreations, - ) + const transactionList = new TransactionList() + transactionList.count = userTransactionsCount + transactionList.transactions = transactions // get gdt sum - transactions.gdtSum = null + transactionList.gdtSum = null try { const resultGDTSum = await apiPost(`${CONFIG.GDT_API_URL}/GdtEntries/sumPerEmailApi`, { - email: userEntity.email, + email: user.email, }) - if (resultGDTSum.success) transactions.gdtSum = Number(resultGDTSum.data.sum) || 0 + if (resultGDTSum.success) transactionList.gdtSum = Number(resultGDTSum.data.sum) || 0 } catch (err: any) {} // get balance - const balanceRepository = getCustomRepository(BalanceRepository) - const balanceEntity = await balanceRepository.findByUser(userEntity.id) + const balanceEntity = await dbBalance.findOne({ userId: user.id }) if (balanceEntity) { const now = new Date() - transactions.balance = roundFloorFrom4(balanceEntity.amount) - transactions.decay = roundFloorFrom4( + transactionList.balance = roundFloorFrom4(balanceEntity.amount) + // TODO: Add a decay object here instead of static data representing the decay. + transactionList.decay = roundFloorFrom4( calculateDecay(balanceEntity.amount, balanceEntity.recordDate, now).balance, ) - transactions.decayDate = now.toString() + transactionList.decayDate = now.toString() } - return transactions + return transactionList } @Authorized([RIGHTS.SEND_COINS]) @@ -343,37 +295,28 @@ export class TransactionResolver { async sendCoins( @Args() { email, amount, memo }: TransactionSendArgs, @Ctx() context: any, - ): Promise { + ): Promise { // TODO this is subject to replay attacks - // validate sender user (logged in) const userRepository = getCustomRepository(UserRepository) const senderUser = await userRepository.findByPubkeyHex(context.pubKey) if (senderUser.pubKey.length !== 32) { throw new Error('invalid sender public key') } + // validate amount if (!hasUserAmount(senderUser, amount)) { - throw new Error("user hasn't enough GDD") + throw new Error("user hasn't enough GDD or amount is < 0") } // validate recipient user - // TODO: the detour over the public key is unnecessary - const recipiantPublicKey = await getPublicKey(email) - if (!recipiantPublicKey) { + const recipientUser = await dbUser.findOne({ email: email }, { withDeleted: true }) + if (!recipientUser) { throw new Error('recipient not known') } - if (!isHexPublicKey(recipiantPublicKey)) { - throw new Error('invalid recipiant public key') + if (recipientUser.deletedAt) { + throw new Error('The recipient account was deleted') } - const recipiantUser = await userRepository.findByPubkeyHex(recipiantPublicKey) - if (!recipiantUser) { - throw new Error('Cannot find recipiant user by local send coins transaction') - } else if (recipiantUser.deletedAt) { - throw new Error('recipiant user account is disabled') - } - - // validate amount - if (amount <= 0) { - throw new Error('invalid amount') + if (!isHexPublicKey(recipientUser.pubKey.toString('hex'))) { + throw new Error('invalid recipient public key') } const centAmount = Math.trunc(amount * 10000) @@ -383,17 +326,16 @@ export class TransactionResolver { await queryRunner.startTransaction('READ UNCOMMITTED') try { // transaction - let transaction = new dbTransaction() + const transaction = new dbTransaction() transaction.transactionTypeId = TransactionTypeId.SEND transaction.memo = memo + transaction.userId = senderUser.id + transaction.pubkey = senderUser.pubKey + transaction.sendReceiverUserId = recipientUser.id + transaction.sendReceiverPublicKey = recipientUser.pubKey + transaction.amount = BigInt(centAmount) - // TODO: NO! this is problematic in its construction - const insertResult = await queryRunner.manager.insert(dbTransaction, transaction) - transaction = await queryRunner.manager - .findOneOrFail(dbTransaction, insertResult.generatedMaps[0].id) - .catch((error) => { - throw new Error('error loading saved transaction: ' + error) - }) + await queryRunner.manager.insert(dbTransaction, transaction) // Insert Transaction: sender - amount const senderUserTransactionBalance = await addUserTransaction( @@ -405,7 +347,7 @@ export class TransactionResolver { // Insert Transaction: recipient + amount const recipiantUserTransactionBalance = await addUserTransaction( - recipiantUser, + recipientUser, transaction, centAmount, queryRunner, @@ -421,7 +363,7 @@ export class TransactionResolver { // Update Balance: recipiant + amount const recipiantStateBalance = await updateStateBalance( - recipiantUser, + recipientUser, centAmount, transaction.received, queryRunner, @@ -434,18 +376,10 @@ export class TransactionResolver { throw new Error('db data corrupted, recipiant') } - // transactionSendCoin - const transactionSendCoin = new dbTransactionSendCoin() - transactionSendCoin.transactionId = transaction.id - transactionSendCoin.userId = senderUser.id - transactionSendCoin.senderPublic = senderUser.pubKey - transactionSendCoin.recipiantUserId = recipiantUser.id - transactionSendCoin.recipiantPublic = Buffer.from(recipiantPublicKey, 'hex') - transactionSendCoin.amount = centAmount - transactionSendCoin.senderFinalBalance = senderStateBalance.amount - await queryRunner.manager.save(transactionSendCoin).catch((error) => { - throw new Error('error saving transaction send coin: ' + error) - }) + // TODO: WTF? + // I just assume that due to implicit type conversion the decimal places were cut. + // Using `Math.trunc` to simulate this behaviour + transaction.sendSenderFinalBalance = BigInt(Math.trunc(senderStateBalance.amount)) await queryRunner.manager.save(transaction).catch((error) => { throw new Error('error saving transaction with tx hash: ' + error) @@ -474,13 +408,13 @@ export class TransactionResolver { await sendTransactionReceivedEmail({ senderFirstName: senderUser.firstName, senderLastName: senderUser.lastName, - recipientFirstName: recipiantUser.firstName, - recipientLastName: recipiantUser.lastName, - email: recipiantUser.email, + recipientFirstName: recipientUser.firstName, + recipientLastName: recipientUser.lastName, + email: recipientUser.email, amount, memo, }) - return 'success' + return true } } diff --git a/backend/src/graphql/resolver/UserResolver.test.ts.disabled b/backend/src/graphql/resolver/UserResolver.test.ts similarity index 70% rename from backend/src/graphql/resolver/UserResolver.test.ts.disabled rename to backend/src/graphql/resolver/UserResolver.test.ts index 02e490edd..b01c99552 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts.disabled +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -6,14 +6,13 @@ import gql from 'graphql-tag' import { GraphQLError } from 'graphql' import createServer from '../../server/createServer' import { resetDB, initialize } from '@dbTools/helpers' -import { getRepository } from 'typeorm' -import { LoginUser } from '@entity/LoginUser' -import { LoginUserBackup } from '@entity/LoginUserBackup' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' import { User } from '@entity/User' import CONFIG from '../../config' import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail' -import { klicktippSignIn } from '../../apis/KlicktippController' +// import { klicktippSignIn } from '../../apis/KlicktippController' + +jest.setTimeout(10000) jest.mock('../../mailer/sendAccountActivationEmail', () => { return { @@ -22,12 +21,14 @@ jest.mock('../../mailer/sendAccountActivationEmail', () => { } }) +/* jest.mock('../../apis/KlicktippController', () => { return { __esModule: true, klicktippSignIn: jest.fn(), } }) +*/ let mutate: any let con: any @@ -40,6 +41,11 @@ beforeAll(async () => { await resetDB() }) +afterAll(async () => { + await resetDB(true) + await con.close() +}) + describe('UserResolver', () => { describe('createUser', () => { const variables = { @@ -84,70 +90,32 @@ describe('UserResolver', () => { }) describe('valid input data', () => { - let loginUser: LoginUser[] let user: User[] - let loginUserBackup: LoginUserBackup[] let loginEmailOptIn: LoginEmailOptIn[] beforeAll(async () => { - loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany() - user = await getRepository(User).createQueryBuilder('state_user').getMany() - loginUserBackup = await getRepository(LoginUserBackup) - .createQueryBuilder('login_user_backup') - .getMany() - loginEmailOptIn = await getRepository(LoginEmailOptIn) - .createQueryBuilder('login_email_optin') - .getMany() + user = await User.find() + loginEmailOptIn = await LoginEmailOptIn.find() emailOptIn = loginEmailOptIn[0].verificationCode.toString() }) describe('filling all tables', () => { it('saves the user in login_user table', () => { - expect(loginUser).toEqual([ + expect(user).toEqual([ { id: expect.any(Number), email: 'peter@lustig.de', firstName: 'Peter', lastName: 'Lustig', - username: '', - description: '', password: '0', pubKey: null, privKey: null, emailHash: expect.any(Buffer), createdAt: expect.any(Date), emailChecked: false, - passphraseShown: false, - language: 'de', - disabled: false, - groupId: 1, - publisherId: 1234, - }, - ]) - }) - - it('saves the user in state_user table', () => { - expect(user).toEqual([ - { - id: expect.any(Number), - indexId: 0, - groupId: 0, - pubkey: expect.any(Buffer), - email: 'peter@lustig.de', - firstName: 'Peter', - lastName: 'Lustig', - username: '', - disabled: false, - }, - ]) - }) - - it('saves the user in login_user_backup table', () => { - expect(loginUserBackup).toEqual([ - { - id: expect.any(Number), passphrase: expect.any(String), - userId: loginUser[0].id, - mnemonicType: 2, + language: 'de', + deletedAt: null, + publisherId: 1234, }, ]) }) @@ -156,7 +124,7 @@ describe('UserResolver', () => { expect(loginEmailOptIn).toEqual([ { id: expect.any(Number), - userId: loginUser[0].id, + userId: user[0].id, verificationCode: expect.any(String), emailOptInTypeId: 1, createdAt: expect.any(Date), @@ -196,9 +164,7 @@ describe('UserResolver', () => { mutation, variables: { ...variables, email: 'bibi@bloxberg.de', language: 'es' }, }) - await expect( - getRepository(LoginUser).createQueryBuilder('login_user').getMany(), - ).resolves.toEqual( + await expect(User.find()).resolves.toEqual( expect.arrayContaining([ expect.objectContaining({ email: 'bibi@bloxberg.de', @@ -215,9 +181,7 @@ describe('UserResolver', () => { mutation, variables: { ...variables, email: 'raeuber@hotzenplotz.de', publisherId: undefined }, }) - await expect( - getRepository(LoginUser).createQueryBuilder('login_user').getMany(), - ).resolves.toEqual( + await expect(User.find()).resolves.toEqual( expect.arrayContaining([ expect.objectContaining({ email: 'raeuber@hotzenplotz.de', @@ -265,23 +229,17 @@ describe('UserResolver', () => { let emailOptIn: string describe('valid optin code and valid password', () => { - let loginUser: any - let newLoginUser: any let newUser: any beforeAll(async () => { await mutate({ mutation: createUserMutation, variables: createUserVariables }) - const loginEmailOptIn = await getRepository(LoginEmailOptIn) - .createQueryBuilder('login_email_optin') - .getMany() - loginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany() + const loginEmailOptIn = await LoginEmailOptIn.find() emailOptIn = loginEmailOptIn[0].verificationCode.toString() result = await mutate({ mutation: setPasswordMutation, variables: { code: emailOptIn, password: 'Aa12345_' }, }) - newLoginUser = await getRepository(LoginUser).createQueryBuilder('login_user').getMany() - newUser = await getRepository(User).createQueryBuilder('state_user').getMany() + newUser = await User.find() }) afterAll(async () => { @@ -289,38 +247,27 @@ describe('UserResolver', () => { }) it('sets email checked to true', () => { - expect(newLoginUser[0].emailChecked).toBeTruthy() + expect(newUser[0].emailChecked).toBeTruthy() }) it('updates the password', () => { - expect(newLoginUser[0].password).toEqual('3917921995996627700') - }) - - it('updates the public Key on both user tables', () => { - expect(newLoginUser[0].pubKey).toEqual(expect.any(Buffer)) - expect(newLoginUser[0].pubKey).not.toEqual(loginUser[0].pubKey) - expect(newLoginUser[0].pubKey).toEqual(newUser[0].pubkey) - }) - - it('updates the private Key', () => { - expect(newLoginUser[0].privKey).toEqual(expect.any(Buffer)) - expect(newLoginUser[0].privKey).not.toEqual(loginUser[0].privKey) + expect(newUser[0].password).toEqual('3917921995996627700') }) it('removes the optin', async () => { - await expect( - getRepository(LoginEmailOptIn).createQueryBuilder('login_email_optin').getMany(), - ).resolves.toHaveLength(0) + await expect(LoginEmailOptIn.find()).resolves.toHaveLength(0) }) + /* it('calls the klicktipp API', () => { expect(klicktippSignIn).toBeCalledWith( - loginUser[0].email, - loginUser[0].language, - loginUser[0].firstName, - loginUser[0].lastName, + user[0].email, + user[0].language, + user[0].firstName, + user[0].lastName, ) }) + */ it('returns true', () => { expect(result).toBeTruthy() @@ -330,9 +277,7 @@ describe('UserResolver', () => { describe('no valid password', () => { beforeAll(async () => { await mutate({ mutation: createUserMutation, variables: createUserVariables }) - const loginEmailOptIn = await getRepository(LoginEmailOptIn) - .createQueryBuilder('login_email_optin') - .getMany() + const loginEmailOptIn = await LoginEmailOptIn.find() emailOptIn = loginEmailOptIn[0].verificationCode.toString() result = await mutate({ mutation: setPasswordMutation, @@ -380,8 +325,3 @@ describe('UserResolver', () => { }) }) }) - -afterAll(async () => { - await resetDB(true) - await con.close() -}) diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 5bc30f6b4..81bd9b1a8 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -3,7 +3,7 @@ import fs from 'fs' import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql' -import { getConnection, getCustomRepository, getRepository, QueryRunner } from '@dbTools/typeorm' +import { getConnection, getCustomRepository, QueryRunner } from '@dbTools/typeorm' import CONFIG from '../../config' import { User } from '../model/User' import { User as DbUser } from '@entity/User' @@ -152,8 +152,7 @@ const createEmailOptIn = async ( loginUserId: number, queryRunner: QueryRunner, ): Promise => { - const loginEmailOptInRepository = await getRepository(LoginEmailOptIn) - let emailOptIn = await loginEmailOptInRepository.findOne({ + let emailOptIn = await LoginEmailOptIn.findOne({ userId: loginUserId, emailOptInTypeId: EMAIL_OPT_IN_REGISTER, }) @@ -182,8 +181,7 @@ const createEmailOptIn = async ( } const getOptInCode = async (loginUserId: number): Promise => { - const loginEmailOptInRepository = await getRepository(LoginEmailOptIn) - let optInCode = await loginEmailOptInRepository.findOne({ + let optInCode = await LoginEmailOptIn.findOne({ userId: loginUserId, emailOptInTypeId: EMAIL_OPT_IN_RESET_PASSWORD, }) @@ -205,7 +203,7 @@ const getOptInCode = async (loginUserId: number): Promise => { optInCode.userId = loginUserId optInCode.emailOptInTypeId = EMAIL_OPT_IN_RESET_PASSWORD } - await loginEmailOptInRepository.save(optInCode) + await LoginEmailOptIn.save(optInCode) return optInCode } @@ -250,9 +248,12 @@ export class UserResolver { @Ctx() context: any, ): Promise { email = email.trim().toLowerCase() - const dbUser = await DbUser.findOneOrFail({ email }).catch(() => { + const dbUser = await DbUser.findOneOrFail({ email }, { withDeleted: true }).catch(() => { throw new Error('No user with this credentials') }) + if (dbUser.deletedAt) { + throw new Error('This user was permanently disabled. Contact support for questions.') + } if (!dbUser.emailChecked) { throw new Error('User email not validated') } @@ -335,9 +336,9 @@ export class UserResolver { // Validate email unique // TODO: i can register an email in upper/lower case twice - const userRepository = getCustomRepository(UserRepository) - const usersFound = await userRepository.count({ email }) - if (usersFound !== 0) { + // TODO we cannot use repository.count(), since it does not allow to specify if you want to include the soft deletes + const userFound = await DbUser.findOne({ email }, { withDeleted: true }) + if (userFound) { // TODO: this is unsecure, but the current implementation of the login server. This way it can be queried if the user with given EMail is existent. throw new Error(`User already exists.`) } @@ -487,12 +488,9 @@ export class UserResolver { } // Load code - const loginEmailOptInRepository = await getRepository(LoginEmailOptIn) - const optInCode = await loginEmailOptInRepository - .findOneOrFail({ verificationCode: code }) - .catch(() => { - throw new Error('Could not login with emailVerificationCode') - }) + const optInCode = await LoginEmailOptIn.findOneOrFail({ verificationCode: code }).catch(() => { + throw new Error('Could not login with emailVerificationCode') + }) // Code is only valid for 10minutes const timeElapsed = Date.now() - new Date(optInCode.updatedAt).getTime() diff --git a/backend/src/server/plugins.ts b/backend/src/server/plugins.ts index 049f63a08..a407135ea 100644 --- a/backend/src/server/plugins.ts +++ b/backend/src/server/plugins.ts @@ -4,40 +4,42 @@ import { ApolloLogPlugin, LogMutateData } from 'apollo-log' import cloneDeep from 'lodash.clonedeep' -const plugins = [ - { - requestDidStart() { - return { - willSendResponse(requestContext: any) { - const { setHeaders = [] } = requestContext.context - setHeaders.forEach(({ key, value }: { [key: string]: string }) => { - if (requestContext.response.http.headers.get(key)) { - requestContext.response.http.headers.set(key, value) - } else { - requestContext.response.http.headers.append(key, value) - } - }) - return requestContext - }, - } - }, +const setHeadersPlugin = { + requestDidStart() { + return { + willSendResponse(requestContext: any) { + const { setHeaders = [] } = requestContext.context + setHeaders.forEach(({ key, value }: { [key: string]: string }) => { + if (requestContext.response.http.headers.get(key)) { + requestContext.response.http.headers.set(key, value) + } else { + requestContext.response.http.headers.append(key, value) + } + }) + return requestContext + }, + } }, - ApolloLogPlugin({ - mutate: (data: LogMutateData) => { - // We need to deep clone the object in order to not modify the actual request - const dataCopy = cloneDeep(data) +} - // mask password if part of the query - if (dataCopy.context.request.variables && dataCopy.context.request.variables.password) { - dataCopy.context.request.variables.password = '***' - } +const apolloLogPlugin = ApolloLogPlugin({ + mutate: (data: LogMutateData) => { + // We need to deep clone the object in order to not modify the actual request + const dataCopy = cloneDeep(data) - // mask token at all times - dataCopy.context.context.token = '***' + // mask password if part of the query + if (dataCopy.context.request.variables && dataCopy.context.request.variables.password) { + dataCopy.context.request.variables.password = '***' + } - return dataCopy - }, - }), -] + // mask token at all times + dataCopy.context.context.token = '***' + + return dataCopy + }, +}) + +const plugins = + process.env.NODE_ENV === 'development' ? [setHeadersPlugin] : [setHeadersPlugin, apolloLogPlugin] export default plugins diff --git a/backend/src/typeorm/repository/Balance.ts b/backend/src/typeorm/repository/Balance.ts deleted file mode 100644 index 856329443..000000000 --- a/backend/src/typeorm/repository/Balance.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { EntityRepository, Repository } from '@dbTools/typeorm' -import { Balance } from '@entity/Balance' - -@EntityRepository(Balance) -export class BalanceRepository extends Repository { - findByUser(userId: number): Promise { - return this.createQueryBuilder('balance').where('balance.userId = :userId', { userId }).getOne() - } -} diff --git a/backend/src/typeorm/repository/Transaction.ts b/backend/src/typeorm/repository/Transaction.ts deleted file mode 100644 index 0a97067a2..000000000 --- a/backend/src/typeorm/repository/Transaction.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { EntityRepository, Repository } from '@dbTools/typeorm' -import { Transaction } from '@entity/Transaction' - -@EntityRepository(Transaction) -export class TransactionRepository extends Repository { - async joinFullTransactionsByIds(transactionIds: number[]): Promise { - return this.createQueryBuilder('transaction') - .where('transaction.id IN (:...transactions)', { transactions: transactionIds }) - .leftJoinAndSelect( - 'transaction.transactionSendCoin', - 'transactionSendCoin', - // 'transactionSendCoin.transaction_id = transaction.id', - ) - .leftJoinAndSelect( - 'transaction.transactionCreation', - 'transactionCreation', - // 'transactionSendCoin.transaction_id = transaction.id', - ) - .getMany() - } -} diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 59d6ff465..66f285978 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from '@dbTools/typeorm' +import { Brackets, EntityRepository, ObjectLiteral, Repository } from '@dbTools/typeorm' import { User } from '@entity/User' @EntityRepository(User) @@ -9,38 +9,39 @@ export class UserRepository extends Repository { .getOneOrFail() } - async findByPubkeyHexBuffer(pubkeyHexBuffer: Buffer): Promise { - const pubKeyString = pubkeyHexBuffer.toString('hex') - return await this.findByPubkeyHex(pubKeyString) - } - - async findByEmail(email: string): Promise { - return this.createQueryBuilder('user').where('user.email = :email', { email }).getOneOrFail() - } - async getUsersIndiced(userIds: number[]): Promise { - if (!userIds.length) return [] - const users = await this.createQueryBuilder('user') + return this.createQueryBuilder('user') + .withDeleted() // We need to show the name for deleted users for old transactions .select(['user.id', 'user.firstName', 'user.lastName', 'user.email']) - .where('user.id IN (:...users)', { users: userIds }) + .where('user.id IN (:...userIds)', { userIds }) .getMany() - const usersIndiced: User[] = [] - users.forEach((value) => { - usersIndiced[value.id] = value - }) - return usersIndiced } - async findBySearchCriteria(searchCriteria: string): Promise { + async findBySearchCriteriaPagedFiltered( + select: string[], + searchCriteria: string, + filterCriteria: ObjectLiteral[], + currentPage: number, + pageSize: number, + ): Promise<[User[], number]> { return await this.createQueryBuilder('user') + .select(select) + .withDeleted() .where( - 'user.firstName like :name or user.lastName like :lastName or user.email like :email', - { - name: `%${searchCriteria}%`, - lastName: `%${searchCriteria}%`, - email: `%${searchCriteria}%`, - }, + new Brackets((qb) => { + qb.where( + 'user.firstName like :name or user.lastName like :lastName or user.email like :email', + { + name: `%${searchCriteria}%`, + lastName: `%${searchCriteria}%`, + email: `%${searchCriteria}%`, + }, + ) + }), ) - .getMany() + .andWhere(filterCriteria) + .take(pageSize) + .skip((currentPage - 1) * pageSize) + .getManyAndCount() } } diff --git a/backend/test/testSetup.ts b/backend/test/testSetup.ts new file mode 100644 index 000000000..ed2c5cf49 --- /dev/null +++ b/backend/test/testSetup.ts @@ -0,0 +1,6 @@ +/* eslint-disable no-console */ + +// disable console.info for apollo log + +// eslint-disable-next-line @typescript-eslint/no-empty-function +console.info = () => {} diff --git a/database/entity/0001-init_db/Transaction.ts b/database/entity/0001-init_db/Transaction.ts index 3c2397108..a33fbd0be 100644 --- a/database/entity/0001-init_db/Transaction.ts +++ b/database/entity/0001-init_db/Transaction.ts @@ -1,6 +1,6 @@ import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm' -import { TransactionCreation } from '../TransactionCreation' -import { TransactionSendCoin } from '../TransactionSendCoin' +import { TransactionCreation } from './TransactionCreation' +import { TransactionSendCoin } from './TransactionSendCoin' @Entity('transactions') export class Transaction extends BaseEntity { diff --git a/database/entity/0016-transaction_signatures/Transaction.ts b/database/entity/0016-transaction_signatures/Transaction.ts index 8c8251baf..5410d010b 100644 --- a/database/entity/0016-transaction_signatures/Transaction.ts +++ b/database/entity/0016-transaction_signatures/Transaction.ts @@ -1,6 +1,6 @@ import { BaseEntity, Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm' -import { TransactionCreation } from '../TransactionCreation' -import { TransactionSendCoin } from '../TransactionSendCoin' +import { TransactionCreation } from '../0001-init_db/TransactionCreation' +import { TransactionSendCoin } from '../0001-init_db/TransactionSendCoin' @Entity('transactions') export class Transaction extends BaseEntity { diff --git a/database/entity/0024-combine_transaction_tables/Transaction.ts b/database/entity/0024-combine_transaction_tables/Transaction.ts new file mode 100644 index 000000000..5834c007a --- /dev/null +++ b/database/entity/0024-combine_transaction_tables/Transaction.ts @@ -0,0 +1,70 @@ +import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm' + +@Entity('transactions') +export class Transaction extends BaseEntity { + // TODO the id is defined as bigint(20) - there might be problems with that: https://github.com/typeorm/typeorm/issues/2400 + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ name: 'transaction_type_id', unsigned: true, nullable: false }) + transactionTypeId: number + + @Column({ name: 'user_id', unsigned: true, nullable: false }) + userId: number + + @Column({ type: 'bigint', nullable: false }) + amount: BigInt + + @Column({ name: 'tx_hash', type: 'binary', length: 48, default: null, nullable: true }) + txHash: Buffer + + @Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' }) + memo: string + + @Column({ type: 'timestamp', nullable: false, default: () => 'CURRENT_TIMESTAMP' }) + received: Date + + @Column({ type: 'binary', length: 64, nullable: true, default: null }) + signature: Buffer + + @Column({ type: 'binary', length: 32, nullable: true, default: null }) + pubkey: Buffer + + @Column({ + name: 'creation_ident_hash', + type: 'binary', + length: 32, + nullable: true, + default: null, + }) + creationIdentHash: Buffer + + @Column({ name: 'creation_date', type: 'timestamp', nullable: true, default: null }) + creationDate: Date + + @Column({ + name: 'send_receiver_public_key', + type: 'binary', + length: 32, + nullable: true, + default: null, + }) + sendReceiverPublicKey: Buffer | null + + @Column({ + name: 'send_receiver_user_id', + type: 'int', + unsigned: true, + nullable: true, + default: null, + }) + sendReceiverUserId?: number | null + + @Column({ + name: 'send_sender_final_balance', + type: 'bigint', + nullable: true, + default: null, + }) + sendSenderFinalBalance: BigInt | null +} diff --git a/database/entity/Transaction.ts b/database/entity/Transaction.ts index f17479f9f..5c9d2df88 100644 --- a/database/entity/Transaction.ts +++ b/database/entity/Transaction.ts @@ -1 +1 @@ -export { Transaction } from './0016-transaction_signatures/Transaction' +export { Transaction } from './0024-combine_transaction_tables/Transaction' diff --git a/database/entity/TransactionCreation.ts b/database/entity/TransactionCreation.ts deleted file mode 100644 index 100e948a1..000000000 --- a/database/entity/TransactionCreation.ts +++ /dev/null @@ -1 +0,0 @@ -export { TransactionCreation } from './0001-init_db/TransactionCreation' diff --git a/database/entity/TransactionSendCoin.ts b/database/entity/TransactionSendCoin.ts deleted file mode 100644 index 5c47d3961..000000000 --- a/database/entity/TransactionSendCoin.ts +++ /dev/null @@ -1 +0,0 @@ -export { TransactionSendCoin } from './0001-init_db/TransactionSendCoin' diff --git a/database/entity/index.ts b/database/entity/index.ts index 37fe6eb55..9371b5420 100644 --- a/database/entity/index.ts +++ b/database/entity/index.ts @@ -4,8 +4,6 @@ import { LoginEmailOptIn } from './LoginEmailOptIn' import { Migration } from './Migration' import { ServerUser } from './ServerUser' import { Transaction } from './Transaction' -import { TransactionCreation } from './TransactionCreation' -import { TransactionSendCoin } from './TransactionSendCoin' import { User } from './User' import { UserSetting } from './UserSetting' import { UserTransaction } from './UserTransaction' @@ -19,8 +17,6 @@ export const entities = [ Migration, ServerUser, Transaction, - TransactionCreation, - TransactionSendCoin, User, UserSetting, UserTransaction, diff --git a/database/migrations/0012-login_user_backups_unify_wordlist.ts b/database/migrations/0012-login_user_backups_unify_wordlist.ts index a59a8da7f..0c04693ec 100644 --- a/database/migrations/0012-login_user_backups_unify_wordlist.ts +++ b/database/migrations/0012-login_user_backups_unify_wordlist.ts @@ -5,19 +5,20 @@ * This also removes the trailing space */ import fs from 'fs' +import path from 'path' const TARGET_MNEMONIC_TYPE = 2 const PHRASE_WORD_COUNT = 24 const WORDS_MNEMONIC_0 = fs - .readFileSync('src/config/mnemonic.uncompressed_buffer18112.txt') + .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18112.txt')) .toString() .split(',') const WORDS_MNEMONIC_1 = fs - .readFileSync('src/config/mnemonic.uncompressed_buffer18113.txt') + .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18113.txt')) .toString() .split(',') const WORDS_MNEMONIC_2 = fs - .readFileSync('src/config/mnemonic.uncompressed_buffer13116.txt') + .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer13116.txt')) .toString() .split(',') const WORDS_MNEMONIC = [WORDS_MNEMONIC_0, WORDS_MNEMONIC_1, WORDS_MNEMONIC_2] diff --git a/database/migrations/0024-combine_transaction_tables.ts b/database/migrations/0024-combine_transaction_tables.ts new file mode 100644 index 000000000..af564ce2d --- /dev/null +++ b/database/migrations/0024-combine_transaction_tables.ts @@ -0,0 +1,125 @@ +/* MIGRATION TO COMBINE ALL TRANSACTION TABLES + * + * Combine all transaction tables into one table with all data + */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + // Create new `user_id` column (former `state_user_id`), with a temporary default of null + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `user_id` int(10) unsigned DEFAULT NULL AFTER `transaction_type_id`;', + ) + // Create new `amount` column, with a temporary default of null + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `amount` bigint(20) DEFAULT NULL AFTER `user_id`;', + ) + // Create new `creation_ident_hash` column (former `ident_hash`) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `creation_ident_hash` binary(32) DEFAULT NULL AFTER `pubkey`;', + ) + // Create new `creation_date` column (former `target_date`) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `creation_date` timestamp NULL DEFAULT NULL AFTER `creation_ident_hash`;', + ) + // Create new `send_receiver_public_key` column (former `receiver_public_key`) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `send_receiver_public_key` binary(32) DEFAULT NULL AFTER `creation_date`;', + ) + // Create new `send_receiver_user_id` column (former `receiver_user_id`) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `send_receiver_user_id` int(10) unsigned DEFAULT NULL AFTER `send_receiver_public_key`;', + ) + // Create new `send_sender_final_balance` column (former `sender_final_balance`) + await queryFn( + 'ALTER TABLE `transactions` ADD COLUMN `send_sender_final_balance` bigint(20) DEFAULT NULL AFTER `send_receiver_user_id`;', + ) + + // Insert Data from `transaction_creations` + await queryFn(` + UPDATE transactions + INNER JOIN transaction_creations ON transaction_creations.transaction_id = transactions.id + SET transactions.user_id = transaction_creations.state_user_id, + transactions.amount = transaction_creations.amount, + transactions.creation_ident_hash = transaction_creations.ident_hash, + transactions.creation_date = transaction_creations.target_date; + `) + + // Insert Data from `transaction_send_coins` + // Note: we drop `sender_public_key` in favor of `pubkey` from the original `transactions` table + // the data from `transaction_send_coins` seems incomplete for half the dataset (zeroed pubkey) + // with one key being different. + await queryFn(` + UPDATE transactions + INNER JOIN transaction_send_coins ON transaction_send_coins.transaction_id = transactions.id + SET transactions.user_id = transaction_send_coins.state_user_id, + transactions.amount = transaction_send_coins.amount, + transactions.send_receiver_public_key = transaction_send_coins.receiver_public_key, + transactions.send_receiver_user_id = transaction_send_coins.receiver_user_id, + transactions.send_sender_final_balance = transaction_send_coins.sender_final_balance; + `) + + // Modify defaults after our inserts + await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `user_id` int(10) unsigned NOT NULL;') + await queryFn('ALTER TABLE `transactions` MODIFY COLUMN `amount` bigint(20) NOT NULL;') + + // Drop table `transaction_creations` + await queryFn('DROP TABLE `transaction_creations`;') + // Drop table `transaction_send_coins` + await queryFn('DROP TABLE `transaction_send_coins`;') +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn(` + CREATE TABLE \`transaction_send_coins\` ( + \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, + \`transaction_id\` int(10) unsigned NOT NULL, + \`sender_public_key\` binary(32) NOT NULL, + \`state_user_id\` int(10) unsigned DEFAULT 0, + \`receiver_public_key\` binary(32) NOT NULL, + \`receiver_user_id\` int(10) unsigned DEFAULT 0, + \`amount\` bigint(20) NOT NULL, + \`sender_final_balance\` bigint(20) NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB AUTO_INCREMENT=659 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `) + await queryFn(` + CREATE TABLE \`transaction_creations\` ( + \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, + \`transaction_id\` int(10) unsigned NOT NULL, + \`state_user_id\` int(10) unsigned NOT NULL, + \`amount\` bigint(20) NOT NULL, + \`ident_hash\` binary(32) DEFAULT NULL, + \`target_date\` timestamp NULL DEFAULT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB AUTO_INCREMENT=2769 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `) + + await queryFn(` + INSERT INTO transaction_send_coins + ( transaction_id, sender_public_key, state_user_id, + receiver_public_key, receiver_user_id, + amount, sender_final_balance ) + ( SELECT id AS transaction_id, IF(pubkey, pubkey, 0x00000000000000000000000000000000) AS sender_public_key, user_id AS state_user_id, + send_receiver_public_key AS receiver_public_key, send_receiver_user_id AS receiver_user_id, + amount, send_sender_final_balance AS sender_final_balance + FROM transactions + WHERE transaction_type_id = 2 ); + `) + + await queryFn(` + INSERT INTO transaction_creations + ( transaction_id, state_user_id, + amount, ident_hash, target_date ) + ( SELECT id AS transaction_id, user_id AS state_user_id, + amount, creation_ident_hash AS ident_hash, creation_date AS target_date + FROM transactions + WHERE transaction_type_id = 1 ); + `) + + await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_sender_final_balance`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_receiver_user_id`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `send_receiver_public_key`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `creation_date`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `creation_ident_hash`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `amount`;') + await queryFn('ALTER TABLE `transactions` DROP COLUMN `user_id`;') +} diff --git a/database/package.json b/database/package.json index 964bb07c7..d3bda1bc4 100644 --- a/database/package.json +++ b/database/package.json @@ -8,7 +8,7 @@ "license": "MIT", "private": false, "scripts": { - "build": "tsc --build", + "build": "mkdir -p build/src/config/ && cp src/config/*.txt build/src/config/ && tsc --build", "clean": "tsc --build --clean", "up": "node build/src/index.js up", "down": "node build/src/index.js down", diff --git a/database/src/factories/transaction-creation.factory.ts b/database/src/factories/transaction-creation.factory.ts deleted file mode 100644 index ec0b9e8a6..000000000 --- a/database/src/factories/transaction-creation.factory.ts +++ /dev/null @@ -1,18 +0,0 @@ -import Faker from 'faker' -import { define } from 'typeorm-seeding' -import { TransactionCreation } from '../../entity/TransactionCreation' -import { TransactionCreationContext } from '../interface/TransactionContext' - -define(TransactionCreation, (faker: typeof Faker, context?: TransactionCreationContext) => { - if (!context || !context.userId || !context.transaction) { - throw new Error('TransactionCreation: No userId and/or transaction present!') - } - - const transactionCreation = new TransactionCreation() - transactionCreation.userId = context.userId - transactionCreation.amount = context.amount ? context.amount : 100000 - transactionCreation.targetDate = context.targetDate ? context.targetDate : new Date() - transactionCreation.transaction = context.transaction - - return transactionCreation -}) diff --git a/database/src/factories/transaction.factory.ts b/database/src/factories/transaction.factory.ts index 10d242265..2637ae298 100644 --- a/database/src/factories/transaction.factory.ts +++ b/database/src/factories/transaction.factory.ts @@ -5,17 +5,24 @@ import { TransactionContext } from '../interface/TransactionContext' import { randomBytes } from 'crypto' define(Transaction, (faker: typeof Faker, context?: TransactionContext) => { - if (!context) context = {} + if (!context) { + throw new Error('TransactionContext not well defined.') + } const transaction = new Transaction() - transaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 2 - transaction.txHash = context.txHash ? context.txHash : randomBytes(48) - transaction.memo = context.memo || context.memo === '' ? context.memo : faker.lorem.sentence() - transaction.received = context.received ? context.received : new Date() - transaction.signature = context.signature ? context.signature : randomBytes(64) - transaction.pubkey = context.signaturePubkey ? context.signaturePubkey : randomBytes(32) - if (context.transactionSendCoin) transaction.transactionSendCoin = context.transactionSendCoin - if (context.transactionCreation) transaction.transactionCreation = context.transactionCreation + transaction.transactionTypeId = context.transactionTypeId // || 2 + transaction.userId = context.userId + transaction.amount = context.amount + transaction.txHash = context.txHash || randomBytes(48) + transaction.memo = context.memo + transaction.received = context.received || new Date() + transaction.signature = context.signature || randomBytes(64) + transaction.pubkey = context.pubkey || randomBytes(32) + transaction.creationIdentHash = context.creationIdentHash || randomBytes(32) + transaction.creationDate = context.creationDate || new Date() + transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null + transaction.sendReceiverUserId = context.sendReceiverUserId || null + transaction.sendSenderFinalBalance = context.sendSenderFinalBalance || null return transaction }) diff --git a/database/src/factories/user.factory.ts b/database/src/factories/user.factory.ts index cb2ccb105..d04dcea4c 100644 --- a/database/src/factories/user.factory.ts +++ b/database/src/factories/user.factory.ts @@ -1,7 +1,7 @@ import Faker from 'faker' import { define } from 'typeorm-seeding' import { User } from '../../entity/User' -import { randomBytes, randomInt } from 'crypto' +import { randomBytes } from 'crypto' import { UserContext } from '../interface/UserContext' define(User, (faker: typeof Faker, context?: UserContext) => { diff --git a/database/src/interface/TransactionContext.ts b/database/src/interface/TransactionContext.ts index 15b66bad6..add9fc71c 100644 --- a/database/src/interface/TransactionContext.ts +++ b/database/src/interface/TransactionContext.ts @@ -1,18 +1,20 @@ import { Transaction } from '../../entity/Transaction' -import { TransactionSendCoin } from '../../entity/TransactionSendCoin' -import { TransactionCreation } from '../../entity/TransactionCreation' import { User } from '../../entity/User' export interface TransactionContext { - transactionTypeId?: number + transactionTypeId: number + userId: number + amount: BigInt txHash?: Buffer - memo?: string + memo: string received?: Date - blockchainTypeId?: number signature?: Buffer - signaturePubkey?: Buffer - transactionSendCoin?: TransactionSendCoin - transactionCreation?: TransactionCreation + pubkey?: Buffer + creationIdentHash?: Buffer + creationDate?: Date + sendReceiverPublicKey?: Buffer + sendReceiverUserId?: number + sendSenderFinalBalance?: BigInt } export interface BalanceContext { @@ -32,13 +34,6 @@ export interface TransactionSendCoinContext { transaction?: Transaction } -export interface TransactionCreationContext { - userId?: number - amount?: number - targetDate?: Date - transaction?: Transaction -} - export interface UserTransactionContext { userId?: number transactionId?: number diff --git a/database/src/interface/UserInterface.ts b/database/src/interface/UserInterface.ts index 30b05db38..1f258f808 100644 --- a/database/src/interface/UserInterface.ts +++ b/database/src/interface/UserInterface.ts @@ -11,7 +11,6 @@ export interface UserInterface { emailChecked?: boolean language?: string deletedAt?: Date - groupId?: number publisherId?: number passphrase?: string // from server user @@ -27,9 +26,8 @@ export interface UserInterface { // balance balanceModified?: Date recordDate?: Date - targetDate?: Date + creationDate?: Date amount?: number creationTxHash?: Buffer signature?: Buffer - signaturePubkey?: Buffer } diff --git a/database/src/seeds/helpers/user-helpers.ts b/database/src/seeds/helpers/user-helpers.ts index 74bdd4326..a195fc181 100644 --- a/database/src/seeds/helpers/user-helpers.ts +++ b/database/src/seeds/helpers/user-helpers.ts @@ -2,7 +2,6 @@ import { UserContext, ServerUserContext } from '../../interface/UserContext' import { BalanceContext, TransactionContext, - TransactionCreationContext, UserTransactionContext, } from '../../interface/TransactionContext' import { UserInterface } from '../../interface/UserInterface' @@ -11,7 +10,6 @@ import { ServerUser } from '../../../entity/ServerUser' import { Balance } from '../../../entity/Balance' import { Transaction } from '../../../entity/Transaction' import { UserTransaction } from '../../../entity/UserTransaction' -import { TransactionCreation } from '../../../entity/TransactionCreation' import { Factory } from 'typeorm-seeding' export const userSeeder = async (factory: Factory, userData: UserInterface): Promise => { @@ -25,10 +23,7 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro // create some GDD for the user await factory(Balance)(createBalanceContext(userData, user)).create() const transaction = await factory(Transaction)( - createTransactionContext(userData, 1, 'Herzlich Willkommen bei Gradido!'), - ).create() - await factory(TransactionCreation)( - createTransactionCreationContext(userData, user, transaction), + createTransactionContext(userData, user, 1, 'Herzlich Willkommen bei Gradido!'), ).create() await factory(UserTransaction)( createUserTransactionContext(userData, user, transaction), @@ -76,27 +71,18 @@ const createBalanceContext = (context: UserInterface, user: User): BalanceContex const createTransactionContext = ( context: UserInterface, + user: User, type: number, memo: string, ): TransactionContext => { return { transactionTypeId: type, + userId: user.id, + amount: BigInt(context.amount || 100000), txHash: context.creationTxHash, memo, received: context.recordDate, - } -} - -const createTransactionCreationContext = ( - context: UserInterface, - user: User, - transaction: Transaction, -): TransactionCreationContext => { - return { - userId: user.id, - amount: context.amount, - targetDate: context.targetDate, - transaction, + creationDate: context.creationDate, } } @@ -112,6 +98,6 @@ const createUserTransactionContext = ( balance: context.amount, balanceDate: context.recordDate, signature: context.signature, - pubkey: context.signaturePubkey, + pubkey: context.pubKey, } } diff --git a/database/src/seeds/users/bibi-bloxberg.ts b/database/src/seeds/users/bibi-bloxberg.ts index e8291b213..f940c3447 100644 --- a/database/src/seeds/users/bibi-bloxberg.ts +++ b/database/src/seeds/users/bibi-bloxberg.ts @@ -1,8 +1,9 @@ -export const bibiBloxberg = { +import { UserInterface } from '../../interface/UserInterface' + +export const bibiBloxberg: UserInterface = { email: 'bibi@bloxberg.de', firstName: 'Bibi', lastName: 'Bloxberg', - username: 'bibi', // description: 'Hex Hex', password: BigInt('12825419584724616625'), pubKey: Buffer.from('42de7e4754625b730018c3b4ea745a4d043d9d867af352d0f08871793dfa6743', 'hex'), @@ -14,16 +15,13 @@ export const bibiBloxberg = { createdAt: new Date('2021-11-26T11:32:16'), emailChecked: true, language: 'de', - disabled: false, - groupId: 1, passphrase: 'knife normal level all hurdle crucial color avoid warrior stadium road bachelor affair topple hawk pottery right afford immune two ceiling budget glance hour ', - mnemonicType: 2, isAdmin: false, addBalance: true, balanceModified: new Date('2021-11-30T10:37:11'), recordDate: new Date('2021-11-30T10:37:11'), - targetDate: new Date('2021-08-01 00:00:00'), + creationDate: new Date('2021-08-01 00:00:00'), amount: 10000000, creationTxHash: Buffer.from( '51103dc0fc2ca5d5d75a9557a1e899304e5406cfdb1328d8df6414d527b0118100000000000000000000000000000000', @@ -33,8 +31,4 @@ export const bibiBloxberg = { '2a2c71f3e41adc060bbc3086577e2d57d24eeeb0a7727339c3f85aad813808f601d7e1df56a26e0929d2e67fc054fca429ccfa283ed2782185c7f009fe008f0c', 'hex', ), - signaturePubkey: Buffer.from( - '7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', - 'hex', - ), } diff --git a/database/src/seeds/users/bob-baumeister.ts b/database/src/seeds/users/bob-baumeister.ts index 1c9f992e6..34597fa3f 100644 --- a/database/src/seeds/users/bob-baumeister.ts +++ b/database/src/seeds/users/bob-baumeister.ts @@ -1,8 +1,9 @@ -export const bobBaumeister = { +import { UserInterface } from '../../interface/UserInterface' + +export const bobBaumeister: UserInterface = { email: 'bob@baumeister.de', firstName: 'Bob', lastName: 'der Baumeister', - username: 'bob', // description: 'Können wir das schaffen? Ja, wir schaffen das!', password: BigInt('3296644341468822636'), pubKey: Buffer.from('a509d9a146374fc975e3677db801ae8a4a83bff9dea96da64053ff6de6b2dd7e', 'hex'), @@ -14,16 +15,13 @@ export const bobBaumeister = { createdAt: new Date('2021-11-26T11:36:31'), emailChecked: true, language: 'de', - disabled: false, - groupId: 1, passphrase: 'detail master source effort unable waste tilt flush domain orchard art truck hint barrel response gate impose peanut secret merry three uncle wink resource ', - mnemonicType: 2, isAdmin: false, addBalance: true, balanceModified: new Date('2021-11-30T10:37:14'), recordDate: new Date('2021-11-30T10:37:14'), - targetDate: new Date('2021-08-01 00:00:00'), + creationDate: new Date('2021-08-01 00:00:00'), amount: 10000000, creationTxHash: Buffer.from( 'be095dc87acb94987e71168fee8ecbf50ecb43a180b1006e75d573b35725c69c00000000000000000000000000000000', @@ -33,8 +31,4 @@ export const bobBaumeister = { '1fbd6b9a3d359923b2501557f3bc79fa7e428127c8090fb16bc490b4d87870ab142b3817ddd902d22f0b26472a483233784a0e460c0622661752a13978903905', 'hex', ), - signaturePubkey: Buffer.from( - '7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', - 'hex', - ), } diff --git a/database/src/seeds/users/garrick-ollivander.ts b/database/src/seeds/users/garrick-ollivander.ts index bddd5aa0f..3f18a875c 100644 --- a/database/src/seeds/users/garrick-ollivander.ts +++ b/database/src/seeds/users/garrick-ollivander.ts @@ -1,8 +1,9 @@ -export const garrickOllivander = { +import { UserInterface } from '../../interface/UserInterface' + +export const garrickOllivander: UserInterface = { email: 'garrick@ollivander.com', firstName: 'Garrick', lastName: 'Ollivander', - username: 'garrick', // description: `Curious ... curious ... // Renowned wandmaker Mr Ollivander owns the wand shop Ollivanders: Makers of Fine Wands Since 382 BC in Diagon Alley. His shop is widely considered the best place to purchase a wand.`, password: BigInt('0'), @@ -10,11 +11,8 @@ export const garrickOllivander = { createdAt: new Date('2022-01-10T10:23:17'), emailChecked: false, language: 'en', - disabled: false, - groupId: 1, passphrase: 'human glide theory clump wish history other duty door fringe neck industry ostrich equal plate diesel tornado neck people antenna door category moon hen ', - mnemonicType: 2, isAdmin: false, addBalance: false, } diff --git a/database/src/seeds/users/peter-lustig.ts b/database/src/seeds/users/peter-lustig.ts index da6c31777..5b4b98488 100644 --- a/database/src/seeds/users/peter-lustig.ts +++ b/database/src/seeds/users/peter-lustig.ts @@ -1,8 +1,9 @@ -export const peterLustig = { +import { UserInterface } from '../../interface/UserInterface' + +export const peterLustig: UserInterface = { email: 'peter@lustig.de', firstName: 'Peter', lastName: 'Lustig', - username: 'peter', // description: 'Latzhose und Nickelbrille', password: BigInt('3917921995996627700'), pubKey: Buffer.from('7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', 'hex'), @@ -14,11 +15,8 @@ export const peterLustig = { createdAt: new Date('2020-11-25T10:48:43'), emailChecked: true, language: 'de', - disabled: false, - groupId: 1, passphrase: 'okay property choice naive calm present weird increase stuff royal vibrant frame attend wood one else tribe pull hedgehog woman kitchen hawk snack smart ', - mnemonicType: 2, role: 'admin', serverUserPassword: '$2y$10$TzIWLeZoKs251gwrhSQmHeKhKI/EQ4EV5ClfAT8Ufnb4lcUXPa5X.', activated: 1, diff --git a/database/src/seeds/users/raeuber-hotzenplotz.ts b/database/src/seeds/users/raeuber-hotzenplotz.ts index aaf862d14..bcd5f995b 100644 --- a/database/src/seeds/users/raeuber-hotzenplotz.ts +++ b/database/src/seeds/users/raeuber-hotzenplotz.ts @@ -1,8 +1,9 @@ -export const raeuberHotzenplotz = { +import { UserInterface } from '../../interface/UserInterface' + +export const raeuberHotzenplotz: UserInterface = { email: 'raeuber@hotzenplotz.de', firstName: 'Räuber', lastName: 'Hotzenplotz', - username: 'räuber', // description: 'Pfefferpistole', password: BigInt('12123692783243004812'), pubKey: Buffer.from('d7c70f94234dff071d982aa8f41583876c356599773b5911b39080da2b8c2d2b', 'hex'), @@ -14,16 +15,13 @@ export const raeuberHotzenplotz = { createdAt: new Date('2021-11-26T11:32:16'), emailChecked: true, language: 'de', - disabled: false, - groupId: 1, passphrase: 'gospel trip tenant mouse spider skill auto curious man video chief response same little over expire drum display fancy clinic keen throw urge basket ', - mnemonicType: 2, isAdmin: false, addBalance: true, balanceModified: new Date('2021-11-30T10:37:13'), recordDate: new Date('2021-11-30T10:37:13'), - targetDate: new Date('2021-08-01 00:00:00'), + creationDate: new Date('2021-08-01 00:00:00'), amount: 10000000, creationTxHash: Buffer.from( '23ba44fd84deb59b9f32969ad0cb18bfa4588be1bdb99c396888506474c16c1900000000000000000000000000000000', @@ -33,8 +31,4 @@ export const raeuberHotzenplotz = { '756d3da061687c575d1dbc5073908f646aa5f498b0927b217c83b48af471450e571dfe8421fb8e1f1ebd1104526b7e7c6fa78684e2da59c8f7f5a8dc3d9e5b0b', 'hex', ), - signaturePubkey: Buffer.from( - '7281e0ee3258b08801f3ec73e431b4519677f65c03b0382c63a913b5784ee770', - 'hex', - ), } diff --git a/frontend/src/components/DecayInformation.vue b/frontend/src/components/DecayInformation.vue index 28af9b2b4..0daa45a18 100644 --- a/frontend/src/components/DecayInformation.vue +++ b/frontend/src/components/DecayInformation.vue @@ -1,7 +1,7 @@