mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into test-login-user-resolver
This commit is contained in:
commit
2030da653e
30
CHANGELOG.md
30
CHANGELOG.md
@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file. Dates are d
|
|||||||
|
|
||||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.6.6](https://github.com/gradido/gradido/compare/1.6.5...1.6.6)
|
||||||
|
|
||||||
|
- Fix: Upper case email on register breaks account [`#1542`](https://github.com/gradido/gradido/pull/1542)
|
||||||
|
- 1106 first transaction cannot be expanded [`#1432`](https://github.com/gradido/gradido/pull/1432)
|
||||||
|
- added missing bootstrap scss. bootstrap/scss/bootstrap, plus more mis… [`#1540`](https://github.com/gradido/gradido/pull/1540)
|
||||||
|
- feat: Seed Deleted User [`#1533`](https://github.com/gradido/gradido/pull/1533)
|
||||||
|
- fix: No Creations for Deleted Users [`#1534`](https://github.com/gradido/gradido/pull/1534)
|
||||||
|
- fix: Wrong Key Name for Recover User [`#1535`](https://github.com/gradido/gradido/pull/1535)
|
||||||
|
- [Feature] : user deleted and undeleted functions for adminarea [`#1520`](https://github.com/gradido/gradido/pull/1520)
|
||||||
|
- fix: Possible SQL Exception in User Search [`#1530`](https://github.com/gradido/gradido/pull/1530)
|
||||||
|
- Feature: Make lint warnings unwanted [`#1529`](https://github.com/gradido/gradido/pull/1529)
|
||||||
|
- 1459 list data again on confirm creation [`#1467`](https://github.com/gradido/gradido/pull/1467)
|
||||||
|
- fix: Return Empty Array When No Pending Creations Are Present [`#1526`](https://github.com/gradido/gradido/pull/1526)
|
||||||
|
- Fix: Correct path of index.js in production [`#1525`](https://github.com/gradido/gradido/pull/1525)
|
||||||
|
- refactor: Get Open Creations by One Query [`#1524`](https://github.com/gradido/gradido/pull/1524)
|
||||||
|
- Admin: Langsame Benutzer-Suche [`#1472`](https://github.com/gradido/gradido/pull/1472)
|
||||||
|
- fix: Backend Unit Tests Running Again [`#1513`](https://github.com/gradido/gradido/pull/1513)
|
||||||
|
- Refactor: Combine transaction tables [`#1523`](https://github.com/gradido/gradido/pull/1523)
|
||||||
|
- Refactor: User resolver [`#1522`](https://github.com/gradido/gradido/pull/1522)
|
||||||
|
- feature: Soft-Delete for users (backend) [`#1521`](https://github.com/gradido/gradido/pull/1521)
|
||||||
|
- feature: Soft-Delete for users (database only) [`#1516`](https://github.com/gradido/gradido/pull/1516)
|
||||||
|
- refactor: Improve Decay Display [`#1517`](https://github.com/gradido/gradido/pull/1517)
|
||||||
|
- 404 page needs back to login button [`#1515`](https://github.com/gradido/gradido/pull/1515)
|
||||||
|
- feature: show current version in admin footer [`#1514`](https://github.com/gradido/gradido/pull/1514)
|
||||||
|
- fix: Never Sent Email Text [`#1512`](https://github.com/gradido/gradido/pull/1512)
|
||||||
|
- refactor: static decay block [`#1405`](https://github.com/gradido/gradido/pull/1405)
|
||||||
|
- refactor: Use Bootstrap Vue Toast [`#1499`](https://github.com/gradido/gradido/pull/1499)
|
||||||
|
- fix: Catch GDT Server Errors [`#1479`](https://github.com/gradido/gradido/pull/1479)
|
||||||
|
- Fix: Autochangelog - no commits [`#1498`](https://github.com/gradido/gradido/pull/1498)
|
||||||
|
|
||||||
#### [1.6.5](https://github.com/gradido/gradido/compare/1.6.4...1.6.5)
|
#### [1.6.5](https://github.com/gradido/gradido/compare/1.6.4...1.6.5)
|
||||||
|
|
||||||
> 15 February 2022
|
> 15 February 2022
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import dotenv from 'dotenv'
|
|||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
const constants = {
|
const constants = {
|
||||||
DB_VERSION: '0024-combine_transaction_tables',
|
DB_VERSION: '0027-clean_transaction_table',
|
||||||
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
|
DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { registerEnumType } from 'type-graphql'
|
|||||||
export enum TransactionTypeId {
|
export enum TransactionTypeId {
|
||||||
CREATION = 1,
|
CREATION = 1,
|
||||||
SEND = 2,
|
SEND = 2,
|
||||||
|
RECEIVE = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
registerEnumType(TransactionTypeId, {
|
registerEnumType(TransactionTypeId, {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export class Transaction {
|
|||||||
this.balance = 0
|
this.balance = 0
|
||||||
this.totalBalance = 0
|
this.totalBalance = 0
|
||||||
this.memo = ''
|
this.memo = ''
|
||||||
|
this.firstTransaction = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
@ -52,4 +53,7 @@ export class Transaction {
|
|||||||
|
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
decay?: Decay
|
decay?: Decay
|
||||||
|
|
||||||
|
@Field(() => Boolean)
|
||||||
|
firstTransaction: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,8 +20,7 @@ import CreatePendingCreationArgs from '../arg/CreatePendingCreationArgs'
|
|||||||
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
import UpdatePendingCreationArgs from '../arg/UpdatePendingCreationArgs'
|
||||||
import SearchUsersArgs from '../arg/SearchUsersArgs'
|
import SearchUsersArgs from '../arg/SearchUsersArgs'
|
||||||
import { Transaction } from '@entity/Transaction'
|
import { Transaction } from '@entity/Transaction'
|
||||||
import { UserTransaction } from '@entity/UserTransaction'
|
import { TransactionRepository } from '../../typeorm/repository/Transaction'
|
||||||
import { UserTransactionRepository } from '../../typeorm/repository/UserTransaction'
|
|
||||||
import { calculateDecay } from '../../util/decay'
|
import { calculateDecay } from '../../util/decay'
|
||||||
import { AdminPendingCreation } from '@entity/AdminPendingCreation'
|
import { AdminPendingCreation } from '@entity/AdminPendingCreation'
|
||||||
import { hasElopageBuys } from '../../util/hasElopageBuys'
|
import { hasElopageBuys } from '../../util/hasElopageBuys'
|
||||||
@ -306,42 +305,29 @@ export class AdminResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const receivedCallDate = new Date()
|
const receivedCallDate = new Date()
|
||||||
let transaction = new Transaction()
|
|
||||||
transaction.transactionTypeId = TransactionTypeId.CREATION
|
|
||||||
transaction.memo = pendingCreation.memo
|
|
||||||
transaction.received = receivedCallDate
|
|
||||||
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')
|
|
||||||
|
|
||||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
const lastUserTransaction = await userTransactionRepository.findLastForUser(
|
const lastUserTransaction = await transactionRepository.findLastForUser(pendingCreation.userId)
|
||||||
pendingCreation.userId,
|
|
||||||
)
|
|
||||||
let newBalance = 0
|
let newBalance = 0
|
||||||
if (!lastUserTransaction) {
|
if (lastUserTransaction) {
|
||||||
newBalance = 0
|
|
||||||
} else {
|
|
||||||
newBalance = calculateDecay(
|
newBalance = calculateDecay(
|
||||||
lastUserTransaction.balance,
|
Number(lastUserTransaction.balance),
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
receivedCallDate,
|
receivedCallDate,
|
||||||
).balance
|
).balance
|
||||||
}
|
}
|
||||||
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
newBalance = Number(newBalance) + Number(parseInt(pendingCreation.amount.toString()))
|
||||||
|
|
||||||
const newUserTransaction = new UserTransaction()
|
const transaction = new Transaction()
|
||||||
newUserTransaction.userId = pendingCreation.userId
|
transaction.typeId = TransactionTypeId.CREATION
|
||||||
newUserTransaction.transactionId = transaction.id
|
transaction.memo = pendingCreation.memo
|
||||||
newUserTransaction.transactionTypeId = transaction.transactionTypeId
|
transaction.userId = pendingCreation.userId
|
||||||
newUserTransaction.balance = Number(newBalance)
|
transaction.amount = BigInt(parseInt(pendingCreation.amount.toString()))
|
||||||
newUserTransaction.balanceDate = transaction.received
|
transaction.creationDate = pendingCreation.date
|
||||||
|
transaction.balance = BigInt(newBalance)
|
||||||
await userTransactionRepository.save(newUserTransaction).catch((error) => {
|
transaction.balanceDate = receivedCallDate
|
||||||
throw new Error('Error saving user transaction: ' + error)
|
await transaction.save()
|
||||||
})
|
|
||||||
|
|
||||||
let userBalance = await Balance.findOne({ userId: pendingCreation.userId })
|
let userBalance = await Balance.findOne({ userId: pendingCreation.userId })
|
||||||
if (!userBalance) {
|
if (!userBalance) {
|
||||||
@ -388,7 +374,7 @@ async function getUserCreations(ids: number[], includePending = true): Promise<C
|
|||||||
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
|
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
|
||||||
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
|
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
|
||||||
WHERE user_id IN (${ids.toString()})
|
WHERE user_id IN (${ids.toString()})
|
||||||
AND transaction_type_id = ${TransactionTypeId.CREATION}
|
AND type_id = ${TransactionTypeId.CREATION}
|
||||||
AND creation_date >= ${dateFilter}
|
AND creation_date >= ${dateFilter}
|
||||||
${unionString}) AS result
|
${unionString}) AS result
|
||||||
GROUP BY month, userId
|
GROUP BY month, userId
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
|
||||||
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
|
||||||
import { getCustomRepository, getConnection, QueryRunner, In } from '@dbTools/typeorm'
|
import { getCustomRepository, getConnection, QueryRunner } from '@dbTools/typeorm'
|
||||||
|
|
||||||
import CONFIG from '../../config'
|
import CONFIG from '../../config'
|
||||||
import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
|
import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
|
||||||
@ -18,10 +18,9 @@ import Paginated from '../arg/Paginated'
|
|||||||
import { Order } from '../enum/Order'
|
import { Order } from '../enum/Order'
|
||||||
|
|
||||||
import { UserRepository } from '../../typeorm/repository/User'
|
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 { User as dbUser } from '@entity/User'
|
||||||
import { UserTransaction as dbUserTransaction } from '@entity/UserTransaction'
|
|
||||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||||
import { Balance as dbBalance } from '@entity/Balance'
|
import { Balance as dbBalance } from '@entity/Balance'
|
||||||
|
|
||||||
@ -33,166 +32,46 @@ import { TransactionType } from '../enum/TransactionType'
|
|||||||
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
import { hasUserAmount, isHexPublicKey } from '../../util/validate'
|
||||||
import { RIGHTS } from '../../auth/RIGHTS'
|
import { RIGHTS } from '../../auth/RIGHTS'
|
||||||
|
|
||||||
// Helper function
|
|
||||||
async function calculateAndAddDecayTransactions(
|
|
||||||
userTransactions: dbUserTransaction[],
|
|
||||||
user: dbUser,
|
|
||||||
decay: boolean,
|
|
||||||
skipFirstTransaction: boolean,
|
|
||||||
): Promise<Transaction[]> {
|
|
||||||
const finalTransactions: Transaction[] = []
|
|
||||||
const transactionIds: number[] = []
|
|
||||||
const involvedUserIds: number[] = []
|
|
||||||
|
|
||||||
userTransactions.forEach((userTransaction: dbUserTransaction) => {
|
|
||||||
transactionIds.push(userTransaction.transactionId)
|
|
||||||
})
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
involvedUserIds.push(transaction.sendReceiverUserId!) // TODO ensure not null properly
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// remove duplicates
|
|
||||||
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
|
|
||||||
const involvedUsersUnique = involvedUserIds.filter((v, i, a) => a.indexOf(v) === i)
|
|
||||||
const userRepository = getCustomRepository(UserRepository)
|
|
||||||
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
|
|
||||||
|
|
||||||
for (let i = 0; i < userTransactions.length; i++) {
|
|
||||||
const userTransaction = userTransactions[i]
|
|
||||||
const transaction = transactionIndiced[userTransaction.transactionId]
|
|
||||||
const finalTransaction = new Transaction()
|
|
||||||
finalTransaction.transactionId = transaction.id
|
|
||||||
finalTransaction.date = transaction.received.toISOString()
|
|
||||||
finalTransaction.memo = transaction.memo
|
|
||||||
finalTransaction.totalBalance = roundFloorFrom4(userTransaction.balance)
|
|
||||||
const previousTransaction = i > 0 ? userTransactions[i - 1] : null
|
|
||||||
|
|
||||||
if (previousTransaction) {
|
|
||||||
const currentTransaction = userTransaction
|
|
||||||
const decay = calculateDecay(
|
|
||||||
previousTransaction.balance,
|
|
||||||
previousTransaction.balanceDate,
|
|
||||||
currentTransaction.balanceDate,
|
|
||||||
)
|
|
||||||
const balance = previousTransaction.balance - decay.balance
|
|
||||||
|
|
||||||
if (CONFIG.DECAY_START_TIME < currentTransaction.balanceDate) {
|
|
||||||
finalTransaction.decay = decay
|
|
||||||
finalTransaction.decay.balance = roundFloorFrom4(balance)
|
|
||||||
if (
|
|
||||||
previousTransaction.balanceDate < CONFIG.DECAY_START_TIME &&
|
|
||||||
currentTransaction.balanceDate > CONFIG.DECAY_START_TIME
|
|
||||||
) {
|
|
||||||
finalTransaction.decay.decayStartBlock = (
|
|
||||||
CONFIG.DECAY_START_TIME.getTime() / 1000
|
|
||||||
).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sender or receiver when user has sent money
|
|
||||||
// group name if creation
|
|
||||||
// type: gesendet / empfangen / geschöpft
|
|
||||||
// transaktion nr / id
|
|
||||||
// date
|
|
||||||
// balance
|
|
||||||
if (userTransaction.transactionTypeId === TransactionTypeId.CREATION) {
|
|
||||||
// creation
|
|
||||||
finalTransaction.name = 'Gradido Akademie'
|
|
||||||
finalTransaction.type = TransactionType.CREATION
|
|
||||||
// finalTransaction.targetDate = creation.targetDate
|
|
||||||
finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion
|
|
||||||
} else if (userTransaction.transactionTypeId === TransactionTypeId.SEND) {
|
|
||||||
// send coin
|
|
||||||
let otherUser: dbUser | undefined
|
|
||||||
finalTransaction.balance = roundFloorFrom4(Number(transaction.amount)) // Todo unsafe conversion
|
|
||||||
if (transaction.userId === user.id) {
|
|
||||||
finalTransaction.type = TransactionType.SEND
|
|
||||||
otherUser = userIndiced.find((u) => u.id === transaction.sendReceiverUserId)
|
|
||||||
// finalTransaction.pubkey = sendCoin.recipiantPublic
|
|
||||||
} else if (transaction.sendReceiverUserId === user.id) {
|
|
||||||
finalTransaction.type = TransactionType.RECIEVE
|
|
||||||
otherUser = userIndiced.find((u) => u.id === transaction.userId)
|
|
||||||
// finalTransaction.pubkey = sendCoin.senderPublic
|
|
||||||
} else {
|
|
||||||
throw new Error('invalid transaction')
|
|
||||||
}
|
|
||||||
if (otherUser) {
|
|
||||||
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
|
|
||||||
finalTransaction.email = otherUser.email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i > 0 || !skipFirstTransaction) {
|
|
||||||
finalTransactions.push(finalTransaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === userTransactions.length - 1 && decay) {
|
|
||||||
const now = new Date()
|
|
||||||
const decay = calculateDecay(userTransaction.balance, userTransaction.balanceDate, now)
|
|
||||||
const balance = userTransaction.balance - decay.balance
|
|
||||||
|
|
||||||
const decayTransaction = new Transaction()
|
|
||||||
decayTransaction.type = 'decay'
|
|
||||||
decayTransaction.balance = roundCeilFrom4(balance)
|
|
||||||
decayTransaction.decayDuration = decay.decayDuration
|
|
||||||
decayTransaction.decayStart = decay.decayStart
|
|
||||||
decayTransaction.decayEnd = decay.decayEnd
|
|
||||||
finalTransactions.push(decayTransaction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return finalTransactions
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper helper function
|
// helper helper function
|
||||||
async function updateStateBalance(
|
async function updateStateBalance(
|
||||||
user: dbUser,
|
user: dbUser,
|
||||||
centAmount: number,
|
balance: number,
|
||||||
received: Date,
|
received: Date,
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
): Promise<dbBalance> {
|
): Promise<dbBalance> {
|
||||||
let balance = await dbBalance.findOne({ userId: user.id })
|
let userBalance = await dbBalance.findOne({ userId: user.id })
|
||||||
if (!balance) {
|
if (!userBalance) {
|
||||||
balance = new dbBalance()
|
userBalance = new dbBalance()
|
||||||
balance.userId = user.id
|
userBalance.userId = user.id
|
||||||
balance.amount = centAmount
|
userBalance.amount = balance
|
||||||
balance.modified = received
|
userBalance.modified = received
|
||||||
} else {
|
} else {
|
||||||
const decayedBalance = calculateDecay(balance.amount, balance.recordDate, received).balance
|
userBalance.amount = balance
|
||||||
balance.amount = Number(decayedBalance) + centAmount
|
userBalance.modified = new Date()
|
||||||
balance.modified = new Date()
|
|
||||||
}
|
}
|
||||||
if (balance.amount <= 0) {
|
if (userBalance.amount <= 0) {
|
||||||
throw new Error('error new balance <= 0')
|
throw new Error('error new balance <= 0')
|
||||||
}
|
}
|
||||||
balance.recordDate = received
|
userBalance.recordDate = received
|
||||||
return queryRunner.manager.save(balance).catch((error) => {
|
return queryRunner.manager.save(userBalance).catch((error) => {
|
||||||
throw new Error('error saving balance:' + error)
|
throw new Error('error saving balance:' + error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper helper function
|
async function calculateNewBalance(
|
||||||
async function addUserTransaction(
|
userId: number,
|
||||||
user: dbUser,
|
transactionDate: Date,
|
||||||
transaction: dbTransaction,
|
|
||||||
centAmount: number,
|
centAmount: number,
|
||||||
queryRunner: QueryRunner,
|
): Promise<number> {
|
||||||
): Promise<dbUserTransaction> {
|
|
||||||
let newBalance = centAmount
|
let newBalance = centAmount
|
||||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
const lastUserTransaction = await userTransactionRepository.findLastForUser(user.id)
|
const lastUserTransaction = await transactionRepository.findLastForUser(userId)
|
||||||
if (lastUserTransaction) {
|
if (lastUserTransaction) {
|
||||||
newBalance += Number(
|
newBalance += Number(
|
||||||
calculateDecay(
|
calculateDecay(
|
||||||
Number(lastUserTransaction.balance),
|
Number(lastUserTransaction.balance),
|
||||||
lastUserTransaction.balanceDate,
|
lastUserTransaction.balanceDate,
|
||||||
transaction.received,
|
transactionDate,
|
||||||
).balance,
|
).balance,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -201,18 +80,8 @@ async function addUserTransaction(
|
|||||||
throw new Error('error new balance <= 0')
|
throw new Error('error new balance <= 0')
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUserTransaction = new dbUserTransaction()
|
return newBalance
|
||||||
newUserTransaction.userId = user.id
|
|
||||||
newUserTransaction.transactionId = transaction.id
|
|
||||||
newUserTransaction.transactionTypeId = transaction.transactionTypeId
|
|
||||||
newUserTransaction.balance = newBalance
|
|
||||||
newUserTransaction.balanceDate = transaction.received
|
|
||||||
|
|
||||||
return queryRunner.manager.save(newUserTransaction).catch((error) => {
|
|
||||||
throw new Error('Error saving user transaction: ' + error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
export class TransactionResolver {
|
export class TransactionResolver {
|
||||||
@Authorized([RIGHTS.TRANSACTION_LIST])
|
@Authorized([RIGHTS.TRANSACTION_LIST])
|
||||||
@ -243,22 +112,119 @@ export class TransactionResolver {
|
|||||||
if (offset && order === Order.ASC) {
|
if (offset && order === Order.ASC) {
|
||||||
offset--
|
offset--
|
||||||
}
|
}
|
||||||
const userTransactionRepository = getCustomRepository(UserTransactionRepository)
|
const transactionRepository = getCustomRepository(TransactionRepository)
|
||||||
const [userTransactions, userTransactionsCount] =
|
const [userTransactions, userTransactionsCount] = await transactionRepository.findByUserPaged(
|
||||||
await userTransactionRepository.findByUserPaged(user.id, limit, offset, order, onlyCreations)
|
user.id,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
order,
|
||||||
|
onlyCreations,
|
||||||
|
)
|
||||||
skipFirstTransaction = userTransactionsCount > offset + limit
|
skipFirstTransaction = userTransactionsCount > offset + limit
|
||||||
const decay = !(currentPage > 1)
|
const decay = !(currentPage > 1)
|
||||||
let transactions: Transaction[] = []
|
const transactions: Transaction[] = []
|
||||||
if (userTransactions.length) {
|
if (userTransactions.length) {
|
||||||
if (order === Order.DESC) {
|
if (order === Order.DESC) {
|
||||||
userTransactions.reverse()
|
userTransactions.reverse()
|
||||||
}
|
}
|
||||||
transactions = await calculateAndAddDecayTransactions(
|
const involvedUserIds: number[] = []
|
||||||
userTransactions,
|
|
||||||
user,
|
userTransactions.forEach((transaction: dbTransaction) => {
|
||||||
decay,
|
involvedUserIds.push(transaction.userId)
|
||||||
skipFirstTransaction,
|
if (
|
||||||
|
transaction.typeId === TransactionTypeId.SEND ||
|
||||||
|
transaction.typeId === TransactionTypeId.RECEIVE
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
involvedUserIds.push(transaction.linkedUserId!) // TODO ensure not null properly
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// remove duplicates
|
||||||
|
// https://stackoverflow.com/questions/1960473/get-all-unique-values-in-a-javascript-array-remove-duplicates
|
||||||
|
const involvedUsersUnique = involvedUserIds.filter((v, i, a) => a.indexOf(v) === i)
|
||||||
|
const userRepository = getCustomRepository(UserRepository)
|
||||||
|
const userIndiced = await userRepository.getUsersIndiced(involvedUsersUnique)
|
||||||
|
|
||||||
|
for (let i = 0; i < userTransactions.length; i++) {
|
||||||
|
const userTransaction = userTransactions[i]
|
||||||
|
const finalTransaction = new Transaction()
|
||||||
|
finalTransaction.transactionId = userTransaction.id
|
||||||
|
finalTransaction.date = userTransaction.balanceDate.toISOString()
|
||||||
|
finalTransaction.memo = userTransaction.memo
|
||||||
|
finalTransaction.totalBalance = roundFloorFrom4(Number(userTransaction.balance))
|
||||||
|
const previousTransaction = i > 0 ? userTransactions[i - 1] : null
|
||||||
|
|
||||||
|
if (previousTransaction) {
|
||||||
|
const currentTransaction = userTransaction
|
||||||
|
const decay = calculateDecay(
|
||||||
|
Number(previousTransaction.balance),
|
||||||
|
previousTransaction.balanceDate,
|
||||||
|
currentTransaction.balanceDate,
|
||||||
)
|
)
|
||||||
|
const balance = Number(previousTransaction.balance) - decay.balance
|
||||||
|
|
||||||
|
if (CONFIG.DECAY_START_TIME < currentTransaction.balanceDate) {
|
||||||
|
finalTransaction.decay = decay
|
||||||
|
finalTransaction.decay.balance = roundFloorFrom4(balance)
|
||||||
|
if (
|
||||||
|
previousTransaction.balanceDate < CONFIG.DECAY_START_TIME &&
|
||||||
|
currentTransaction.balanceDate > CONFIG.DECAY_START_TIME
|
||||||
|
) {
|
||||||
|
finalTransaction.decay.decayStartBlock = (
|
||||||
|
CONFIG.DECAY_START_TIME.getTime() / 1000
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalTransaction.balance = roundFloorFrom4(Number(userTransaction.amount)) // Todo unsafe conversion
|
||||||
|
|
||||||
|
const otherUser = userIndiced.find((u) => u.id === userTransaction.linkedUserId)
|
||||||
|
switch (userTransaction.typeId) {
|
||||||
|
case TransactionTypeId.CREATION:
|
||||||
|
finalTransaction.name = 'Gradido Akademie'
|
||||||
|
finalTransaction.type = TransactionType.CREATION
|
||||||
|
break
|
||||||
|
case TransactionTypeId.SEND:
|
||||||
|
finalTransaction.type = TransactionType.SEND
|
||||||
|
if (otherUser) {
|
||||||
|
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
|
||||||
|
finalTransaction.email = otherUser.email
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case TransactionTypeId.RECEIVE:
|
||||||
|
finalTransaction.type = TransactionType.RECIEVE
|
||||||
|
if (otherUser) {
|
||||||
|
finalTransaction.name = otherUser.firstName + ' ' + otherUser.lastName
|
||||||
|
finalTransaction.email = otherUser.email
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error('invalid transaction')
|
||||||
|
}
|
||||||
|
if (i > 0 || !skipFirstTransaction) {
|
||||||
|
transactions.push(finalTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === userTransactions.length - 1 && decay) {
|
||||||
|
const now = new Date()
|
||||||
|
const decay = calculateDecay(
|
||||||
|
Number(userTransaction.balance),
|
||||||
|
userTransaction.balanceDate,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
const balance = Number(userTransaction.balance) - decay.balance
|
||||||
|
|
||||||
|
const decayTransaction = new Transaction()
|
||||||
|
decayTransaction.type = 'decay'
|
||||||
|
decayTransaction.balance = roundCeilFrom4(balance)
|
||||||
|
decayTransaction.decayDuration = decay.decayDuration
|
||||||
|
decayTransaction.decayStart = decay.decayStart
|
||||||
|
decayTransaction.decayEnd = decay.decayEnd
|
||||||
|
transactions.push(decayTransaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (order === Order.DESC) {
|
if (order === Order.DESC) {
|
||||||
transactions.reverse()
|
transactions.reverse()
|
||||||
}
|
}
|
||||||
@ -321,87 +287,62 @@ export class TransactionResolver {
|
|||||||
throw new Error('invalid recipient public key')
|
throw new Error('invalid recipient public key')
|
||||||
}
|
}
|
||||||
|
|
||||||
const centAmount = Math.trunc(amount * 10000)
|
const centAmount = Math.round(amount * 10000)
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
await queryRunner.startTransaction('READ UNCOMMITTED')
|
await queryRunner.startTransaction('READ UNCOMMITTED')
|
||||||
try {
|
try {
|
||||||
|
const receivedCallDate = new Date()
|
||||||
// transaction
|
// transaction
|
||||||
const transaction = new dbTransaction()
|
const transactionSend = new dbTransaction()
|
||||||
transaction.transactionTypeId = TransactionTypeId.SEND
|
transactionSend.typeId = TransactionTypeId.SEND
|
||||||
transaction.memo = memo
|
transactionSend.memo = memo
|
||||||
transaction.userId = senderUser.id
|
transactionSend.userId = senderUser.id
|
||||||
transaction.pubkey = senderUser.pubKey
|
transactionSend.linkedUserId = recipientUser.id
|
||||||
transaction.sendReceiverUserId = recipientUser.id
|
transactionSend.amount = BigInt(centAmount)
|
||||||
transaction.sendReceiverPublicKey = recipientUser.pubKey
|
const sendBalance = await calculateNewBalance(senderUser.id, receivedCallDate, -centAmount)
|
||||||
transaction.amount = BigInt(centAmount)
|
transactionSend.balance = BigInt(Math.trunc(sendBalance))
|
||||||
|
transactionSend.balanceDate = receivedCallDate
|
||||||
|
transactionSend.sendSenderFinalBalance = transactionSend.balance
|
||||||
|
await queryRunner.manager.insert(dbTransaction, transactionSend)
|
||||||
|
|
||||||
await queryRunner.manager.insert(dbTransaction, transaction)
|
const transactionReceive = new dbTransaction()
|
||||||
|
transactionReceive.typeId = TransactionTypeId.RECEIVE
|
||||||
// Insert Transaction: sender - amount
|
transactionReceive.memo = memo
|
||||||
const senderUserTransactionBalance = await addUserTransaction(
|
transactionReceive.userId = recipientUser.id
|
||||||
senderUser,
|
transactionReceive.linkedUserId = senderUser.id
|
||||||
transaction,
|
transactionReceive.amount = BigInt(centAmount)
|
||||||
-centAmount,
|
const receiveBalance = await calculateNewBalance(
|
||||||
queryRunner,
|
recipientUser.id,
|
||||||
)
|
receivedCallDate,
|
||||||
|
|
||||||
// Insert Transaction: recipient + amount
|
|
||||||
const recipiantUserTransactionBalance = await addUserTransaction(
|
|
||||||
recipientUser,
|
|
||||||
transaction,
|
|
||||||
centAmount,
|
centAmount,
|
||||||
queryRunner,
|
|
||||||
)
|
)
|
||||||
|
transactionReceive.balance = BigInt(Math.trunc(receiveBalance))
|
||||||
|
transactionReceive.balanceDate = receivedCallDate
|
||||||
|
transactionReceive.sendSenderFinalBalance = transactionSend.balance
|
||||||
|
transactionReceive.linkedTransactionId = transactionSend.id
|
||||||
|
await queryRunner.manager.insert(dbTransaction, transactionReceive)
|
||||||
|
|
||||||
// Update Balance: sender - amount
|
// Save linked transaction id for send
|
||||||
const senderStateBalance = await updateStateBalance(
|
transactionSend.linkedTransactionId = transactionReceive.id
|
||||||
senderUser,
|
await queryRunner.manager.update(dbTransaction, { id: transactionSend.id }, transactionSend)
|
||||||
-centAmount,
|
|
||||||
transaction.received,
|
|
||||||
queryRunner,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update Balance: recipiant + amount
|
// Update Balance sender
|
||||||
const recipiantStateBalance = await updateStateBalance(
|
await updateStateBalance(senderUser, Math.trunc(sendBalance), receivedCallDate, queryRunner)
|
||||||
|
|
||||||
|
// Update Balance recipient
|
||||||
|
await updateStateBalance(
|
||||||
recipientUser,
|
recipientUser,
|
||||||
centAmount,
|
Math.trunc(receiveBalance),
|
||||||
transaction.received,
|
receivedCallDate,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (senderStateBalance.amount !== senderUserTransactionBalance.balance) {
|
|
||||||
throw new Error('db data corrupted, sender')
|
|
||||||
}
|
|
||||||
if (recipiantStateBalance.amount !== recipiantUserTransactionBalance.balance) {
|
|
||||||
throw new Error('db data corrupted, recipiant')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
})
|
|
||||||
|
|
||||||
await queryRunner.commitTransaction()
|
await queryRunner.commitTransaction()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
// TODO: This is broken code - we should never correct an autoincrement index in production
|
throw new Error(`Transaction was not successful: ${e}`)
|
||||||
// according to dario it is required tho to properly work. The index of the table is used as
|
|
||||||
// index for the transaction which requires a chain without gaps
|
|
||||||
const count = await queryRunner.manager.count(dbTransaction)
|
|
||||||
// fix autoincrement value which seems not effected from rollback
|
|
||||||
await queryRunner
|
|
||||||
.query('ALTER TABLE `transactions` auto_increment = ?', [count])
|
|
||||||
.catch((error) => {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log('problems with reset auto increment: %o', error)
|
|
||||||
})
|
|
||||||
throw e
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -335,7 +335,7 @@ export class UserResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate email unique
|
// Validate email unique
|
||||||
// TODO: i can register an email in upper/lower case twice
|
email = email.trim().toLowerCase()
|
||||||
// TODO we cannot use repository.count(), since it does not allow to specify if you want to include the soft deletes
|
// 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 })
|
const userFound = await DbUser.findOne({ email }, { withDeleted: true })
|
||||||
if (userFound) {
|
if (userFound) {
|
||||||
@ -408,6 +408,7 @@ export class UserResolver {
|
|||||||
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
|
@Authorized([RIGHTS.SEND_ACTIVATION_EMAIL])
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
|
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
|
email = email.trim().toLowerCase()
|
||||||
const user = await DbUser.findOneOrFail({ email: email })
|
const user = await DbUser.findOneOrFail({ email: email })
|
||||||
|
|
||||||
const queryRunner = getConnection().createQueryRunner()
|
const queryRunner = getConnection().createQueryRunner()
|
||||||
@ -448,7 +449,7 @@ export class UserResolver {
|
|||||||
@Query(() => Boolean)
|
@Query(() => Boolean)
|
||||||
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
async sendResetPasswordEmail(@Arg('email') email: string): Promise<boolean> {
|
||||||
// TODO: this has duplicate code with createUser
|
// TODO: this has duplicate code with createUser
|
||||||
|
email = email.trim().toLowerCase()
|
||||||
const user = await DbUser.findOneOrFail({ email })
|
const user = await DbUser.findOneOrFail({ email })
|
||||||
|
|
||||||
const optInCode = await getOptInCode(user.id)
|
const optInCode = await getOptInCode(user.id)
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { EntityRepository, Repository } from '@dbTools/typeorm'
|
import { EntityRepository, Repository } from '@dbTools/typeorm'
|
||||||
|
import { Transaction } from '@entity/Transaction'
|
||||||
import { Order } from '../../graphql/enum/Order'
|
import { Order } from '../../graphql/enum/Order'
|
||||||
import { UserTransaction } from '@entity/UserTransaction'
|
|
||||||
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
|
import { TransactionTypeId } from '../../graphql/enum/TransactionTypeId'
|
||||||
|
|
||||||
@EntityRepository(UserTransaction)
|
@EntityRepository(Transaction)
|
||||||
export class UserTransactionRepository extends Repository<UserTransaction> {
|
export class TransactionRepository extends Repository<Transaction> {
|
||||||
findByUserPaged(
|
findByUserPaged(
|
||||||
userId: number,
|
userId: number,
|
||||||
limit: number,
|
limit: number,
|
||||||
offset: number,
|
offset: number,
|
||||||
order: Order,
|
order: Order,
|
||||||
onlyCreation?: boolean,
|
onlyCreation?: boolean,
|
||||||
): Promise<[UserTransaction[], number]> {
|
): Promise<[Transaction[], number]> {
|
||||||
if (onlyCreation) {
|
if (onlyCreation) {
|
||||||
return this.createQueryBuilder('userTransaction')
|
return this.createQueryBuilder('userTransaction')
|
||||||
.where('userTransaction.userId = :userId', { userId })
|
.where('userTransaction.userId = :userId', { userId })
|
||||||
@ -31,10 +31,10 @@ export class UserTransactionRepository extends Repository<UserTransaction> {
|
|||||||
.getManyAndCount()
|
.getManyAndCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
findLastForUser(userId: number): Promise<UserTransaction | undefined> {
|
findLastForUser(userId: number): Promise<Transaction | undefined> {
|
||||||
return this.createQueryBuilder('userTransaction')
|
return this.createQueryBuilder('userTransaction')
|
||||||
.where('userTransaction.userId = :userId', { userId })
|
.where('userTransaction.userId = :userId', { userId })
|
||||||
.orderBy('userTransaction.transactionId', 'DESC')
|
.orderBy('userTransaction.balanceDate', 'DESC')
|
||||||
.getOne()
|
.getOne()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('transactions')
|
||||||
|
export class Transaction extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ name: 'transaction_id', unsigned: true, nullable: false })
|
||||||
|
transactionId: number
|
||||||
|
|
||||||
|
@Column({ name: 'transaction_type_id', unsigned: true, nullable: false })
|
||||||
|
transactionTypeId: number
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', nullable: false })
|
||||||
|
amount: BigInt
|
||||||
|
|
||||||
|
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'send_sender_final_balance',
|
||||||
|
type: 'bigint',
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
sendSenderFinalBalance: BigInt | null
|
||||||
|
|
||||||
|
@Column({ name: 'balance', type: 'bigint', default: 0 })
|
||||||
|
balance: BigInt
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'balance_date',
|
||||||
|
type: 'timestamp',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
balanceDate: Date
|
||||||
|
|
||||||
|
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP', nullable: false })
|
||||||
|
received: Date
|
||||||
|
|
||||||
|
@Column({ name: 'creation_date', type: 'timestamp', nullable: true, default: null })
|
||||||
|
creationDate: Date
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'linked_user_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
linkedUserId?: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'linked_state_user_transaction_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
linkedStateUserTransactionId?: number | null
|
||||||
|
|
||||||
|
@Column({ type: 'binary', length: 64, nullable: true, default: null })
|
||||||
|
signature: Buffer
|
||||||
|
|
||||||
|
@Column({ name: 'tx_hash', type: 'binary', length: 48, default: null, nullable: true })
|
||||||
|
txHash: 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
|
||||||
|
}
|
||||||
59
database/entity/0027-clean_transaction_table/Transaction.ts
Normal file
59
database/entity/0027-clean_transaction_table/Transaction.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
|
||||||
|
|
||||||
|
@Entity('transactions')
|
||||||
|
export class Transaction extends BaseEntity {
|
||||||
|
@PrimaryGeneratedColumn('increment', { unsigned: true })
|
||||||
|
id: number
|
||||||
|
|
||||||
|
@Column({ name: 'user_id', unsigned: true, nullable: false })
|
||||||
|
userId: number
|
||||||
|
|
||||||
|
@Column({ name: 'type_id', unsigned: true, nullable: false })
|
||||||
|
typeId: number
|
||||||
|
|
||||||
|
@Column({ type: 'bigint', nullable: false })
|
||||||
|
amount: BigInt
|
||||||
|
|
||||||
|
@Column({ length: 255, nullable: false, collation: 'utf8mb4_unicode_ci' })
|
||||||
|
memo: string
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'send_sender_final_balance',
|
||||||
|
type: 'bigint',
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
sendSenderFinalBalance: BigInt | null
|
||||||
|
|
||||||
|
@Column({ name: 'balance', type: 'bigint', default: 0 })
|
||||||
|
balance: BigInt
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'balance_date',
|
||||||
|
type: 'timestamp',
|
||||||
|
default: () => 'CURRENT_TIMESTAMP',
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
balanceDate: Date
|
||||||
|
|
||||||
|
@Column({ name: 'creation_date', type: 'timestamp', nullable: true, default: null })
|
||||||
|
creationDate: Date
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'linked_user_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
linkedUserId?: number | null
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
name: 'linked_transaction_id',
|
||||||
|
type: 'int',
|
||||||
|
unsigned: true,
|
||||||
|
nullable: true,
|
||||||
|
default: null,
|
||||||
|
})
|
||||||
|
linkedTransactionId?: number | null
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
export { Transaction } from './0024-combine_transaction_tables/Transaction'
|
export { Transaction } from './0027-clean_transaction_table/Transaction'
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export { UserTransaction } from './0001-init_db/UserTransaction'
|
|
||||||
@ -6,7 +6,6 @@ import { ServerUser } from './ServerUser'
|
|||||||
import { Transaction } from './Transaction'
|
import { Transaction } from './Transaction'
|
||||||
import { User } from './User'
|
import { User } from './User'
|
||||||
import { UserSetting } from './UserSetting'
|
import { UserSetting } from './UserSetting'
|
||||||
import { UserTransaction } from './UserTransaction'
|
|
||||||
import { AdminPendingCreation } from './AdminPendingCreation'
|
import { AdminPendingCreation } from './AdminPendingCreation'
|
||||||
|
|
||||||
export const entities = [
|
export const entities = [
|
||||||
@ -19,5 +18,4 @@ export const entities = [
|
|||||||
Transaction,
|
Transaction,
|
||||||
User,
|
User,
|
||||||
UserSetting,
|
UserSetting,
|
||||||
UserTransaction,
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/* MIGRATION TO COMBINE ALL TRANSACTION TABLES
|
/* MIGRATION TO COMBINE SEVERAL TRANSACTION TABLES
|
||||||
*
|
*
|
||||||
* Combine all transaction tables into one table with all data
|
* Combine several transaction tables into one table with all data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
|
|||||||
17
database/migrations/0025-emails_to_lower.ts
Normal file
17
database/migrations/0025-emails_to_lower.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/* MIGRATION TO MAKE ALL EMAILS LOWERCASE
|
||||||
|
*
|
||||||
|
* Make all `email` values in `users` lowercase.
|
||||||
|
* This allows safe queries without any modificators
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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('UPDATE `users` SET `email` = LOWER(`email`);')
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// This migration cannot be revered
|
||||||
|
}
|
||||||
218
database/migrations/0026-combine_transaction_tables2.ts
Normal file
218
database/migrations/0026-combine_transaction_tables2.ts
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/* MIGRATION TO COMBINE AND REFACTOR SOME TRANSACTION TABLES
|
||||||
|
*
|
||||||
|
* Combine `state_user_transactions` and `transactions` tables.
|
||||||
|
* This changes the structure of transactions from 1 transaction for
|
||||||
|
* each send-coins to two transactions per send-coin
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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>>) {
|
||||||
|
/*
|
||||||
|
* This migration has a possible incompatibility
|
||||||
|
* due to the construction of the tables.
|
||||||
|
* For our production data it works well.
|
||||||
|
* With this migration we decide for int instead of bigint
|
||||||
|
* to handle things more easily
|
||||||
|
*
|
||||||
|
* CREATE TABLE `transactions` (
|
||||||
|
* `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
* CREATE TABLE `state_user_transactions` (
|
||||||
|
* ...
|
||||||
|
* `transaction_id` int(10) unsigned NOT NULL,
|
||||||
|
* ...
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
|
||||||
|
// rename `state_user_id` to `user_id`
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` RENAME COLUMN state_user_id TO user_id;')
|
||||||
|
// Create new `amount` column, with a temporary default of null
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `amount` bigint(20) DEFAULT NULL AFTER `transaction_type_id`;',
|
||||||
|
)
|
||||||
|
// Create new `send_sender_final_balance`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `send_sender_final_balance` bigint(20) DEFAULT NULL AFTER `amount`;',
|
||||||
|
)
|
||||||
|
// Create new `memo` column, with a temporary default of null
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `memo` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER `amount`;',
|
||||||
|
)
|
||||||
|
// Create new `received` column, with a temporary default of null
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `received` timestamp NULL DEFAULT NULL AFTER `balance_date`;',
|
||||||
|
)
|
||||||
|
// Create new `creation_date` column
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `creation_date` timestamp NULL DEFAULT NULL AFTER `received`;',
|
||||||
|
)
|
||||||
|
// Create new `linked_user_id` column (former `send_receiver_user_id`)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `linked_user_id` int(10) unsigned DEFAULT NULL AFTER `creation_date`;',
|
||||||
|
)
|
||||||
|
// Create new `linked_state_user_transaction_id`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `linked_state_user_transaction_id` int(10) unsigned DEFAULT NULL AFTER `linked_user_id`;',
|
||||||
|
)
|
||||||
|
// Create new `tx_hash`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `tx_hash` binary(48) DEFAULT NULL AFTER `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
// Create new `signature`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `signature` binary(64) DEFAULT NULL AFTER `tx_hash`;',
|
||||||
|
)
|
||||||
|
// Create new `pubkey`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `pubkey` binary(32) DEFAULT NULL AFTER `signature`;',
|
||||||
|
)
|
||||||
|
// Create new `creation_ident_hash`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` ADD COLUMN `creation_ident_hash` binary(32) DEFAULT NULL AFTER `pubkey`;',
|
||||||
|
)
|
||||||
|
|
||||||
|
// Insert Data from `transactions` for creations
|
||||||
|
await queryFn(`
|
||||||
|
UPDATE state_user_transactions
|
||||||
|
LEFT JOIN transactions ON state_user_transactions.transaction_id = transactions.id
|
||||||
|
SET state_user_transactions.amount = transactions.amount,
|
||||||
|
state_user_transactions.send_sender_final_balance = transactions.send_sender_final_balance,
|
||||||
|
state_user_transactions.memo = transactions.memo,
|
||||||
|
state_user_transactions.received = transactions.received,
|
||||||
|
state_user_transactions.creation_date = transactions.creation_date,
|
||||||
|
state_user_transactions.linked_user_id = transactions.send_receiver_user_id,
|
||||||
|
state_user_transactions.linked_state_user_transaction_id = NULL,
|
||||||
|
state_user_transactions.tx_hash = transactions.tx_hash,
|
||||||
|
state_user_transactions.signature = transactions.signature,
|
||||||
|
state_user_transactions.pubkey = transactions.pubkey,
|
||||||
|
state_user_transactions.creation_ident_hash = transactions.creation_ident_hash
|
||||||
|
WHERE state_user_transactions.transaction_type_id = 1;
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Insert Data from `transactions` for sendCoin sender
|
||||||
|
await queryFn(`
|
||||||
|
UPDATE state_user_transactions
|
||||||
|
LEFT JOIN transactions ON state_user_transactions.transaction_id = transactions.id
|
||||||
|
SET state_user_transactions.amount = transactions.amount,
|
||||||
|
state_user_transactions.send_sender_final_balance = transactions.send_sender_final_balance,
|
||||||
|
state_user_transactions.memo = transactions.memo,
|
||||||
|
state_user_transactions.received = transactions.received,
|
||||||
|
state_user_transactions.creation_date = transactions.creation_date,
|
||||||
|
state_user_transactions.linked_user_id = transactions.send_receiver_user_id,
|
||||||
|
state_user_transactions.linked_state_user_transaction_id = (
|
||||||
|
SELECT id FROM state_user_transactions AS sut
|
||||||
|
WHERE sut.transaction_type_id = 2
|
||||||
|
AND sut.transaction_id = state_user_transactions.transaction_id
|
||||||
|
AND sut.user_id = transactions.send_receiver_user_id
|
||||||
|
),
|
||||||
|
state_user_transactions.tx_hash = transactions.tx_hash,
|
||||||
|
state_user_transactions.signature = transactions.signature,
|
||||||
|
state_user_transactions.pubkey = transactions.pubkey,
|
||||||
|
state_user_transactions.creation_ident_hash = transactions.creation_ident_hash
|
||||||
|
WHERE state_user_transactions.transaction_type_id = 2
|
||||||
|
AND state_user_transactions.user_id = transactions.user_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Insert Data from `transactions` for sendCoin receiver
|
||||||
|
await queryFn(`
|
||||||
|
UPDATE state_user_transactions
|
||||||
|
LEFT JOIN transactions ON state_user_transactions.transaction_id = transactions.id
|
||||||
|
SET state_user_transactions.amount = transactions.amount,
|
||||||
|
state_user_transactions.send_sender_final_balance = transactions.send_sender_final_balance,
|
||||||
|
state_user_transactions.memo = transactions.memo,
|
||||||
|
state_user_transactions.received = transactions.received,
|
||||||
|
state_user_transactions.creation_date = transactions.creation_date,
|
||||||
|
state_user_transactions.linked_user_id = transactions.user_id,
|
||||||
|
state_user_transactions.linked_state_user_transaction_id = (
|
||||||
|
SELECT id FROM state_user_transactions AS sut
|
||||||
|
WHERE sut.transaction_type_id = 2
|
||||||
|
AND sut.transaction_id = state_user_transactions.transaction_id
|
||||||
|
AND sut.user_id = transactions.user_id
|
||||||
|
),
|
||||||
|
state_user_transactions.tx_hash = transactions.tx_hash,
|
||||||
|
state_user_transactions.signature = transactions.signature,
|
||||||
|
state_user_transactions.pubkey = transactions.send_receiver_public_key,
|
||||||
|
state_user_transactions.creation_ident_hash = transactions.creation_ident_hash,
|
||||||
|
state_user_transactions.transaction_type_id = 3
|
||||||
|
WHERE state_user_transactions.transaction_type_id = 2
|
||||||
|
AND state_user_transactions.user_id = transactions.send_receiver_user_id;
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Modify defaults after our inserts
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` MODIFY COLUMN `amount` bigint(20) NOT NULL;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` MODIFY COLUMN `memo` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` MODIFY COLUMN `received` timestamp NOT NULL DEFAULT current_timestamp()',
|
||||||
|
)
|
||||||
|
|
||||||
|
// Drop table `transactions`
|
||||||
|
await queryFn('DROP TABLE `transactions`;')
|
||||||
|
|
||||||
|
// Rename table `transaction_send_coins` to `transactions`
|
||||||
|
await queryFn('RENAME TABLE `state_user_transactions` TO `transactions`;')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
await queryFn('RENAME TABLE `transactions` TO `state_user_transactions`;')
|
||||||
|
await queryFn(`CREATE TABLE \`transactions\` (
|
||||||
|
\`id\` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
\`transaction_type_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`user_id\` int(10) unsigned NOT NULL,
|
||||||
|
\`amount\` bigint(20) NOT NULL,
|
||||||
|
\`tx_hash\` binary(48) DEFAULT NULL,
|
||||||
|
\`memo\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||||
|
\`received\` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
\`signature\` binary(64) DEFAULT NULL,
|
||||||
|
\`pubkey\` binary(32) DEFAULT NULL,
|
||||||
|
\`creation_ident_hash\` binary(32) DEFAULT NULL,
|
||||||
|
\`creation_date\` timestamp NULL DEFAULT NULL,
|
||||||
|
\`send_receiver_public_key\` binary(32) DEFAULT NULL,
|
||||||
|
\`send_receiver_user_id\` int(10) unsigned DEFAULT NULL,
|
||||||
|
\`send_sender_final_balance\` bigint(20) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=3424 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
`)
|
||||||
|
await queryFn(`
|
||||||
|
INSERT INTO transactions (
|
||||||
|
id, transaction_type_id, user_id, amount,
|
||||||
|
tx_hash, memo, received, signature, pubkey,
|
||||||
|
creation_ident_hash, creation_date,
|
||||||
|
send_receiver_public_key, send_receiver_user_id,
|
||||||
|
send_sender_final_balance
|
||||||
|
)
|
||||||
|
SELECT transaction_id AS id, transaction_type_id,
|
||||||
|
user_id, amount, tx_hash, memo, received,
|
||||||
|
signature, pubkey, creation_ident_hash,
|
||||||
|
creation_date, send_receiver_public_key,
|
||||||
|
linked_user_id AS send_receiver_user_id,
|
||||||
|
send_sender_final_balance
|
||||||
|
FROM state_user_transactions LEFT JOIN (
|
||||||
|
SELECT id, pubkey AS send_receiver_public_key
|
||||||
|
FROM state_user_transactions AS sut
|
||||||
|
WHERE sut.transaction_type_id = 3
|
||||||
|
) AS sutj ON sutj.id = state_user_transactions.id
|
||||||
|
WHERE transaction_type_id IN (1,2)
|
||||||
|
`)
|
||||||
|
await queryFn(
|
||||||
|
'UPDATE state_user_transactions SET transaction_type_id = 2 WHERE transaction_type_id = 3;',
|
||||||
|
)
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `creation_ident_hash`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `pubkey`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `signature`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `tx_hash`;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `state_user_transactions` DROP COLUMN `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `linked_user_id`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `creation_date`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `received`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `memo`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `send_sender_final_balance`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` DROP COLUMN `amount`;')
|
||||||
|
await queryFn('ALTER TABLE `state_user_transactions` RENAME COLUMN user_id TO state_user_id;')
|
||||||
|
}
|
||||||
73
database/migrations/0027-clean_transaction_table.ts
Normal file
73
database/migrations/0027-clean_transaction_table.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* MIGRATION TO CLEAN THE TRANSACTION TABLE
|
||||||
|
*
|
||||||
|
* Remove several unused fields or those with duplicate data
|
||||||
|
* and rename fields to a proper name in `transactions` .
|
||||||
|
*
|
||||||
|
* This migration has data loss
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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>>) {
|
||||||
|
// drop column `transaction_id`, it is not needed
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `transaction_id`;')
|
||||||
|
// drop column `received`, it is a duplicate of balance_date
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `received`;')
|
||||||
|
// drop column `tx_hash`, it is not needed
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `tx_hash`;')
|
||||||
|
// drop column `signature`, it is not needed
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `signature`;')
|
||||||
|
// drop column `pubkey`, it is not needed
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `pubkey`;')
|
||||||
|
// drop column `creation_ident_hash`, it is not needed
|
||||||
|
await queryFn('ALTER TABLE `transactions` DROP COLUMN `creation_ident_hash`;')
|
||||||
|
|
||||||
|
// rename `transaction_type_id` to `type_id`
|
||||||
|
await queryFn('ALTER TABLE `transactions` RENAME COLUMN transaction_type_id TO type_id;')
|
||||||
|
// rename `linked_state_user_transaction_id` to `linked_transaction_id`
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` RENAME COLUMN linked_state_user_transaction_id TO linked_transaction_id;',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
|
||||||
|
// Not all data is recoverable here, some data is simulated,
|
||||||
|
// but we have data loss on:
|
||||||
|
// - transaction_id (we have data here, but its not the same as before)
|
||||||
|
// - tx_hash (null)
|
||||||
|
// - signature (null)
|
||||||
|
// - pubkey (null)
|
||||||
|
// - creation_ident_hash (null)
|
||||||
|
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` RENAME COLUMN linked_transaction_id TO linked_state_user_transaction_id;',
|
||||||
|
)
|
||||||
|
await queryFn('ALTER TABLE `transactions` RENAME COLUMN type_id TO transaction_type_id;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `creation_ident_hash` binary(32) DEFAULT NULL AFTER `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `pubkey` binary(32) DEFAULT NULL AFTER `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `signature` binary(64) DEFAULT NULL AFTER `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `tx_hash` binary(48) DEFAULT NULL AFTER `linked_state_user_transaction_id`;',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `received` timestamp NULL DEFAULT NULL AFTER `balance_date`;',
|
||||||
|
)
|
||||||
|
await queryFn('UPDATE `transactions` SET `received` = `balance_date`;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` MODIFY COLUMN `received` timestamp NOT NULL DEFAULT current_timestamp();',
|
||||||
|
)
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` ADD COLUMN `transaction_id` int(10) unsigned DEFAULT NULL AFTER `user_id`;',
|
||||||
|
)
|
||||||
|
await queryFn('UPDATE `transactions` SET `transaction_id` = `id`;')
|
||||||
|
await queryFn(
|
||||||
|
'ALTER TABLE `transactions` MODIFY COLUMN `transaction_id` int(10) unsigned NOT NULL;',
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import Faker from 'faker'
|
|||||||
import { define } from 'typeorm-seeding'
|
import { define } from 'typeorm-seeding'
|
||||||
import { Transaction } from '../../entity/Transaction'
|
import { Transaction } from '../../entity/Transaction'
|
||||||
import { TransactionContext } from '../interface/TransactionContext'
|
import { TransactionContext } from '../interface/TransactionContext'
|
||||||
import { randomBytes } from 'crypto'
|
|
||||||
|
|
||||||
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
@ -10,18 +9,13 @@ define(Transaction, (faker: typeof Faker, context?: TransactionContext) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transaction = new Transaction()
|
const transaction = new Transaction()
|
||||||
transaction.transactionTypeId = context.transactionTypeId // || 2
|
transaction.typeId = context.typeId // || 2
|
||||||
transaction.userId = context.userId
|
transaction.userId = context.userId
|
||||||
transaction.amount = context.amount
|
transaction.amount = context.amount
|
||||||
transaction.txHash = context.txHash || randomBytes(48)
|
|
||||||
transaction.memo = context.memo
|
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.creationDate = context.creationDate || new Date()
|
||||||
transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null
|
// transaction.sendReceiverPublicKey = context.sendReceiverPublicKey || null
|
||||||
transaction.sendReceiverUserId = context.sendReceiverUserId || null
|
transaction.linkedUserId = context.sendReceiverUserId || null
|
||||||
transaction.sendSenderFinalBalance = context.sendSenderFinalBalance || null
|
transaction.sendSenderFinalBalance = context.sendSenderFinalBalance || null
|
||||||
|
|
||||||
return transaction
|
return transaction
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import Faker from 'faker'
|
|
||||||
import { define } from 'typeorm-seeding'
|
|
||||||
import { UserTransaction } from '../../entity/UserTransaction'
|
|
||||||
import { UserTransactionContext } from '../interface/TransactionContext'
|
|
||||||
|
|
||||||
define(UserTransaction, (faker: typeof Faker, context?: UserTransactionContext) => {
|
|
||||||
if (!context || !context.userId || !context.transactionId) {
|
|
||||||
throw new Error('UserTransaction: No userId and/or transactionId present!')
|
|
||||||
}
|
|
||||||
|
|
||||||
const userTransaction = new UserTransaction()
|
|
||||||
userTransaction.userId = context.userId
|
|
||||||
userTransaction.transactionId = context.transactionId
|
|
||||||
userTransaction.transactionTypeId = context.transactionTypeId ? context.transactionTypeId : 1
|
|
||||||
userTransaction.balance = context.balance ? context.balance : 100000
|
|
||||||
userTransaction.balanceDate = context.balanceDate ? context.balanceDate : new Date()
|
|
||||||
|
|
||||||
return userTransaction
|
|
||||||
})
|
|
||||||
@ -1,18 +1,13 @@
|
|||||||
import { Transaction } from '../../entity/Transaction'
|
|
||||||
import { User } from '../../entity/User'
|
import { User } from '../../entity/User'
|
||||||
|
|
||||||
export interface TransactionContext {
|
export interface TransactionContext {
|
||||||
transactionTypeId: number
|
typeId: number
|
||||||
userId: number
|
userId: number
|
||||||
|
balance: BigInt
|
||||||
|
balanceDate: Date
|
||||||
amount: BigInt
|
amount: BigInt
|
||||||
txHash?: Buffer
|
|
||||||
memo: string
|
memo: string
|
||||||
received?: Date
|
|
||||||
signature?: Buffer
|
|
||||||
pubkey?: Buffer
|
|
||||||
creationIdentHash?: Buffer
|
|
||||||
creationDate?: Date
|
creationDate?: Date
|
||||||
sendReceiverPublicKey?: Buffer
|
|
||||||
sendReceiverUserId?: number
|
sendReceiverUserId?: number
|
||||||
sendSenderFinalBalance?: BigInt
|
sendSenderFinalBalance?: BigInt
|
||||||
}
|
}
|
||||||
@ -23,23 +18,3 @@ export interface BalanceContext {
|
|||||||
amount?: number
|
amount?: number
|
||||||
user?: User
|
user?: User
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionSendCoinContext {
|
|
||||||
senderPublic?: Buffer
|
|
||||||
userId?: number
|
|
||||||
recipiantPublic?: Buffer
|
|
||||||
recipiantUserId?: number
|
|
||||||
amount?: number
|
|
||||||
senderFinalBalance?: number
|
|
||||||
transaction?: Transaction
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserTransactionContext {
|
|
||||||
userId?: number
|
|
||||||
transactionId?: number
|
|
||||||
transactionTypeId?: number
|
|
||||||
balance?: number
|
|
||||||
balanceDate?: Date
|
|
||||||
signature?: Buffer
|
|
||||||
pubkey?: Buffer
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,15 +1,10 @@
|
|||||||
import { UserContext, ServerUserContext } from '../../interface/UserContext'
|
import { UserContext, ServerUserContext } from '../../interface/UserContext'
|
||||||
import {
|
import { BalanceContext, TransactionContext } from '../../interface/TransactionContext'
|
||||||
BalanceContext,
|
|
||||||
TransactionContext,
|
|
||||||
UserTransactionContext,
|
|
||||||
} from '../../interface/TransactionContext'
|
|
||||||
import { UserInterface } from '../../interface/UserInterface'
|
import { UserInterface } from '../../interface/UserInterface'
|
||||||
import { User } from '../../../entity/User'
|
import { User } from '../../../entity/User'
|
||||||
import { ServerUser } from '../../../entity/ServerUser'
|
import { ServerUser } from '../../../entity/ServerUser'
|
||||||
import { Balance } from '../../../entity/Balance'
|
import { Balance } from '../../../entity/Balance'
|
||||||
import { Transaction } from '../../../entity/Transaction'
|
import { Transaction } from '../../../entity/Transaction'
|
||||||
import { UserTransaction } from '../../../entity/UserTransaction'
|
|
||||||
import { Factory } from 'typeorm-seeding'
|
import { Factory } from 'typeorm-seeding'
|
||||||
|
|
||||||
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
export const userSeeder = async (factory: Factory, userData: UserInterface): Promise<void> => {
|
||||||
@ -22,12 +17,9 @@ export const userSeeder = async (factory: Factory, userData: UserInterface): Pro
|
|||||||
if (userData.addBalance) {
|
if (userData.addBalance) {
|
||||||
// create some GDD for the user
|
// create some GDD for the user
|
||||||
await factory(Balance)(createBalanceContext(userData, user)).create()
|
await factory(Balance)(createBalanceContext(userData, user)).create()
|
||||||
const transaction = await factory(Transaction)(
|
await factory(Transaction)(
|
||||||
createTransactionContext(userData, user, 1, 'Herzlich Willkommen bei Gradido!'),
|
createTransactionContext(userData, user, 1, 'Herzlich Willkommen bei Gradido!'),
|
||||||
).create()
|
).create()
|
||||||
await factory(UserTransaction)(
|
|
||||||
createUserTransactionContext(userData, user, transaction),
|
|
||||||
).create()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,28 +68,12 @@ const createTransactionContext = (
|
|||||||
memo: string,
|
memo: string,
|
||||||
): TransactionContext => {
|
): TransactionContext => {
|
||||||
return {
|
return {
|
||||||
transactionTypeId: type,
|
typeId: type,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
amount: BigInt(context.amount || 100000),
|
amount: BigInt(context.amount || 100000),
|
||||||
txHash: context.creationTxHash,
|
balance: BigInt(context.amount || 100000),
|
||||||
|
balanceDate: new Date(context.recordDate || Date.now()),
|
||||||
memo,
|
memo,
|
||||||
received: context.recordDate,
|
|
||||||
creationDate: context.creationDate,
|
creationDate: context.creationDate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createUserTransactionContext = (
|
|
||||||
context: UserInterface,
|
|
||||||
user: User,
|
|
||||||
transaction: Transaction,
|
|
||||||
): UserTransactionContext => {
|
|
||||||
return {
|
|
||||||
userId: user.id,
|
|
||||||
transactionId: transaction.id,
|
|
||||||
transactionTypeId: transaction.transactionTypeId,
|
|
||||||
balance: context.amount,
|
|
||||||
balanceDate: context.recordDate,
|
|
||||||
signature: context.signature,
|
|
||||||
pubkey: context.pubKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node run/server.js",
|
"start": "node run/server.js",
|
||||||
|
|||||||
@ -40,67 +40,3 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
|
||||||
.gdd-toaster-title {
|
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
|
||||||
.gdd-toaster-body {
|
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
|
||||||
.gdd-toaster {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
.btn-primary pim {
|
|
||||||
background-color: #5a7b02;
|
|
||||||
border-color: #5e72e4;
|
|
||||||
}
|
|
||||||
a,
|
|
||||||
.copyright {
|
|
||||||
color: #5a7b02;
|
|
||||||
}
|
|
||||||
.font1_2em {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
.font2em {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
.gradido-global-color-text {
|
|
||||||
color: #3d443b;
|
|
||||||
}
|
|
||||||
.gradido-global-color-accent {
|
|
||||||
color: #047006;
|
|
||||||
}
|
|
||||||
.gradido-global-color-6e0a9c9e {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
.gradido-global-color-2d0fb154 {
|
|
||||||
color: #047006;
|
|
||||||
}
|
|
||||||
.gradido-global-color-16efe88c {
|
|
||||||
color: #7ebc55;
|
|
||||||
}
|
|
||||||
.gradido-global-color-1939326 {
|
|
||||||
color: #f6fff6;
|
|
||||||
}
|
|
||||||
.gradido-global-color-9d79fc1 {
|
|
||||||
color: #047006;
|
|
||||||
}
|
|
||||||
.gradido-global-color-6347f4d {
|
|
||||||
color: #5a7b02;
|
|
||||||
}
|
|
||||||
.gradido-global-color-4fbc19a {
|
|
||||||
color: #014034;
|
|
||||||
}
|
|
||||||
.gradido-global-color-d341874 {
|
|
||||||
color: #b6d939;
|
|
||||||
}
|
|
||||||
.gradido-global-color-619d338 {
|
|
||||||
color: #8ebfb1;
|
|
||||||
}
|
|
||||||
.gradido-global-color-44819a9 {
|
|
||||||
color: #026873;
|
|
||||||
}
|
|
||||||
.gradido-global-color-gray {
|
|
||||||
color: #858383;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -3,13 +3,11 @@
|
|||||||
|
|
||||||
@import "~bootstrap/scss/functions";
|
@import "~bootstrap/scss/functions";
|
||||||
|
|
||||||
|
|
||||||
@import "custom/variables";
|
@import "custom/variables";
|
||||||
// @import "~bootstrap/scss/variables"; wird am
|
// @import "~bootstrap/scss/variables"; wird am
|
||||||
// ende der custom/variables angehangen
|
// ende der custom/variables angehangen
|
||||||
|
|
||||||
// Bootstrap (4.5.3) mixins
|
// Bootstrap (4.5.3) mixins
|
||||||
|
|
||||||
@import "~bootstrap/scss/mixins";
|
@import "~bootstrap/scss/mixins";
|
||||||
|
|
||||||
// Bootstrap (4.5.3) components
|
// Bootstrap (4.5.3) components
|
||||||
@ -20,11 +18,18 @@
|
|||||||
@import "~bootstrap/scss/button-group";
|
@import "~bootstrap/scss/button-group";
|
||||||
@import "~bootstrap/scss/buttons";
|
@import "~bootstrap/scss/buttons";
|
||||||
@import "~bootstrap/scss/card";
|
@import "~bootstrap/scss/card";
|
||||||
|
@import "~bootstrap/scss/carousel";
|
||||||
|
@import "~bootstrap/scss/close";
|
||||||
|
@import "~bootstrap/scss/code";
|
||||||
@import "~bootstrap/scss/custom-forms";
|
@import "~bootstrap/scss/custom-forms";
|
||||||
|
@import "~bootstrap/scss/dropdown";
|
||||||
@import "~bootstrap/scss/forms";
|
@import "~bootstrap/scss/forms";
|
||||||
|
@import "~bootstrap/scss/functions";
|
||||||
@import "~bootstrap/scss/grid";
|
@import "~bootstrap/scss/grid";
|
||||||
@import "~bootstrap/scss/input-group";
|
@import "~bootstrap/scss/input-group";
|
||||||
@import "~bootstrap/scss/list-group";
|
@import "~bootstrap/scss/list-group";
|
||||||
|
@import "~bootstrap/scss/mixins";
|
||||||
|
@import "~bootstrap/scss/modal";
|
||||||
@import "~bootstrap/scss/nav";
|
@import "~bootstrap/scss/nav";
|
||||||
@import "~bootstrap/scss/navbar";
|
@import "~bootstrap/scss/navbar";
|
||||||
@import "~bootstrap/scss/pagination";
|
@import "~bootstrap/scss/pagination";
|
||||||
@ -38,7 +43,99 @@
|
|||||||
@import "~bootstrap/scss/type";
|
@import "~bootstrap/scss/type";
|
||||||
@import "~bootstrap/scss/utilities";
|
@import "~bootstrap/scss/utilities";
|
||||||
@import "~bootstrap/scss/variables";
|
@import "~bootstrap/scss/variables";
|
||||||
|
@import "~bootstrap/scss/bootstrap-grid";
|
||||||
|
@import "~bootstrap/scss/bootstrap-reboot";
|
||||||
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
// Bootstrap-vue (2.21.1) scss
|
// Bootstrap-vue (2.21.1) scss
|
||||||
@import '~bootstrap-vue/src/index.scss';
|
@import '~bootstrap-vue/src/index.scss';
|
||||||
|
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #155724;
|
||||||
|
background-color: #d4edda;
|
||||||
|
border-color: #c3e6cb;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #721c24;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.b-toast-danger .toast .toast-header {
|
||||||
|
color: #721c24;
|
||||||
|
background-color: rgba(248,215,218,.85);
|
||||||
|
border-bottom-color: rgba(245,198,203,.85);
|
||||||
|
}
|
||||||
|
.b-toast-danger .toast .toast-body{
|
||||||
|
background-color: rgba(252,237,238,.85);
|
||||||
|
border-color: rgba(245,198,203,.85);
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-toast-success .toast .toast-header {
|
||||||
|
color: #155724;
|
||||||
|
background-color: rgba(212,237,218,.58);
|
||||||
|
border-bottom-color: rgba(195,230,203,.85);
|
||||||
|
}
|
||||||
|
.b-toast-success .toast .toast-body{
|
||||||
|
color: #155724;
|
||||||
|
background-color: rgba(212,237,218,.85);
|
||||||
|
border-bottom-color: rgba(195,230,203,.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.btn-primary pim {
|
||||||
|
background-color: #5a7b02;
|
||||||
|
border-color: #5e72e4;
|
||||||
|
}
|
||||||
|
a,
|
||||||
|
.copyright {
|
||||||
|
color: #5a7b02;
|
||||||
|
}
|
||||||
|
.font1_2em {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.font2em {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.gradido-global-color-text {
|
||||||
|
color: #3d443b;
|
||||||
|
}
|
||||||
|
.gradido-global-color-accent {
|
||||||
|
color: #047006;
|
||||||
|
}
|
||||||
|
.gradido-global-color-6e0a9c9e {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.gradido-global-color-2d0fb154 {
|
||||||
|
color: #047006;
|
||||||
|
}
|
||||||
|
.gradido-global-color-16efe88c {
|
||||||
|
color: #7ebc55;
|
||||||
|
}
|
||||||
|
.gradido-global-color-1939326 {
|
||||||
|
color: #f6fff6;
|
||||||
|
}
|
||||||
|
.gradido-global-color-9d79fc1 {
|
||||||
|
color: #047006;
|
||||||
|
}
|
||||||
|
.gradido-global-color-6347f4d {
|
||||||
|
color: #5a7b02;
|
||||||
|
}
|
||||||
|
.gradido-global-color-4fbc19a {
|
||||||
|
color: #014034;
|
||||||
|
}
|
||||||
|
.gradido-global-color-d341874 {
|
||||||
|
color: #b6d939;
|
||||||
|
}
|
||||||
|
.gradido-global-color-619d338 {
|
||||||
|
color: #8ebfb1;
|
||||||
|
}
|
||||||
|
.gradido-global-color-44819a9 {
|
||||||
|
color: #026873;
|
||||||
|
}
|
||||||
|
.gradido-global-color-gray {
|
||||||
|
color: #858383;
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
{{ decay ? ' − ' + $n(decay.balance, 'decimal') : '' }}
|
{{ decay ? ' − ' + $n(decay.balance, 'decimal') : '' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div v-if="decaytyp === 'new'">
|
<div v-if="decaytyp === 'new' || decaytyp === 'decayLastTransaction'">
|
||||||
<div class="d-flex" v-if="!decay.decayStartBlock">
|
<div class="d-flex" v-if="!decay.decayStartBlock">
|
||||||
<div style="width: 100%" class="text-center pb-3">
|
<div style="width: 100%" class="text-center pb-3">
|
||||||
<b-icon icon="droplet-half" height="12" class="mb-2" />
|
<b-icon icon="droplet-half" height="12" class="mb-2" />
|
||||||
@ -56,17 +56,22 @@
|
|||||||
<div>{{ $t('decay.decay') }}</div>
|
<div>{{ $t('decay.decay') }}</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="6">
|
<b-col cols="6">
|
||||||
<div>− {{ $n(decay.balance, 'decimal') }}</div>
|
<div v-if="decaytyp === 'new'">- {{ $n(decay.balance, 'decimal') }}</div>
|
||||||
|
<div v-if="decaytyp === 'decayLastTransaction'">
|
||||||
|
{{ $n(decay.balance + gddbalance, 'decimal') }} GDD -
|
||||||
|
{{ $n(decay.balance, 'decimal') }} GDD =
|
||||||
|
<b>{{ $n(gddbalance, 'decimal') }} GDD</b>
|
||||||
|
</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<hr class="mt-2 mb-2" />
|
<hr class="mt-2 mb-2" />
|
||||||
<b-row>
|
<b-row v-if="decaytyp === 'new'">
|
||||||
<b-col class="text-center pt-3 pb-2">
|
<b-col class="text-center pt-3 pb-2">
|
||||||
<b>{{ $t('decay.calculation_total') }}</b>
|
<b>{{ $t('decay.calculation_total') }}</b>
|
||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<!-- Type-->
|
<!-- Type-->
|
||||||
<b-row>
|
<b-row v-if="decaytyp === 'new'">
|
||||||
<b-col cols="6" class="text-right">
|
<b-col cols="6" class="text-right">
|
||||||
<div v-if="type === 'send'">{{ $t('decay.sent') }}</div>
|
<div v-if="type === 'send'">{{ $t('decay.sent') }}</div>
|
||||||
<div v-if="type === 'receive'">{{ $t('decay.received') }}</div>
|
<div v-if="type === 'receive'">{{ $t('decay.received') }}</div>
|
||||||
@ -77,7 +82,7 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<!-- Decay-->
|
<!-- Decay-->
|
||||||
<b-row>
|
<b-row v-if="decaytyp === 'new'">
|
||||||
<b-col cols="6" class="text-right">
|
<b-col cols="6" class="text-right">
|
||||||
<div>{{ $t('decay.decay') }}</div>
|
<div>{{ $t('decay.decay') }}</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
@ -86,7 +91,7 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<!-- Total-->
|
<!-- Total-->
|
||||||
<b-row>
|
<b-row v-if="decaytyp === 'new'">
|
||||||
<b-col cols="6" class="text-right">
|
<b-col cols="6" class="text-right">
|
||||||
<div>{{ $t('decay.total') }}</div>
|
<div>{{ $t('decay.total') }}</div>
|
||||||
</b-col>
|
</b-col>
|
||||||
@ -109,6 +114,7 @@
|
|||||||
export default {
|
export default {
|
||||||
name: 'DecayInformation',
|
name: 'DecayInformation',
|
||||||
props: {
|
props: {
|
||||||
|
gddbalance: { type: Number },
|
||||||
balance: { type: Number },
|
balance: { type: Number },
|
||||||
type: { type: String, default: '' },
|
type: { type: String, default: '' },
|
||||||
decay: {
|
decay: {
|
||||||
|
|||||||
@ -31,8 +31,13 @@ describe('Transaction', () => {
|
|||||||
expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy()
|
expect(wrapper.find('div.gdt-transaction-list-item').exists()).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a collapse button', () => {
|
it('has a collapse icon bi-caret-down-square', () => {
|
||||||
expect(wrapper.find('button[type="button"].btn-secondary').text()).toBe('i')
|
expect(wrapper.find('div.gdt-transaction-list-item').findAll('svg').at(1).classes()).toEqual([
|
||||||
|
'bi-caret-down-square',
|
||||||
|
'b-icon',
|
||||||
|
'bi',
|
||||||
|
'text-muted',
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('no valid GDT entry type', () => {
|
describe('no valid GDT entry type', () => {
|
||||||
|
|||||||
@ -9,9 +9,10 @@
|
|||||||
|
|
||||||
<!-- collaps Button -->
|
<!-- collaps Button -->
|
||||||
<div class="text-right" style="width: 96%; position: absolute">
|
<div class="text-right" style="width: 96%; position: absolute">
|
||||||
<b-button class="btn-sm">
|
<b-icon
|
||||||
<b>i</b>
|
:icon="getCollapseState(id) ? 'caret-up-square' : 'caret-down-square'"
|
||||||
</b-button>
|
:class="getCollapseState(id) ? 'text-black' : 'text-muted'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- type -->
|
<!-- type -->
|
||||||
@ -85,6 +86,16 @@ export default {
|
|||||||
gdt: { type: Number },
|
gdt: { type: Number },
|
||||||
id: { type: Number },
|
id: { type: Number },
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
collapseStatus: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getCollapseState(id) {
|
||||||
|
return this.collapseStatus.includes('gdt-collapse-' + id)
|
||||||
|
},
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
collapseId() {
|
collapseId() {
|
||||||
return 'gdt-collapse-' + String(this.id)
|
return 'gdt-collapse-' + String(this.id)
|
||||||
@ -130,5 +141,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$root.$on('bv::collapse::state', (collapseId, isJustShown) => {
|
||||||
|
if (isJustShown) {
|
||||||
|
this.collapseStatus.push(collapseId)
|
||||||
|
} else {
|
||||||
|
this.collapseStatus = this.collapseStatus.filter((id) => id !== collapseId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -78,6 +78,7 @@ export const transactionsQuery = gql`
|
|||||||
decayDuration
|
decayDuration
|
||||||
decayStartBlock
|
decayStartBlock
|
||||||
}
|
}
|
||||||
|
firstTransaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"switch-to-this-community": "zu dieser Gemeinschaft wechseln"
|
"switch-to-this-community": "zu dieser Gemeinschaft wechseln"
|
||||||
},
|
},
|
||||||
"decay": {
|
"decay": {
|
||||||
|
"befor_startblock_transaction": "Diese Transaktion beinhaltet keine Vergänglichkeit.",
|
||||||
"calculation_decay": "Berechnung der Vergänglichkeit",
|
"calculation_decay": "Berechnung der Vergänglichkeit",
|
||||||
"calculation_total": "Berechnung der Gesamtsumme",
|
"calculation_total": "Berechnung der Gesamtsumme",
|
||||||
"created": "Geschöpft",
|
"created": "Geschöpft",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"decayStart": " - Startblock für Vergänglichkeit am: ",
|
"decayStart": " - Startblock für Vergänglichkeit am: ",
|
||||||
"decay_introduced": "Die Vergänglichkeit wurde eingeführt am ",
|
"decay_introduced": "Die Vergänglichkeit wurde eingeführt am ",
|
||||||
"decay_since_last_transaction": "Vergänglichkeit seit der letzten Transaktion",
|
"decay_since_last_transaction": "Vergänglichkeit seit der letzten Transaktion",
|
||||||
|
"first_transaction": "Die erste Transaktion beinhaltet keine Vergänglichkeit.",
|
||||||
"hours": "Stunden",
|
"hours": "Stunden",
|
||||||
"last_transaction": "Letzte Transaktion",
|
"last_transaction": "Letzte Transaktion",
|
||||||
"minutes": "Minuten",
|
"minutes": "Minuten",
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"switch-to-this-community": "Switch to this community"
|
"switch-to-this-community": "Switch to this community"
|
||||||
},
|
},
|
||||||
"decay": {
|
"decay": {
|
||||||
|
"befor_startblock_transaction": "This transaction does not include decay.",
|
||||||
"calculation_decay": "Calculation of Decay",
|
"calculation_decay": "Calculation of Decay",
|
||||||
"calculation_total": "Calculation of the total Amount",
|
"calculation_total": "Calculation of the total Amount",
|
||||||
"created": "Created",
|
"created": "Created",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"decayStart": " - Starting block for decay at: ",
|
"decayStart": " - Starting block for decay at: ",
|
||||||
"decay_introduced": "Decay was introduced on",
|
"decay_introduced": "Decay was introduced on",
|
||||||
"decay_since_last_transaction": "Decay since the last transaction",
|
"decay_since_last_transaction": "Decay since the last transaction",
|
||||||
|
"first_transaction": "The first transaction does not include decay.",
|
||||||
"hours": "Hours",
|
"hours": "Hours",
|
||||||
"last_transaction": "Last transaction:",
|
"last_transaction": "Last transaction:",
|
||||||
"minutes": "Minutes",
|
"minutes": "Minutes",
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const toasters = {
|
|||||||
message = message.replace(/^GraphQL error: /, '')
|
message = message.replace(/^GraphQL error: /, '')
|
||||||
this.$bvToast.toast(message, {
|
this.$bvToast.toast(message, {
|
||||||
autoHideDelay: 5000,
|
autoHideDelay: 5000,
|
||||||
appendToast: false,
|
appendToast: true,
|
||||||
solid: true,
|
solid: true,
|
||||||
toaster: 'b-toaster-top-right',
|
toaster: 'b-toaster-top-right',
|
||||||
headerClass: 'gdd-toaster-title',
|
headerClass: 'gdd-toaster-title',
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
</b-col>
|
</b-col>
|
||||||
</b-row>
|
</b-row>
|
||||||
<gdd-transaction-list
|
<gdd-transaction-list
|
||||||
|
:gddbalance="balance"
|
||||||
:transactions="transactions"
|
:transactions="transactions"
|
||||||
:pageSize="5"
|
:pageSize="5"
|
||||||
:timestamp="timestamp"
|
:timestamp="timestamp"
|
||||||
|
|||||||
@ -117,7 +117,7 @@ describe('GddTransactionList', () => {
|
|||||||
decay: { balance: '1.5' },
|
decay: { balance: '1.5' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
balance: '1.07',
|
balance: 1.07,
|
||||||
type: 'decay',
|
type: 'decay',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -135,12 +135,31 @@ describe('GddTransactionList', () => {
|
|||||||
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(0)
|
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a bi-caret-down-square icon', () => {
|
||||||
|
expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
'bi-caret-down-square',
|
||||||
|
'b-icon',
|
||||||
|
'bi',
|
||||||
|
'text-muted',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// it('transaction is clicked', async () => {
|
||||||
|
// await transaction.trigger('click')
|
||||||
|
// expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
// 'bi-caret-up-square',
|
||||||
|
// 'b-icon',
|
||||||
|
// 'bi',
|
||||||
|
// 'text-muted',
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
|
||||||
it('has a bi-arrow-left-circle icon', () => {
|
it('has a bi-arrow-left-circle icon', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('bi-arrow-left-circle')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('bi-arrow-left-circle')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has text-danger color', () => {
|
it('has text-danger color', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('text-danger')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('text-danger')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a minus operator', () => {
|
it('has a minus operator', () => {
|
||||||
@ -186,12 +205,33 @@ describe('GddTransactionList', () => {
|
|||||||
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(1)
|
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a bi-caret-down-square icon', () => {
|
||||||
|
expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
'bi-caret-down-square',
|
||||||
|
'b-icon',
|
||||||
|
'bi',
|
||||||
|
'text-muted',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// it('transaction is clicked', async () => {
|
||||||
|
// await transaction.trigger('click')
|
||||||
|
// expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
// 'bi-caret-up-square',
|
||||||
|
// 'b-icon',
|
||||||
|
// 'bi',
|
||||||
|
// 'text-muted',
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
|
||||||
it('has a bi-gift icon', () => {
|
it('has a bi-gift icon', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('bi-gift')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('bi-gift')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has gradido-global-color-accent color', () => {
|
it('has gradido-global-color-accent color', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('gradido-global-color-accent')
|
expect(transaction.findAll('svg').at(1).classes()).toContain(
|
||||||
|
'gradido-global-color-accent',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a plus operator', () => {
|
it('has a plus operator', () => {
|
||||||
@ -225,12 +265,33 @@ describe('GddTransactionList', () => {
|
|||||||
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(2)
|
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a bi-caret-down-square icon', () => {
|
||||||
|
expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
'bi-caret-down-square',
|
||||||
|
'b-icon',
|
||||||
|
'bi',
|
||||||
|
'text-muted',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// it('transaction is clicked', async () => {
|
||||||
|
// await transaction.trigger('click')
|
||||||
|
// expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
// 'bi-caret-up-square',
|
||||||
|
// 'b-icon',
|
||||||
|
// 'bi',
|
||||||
|
// 'text-muted',
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
|
||||||
it('has a bi-arrow-right-circle icon', () => {
|
it('has a bi-arrow-right-circle icon', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('bi-arrow-right-circle')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('bi-arrow-right-circle')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has gradido-global-color-accent color', () => {
|
it('has gradido-global-color-accent color', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('gradido-global-color-accent')
|
expect(transaction.findAll('svg').at(1).classes()).toContain(
|
||||||
|
'gradido-global-color-accent',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a plus operator', () => {
|
it('has a plus operator', () => {
|
||||||
@ -276,12 +337,31 @@ describe('GddTransactionList', () => {
|
|||||||
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(3)
|
transaction = wrapper.findAll('div.gdd-transaction-list-item').at(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('has a bi-caret-down-square icon', () => {
|
||||||
|
expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
'bi-caret-down-square',
|
||||||
|
'b-icon',
|
||||||
|
'bi',
|
||||||
|
'text-muted',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// it('transaction is clicked', async () => {
|
||||||
|
// await transaction.trigger('click')
|
||||||
|
// expect(transaction.findAll('svg').at(0).classes()).toEqual([
|
||||||
|
// 'bi-caret-up-square',
|
||||||
|
// 'b-icon',
|
||||||
|
// 'bi',
|
||||||
|
// 'text-muted',
|
||||||
|
// ])
|
||||||
|
// })
|
||||||
|
|
||||||
it('has a bi-droplet-half icon', () => {
|
it('has a bi-droplet-half icon', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('bi-droplet-half')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('bi-droplet-half')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has gradido-global-color-gray color', () => {
|
it('has gradido-global-color-gray color', () => {
|
||||||
expect(transaction.find('svg').classes()).toContain('gradido-global-color-gray')
|
expect(transaction.findAll('svg').at(1).classes()).toContain('gradido-global-color-gray')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a minus operator', () => {
|
it('has a minus operator', () => {
|
||||||
|
|||||||
@ -2,35 +2,44 @@
|
|||||||
<div class="gdd-transaction-list">
|
<div class="gdd-transaction-list">
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<div v-if="!transactions" class="test-no-transactionlist text-right">
|
<div v-if="!transactions" class="test-no-transactionlist text-right">
|
||||||
<b-icon icon="exclamation-triangle" class="mr-2" style="color: red"></b-icon>
|
<b-icon icon="exclamation-triangle" class="mr-2" variant="danger"></b-icon>
|
||||||
<small>
|
<small>
|
||||||
{{ $t('error.no-transactionlist') }}
|
{{ $t('error.no-transactionlist') }}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="transactionCount < 0" class="test-empty-transactionlist text-right">
|
<div v-if="transactionCount < 0" class="test-empty-transactionlist text-right">
|
||||||
<b-icon icon="exclamation-triangle" class="mr-2" style="color: red"></b-icon>
|
<b-icon icon="exclamation-triangle" class="mr-2" variant="danger"></b-icon>
|
||||||
<small>{{ $t('error.empty-transactionlist') }}</small>
|
<small>{{ $t('error.empty-transactionlist') }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="{ decay, transactionId, type, date, balance, name, memo } in transactions"
|
v-for="{
|
||||||
|
decay,
|
||||||
|
transactionId,
|
||||||
|
type,
|
||||||
|
date,
|
||||||
|
balance,
|
||||||
|
name,
|
||||||
|
memo,
|
||||||
|
firstTransaction,
|
||||||
|
decayDuration,
|
||||||
|
decayEnd,
|
||||||
|
decayStart,
|
||||||
|
} in transactions"
|
||||||
:key="transactionId"
|
:key="transactionId"
|
||||||
:style="type === 'decay' ? 'background-color:#f1e0ae3d' : ''"
|
:style="type === 'decay' ? 'background-color:#f1e0ae3d' : ''"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="list-group-item gdd-transaction-list-item"
|
class="list-group-item gdd-transaction-list-item"
|
||||||
|
:class="getCollapseState(transactionId) ? 'bg-secondary' : ''"
|
||||||
v-b-toggle="'decay-' + transactionId"
|
v-b-toggle="'decay-' + transactionId"
|
||||||
>
|
>
|
||||||
<!-- Collaps Button -->
|
<!-- Collaps Button -->
|
||||||
<div
|
<div class="text-right" style="width: 95%; position: absolute">
|
||||||
v-if="type != 'decay' && decay"
|
<b-icon
|
||||||
class="text-right"
|
:icon="getCollapseState(transactionId) ? 'caret-up-square' : 'caret-down-square'"
|
||||||
style="width: 95%; position: absolute"
|
:class="getCollapseState(transactionId) ? 'text-black' : 'text-muted'"
|
||||||
>
|
/>
|
||||||
<b-button class="btn-sm">
|
|
||||||
<b>i</b>
|
|
||||||
</b-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<b-row>
|
<b-row>
|
||||||
<!-- ICON -->
|
<!-- ICON -->
|
||||||
@ -112,9 +121,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Collaps Start -->
|
<!-- Collaps Start -->
|
||||||
|
<!-- v-if="
|
||||||
<b-collapse v-if="type != 'decay' && decay" class="pb-4" :id="'decay-' + transactionId">
|
(type != 'decay' && decay) ||
|
||||||
<div style="border: 0px; background-color: #f1f1f1" class="p-2 pb-4 mb-4">
|
firstTransaction ||
|
||||||
|
(!firstTransaction && decay === null)
|
||||||
|
" -->
|
||||||
|
<b-collapse class="pb-4" :id="'decay-' + transactionId">
|
||||||
|
<div class="pt-4 pb-4 bg-white border border-muted">
|
||||||
<decay-information
|
<decay-information
|
||||||
v-if="decay"
|
v-if="decay"
|
||||||
decaytyp="new"
|
decaytyp="new"
|
||||||
@ -122,6 +135,30 @@
|
|||||||
:decay="decay"
|
:decay="decay"
|
||||||
:type="type"
|
:type="type"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="firstTransaction" class="mt-3 mb-3 text-center">
|
||||||
|
<b>{{ $t('decay.first_transaction') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="type !== 'decay' && !firstTransaction && decay === null"
|
||||||
|
class="mt-3 mb-3 text-center"
|
||||||
|
>
|
||||||
|
<b>{{ $t('decay.befor_startblock_transaction') }}</b>
|
||||||
|
</div>
|
||||||
|
<div v-if="type === 'decay'" class="mt-3 mb-3">
|
||||||
|
<decay-information
|
||||||
|
decaytyp="decayLastTransaction"
|
||||||
|
:gddbalance="gddbalance"
|
||||||
|
:balance="balance"
|
||||||
|
:decay="{
|
||||||
|
balance: balance,
|
||||||
|
decayStart: parseInt(decayStart),
|
||||||
|
decayEnd: parseInt(decayEnd),
|
||||||
|
decayDuration: parseInt(decayDuration),
|
||||||
|
}"
|
||||||
|
:type="type"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</b-collapse>
|
</b-collapse>
|
||||||
|
|
||||||
@ -161,9 +198,11 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
|
collapseStatus: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
gddbalance: { type: Number },
|
||||||
transactions: { default: () => [] },
|
transactions: { default: () => [] },
|
||||||
pageSize: { type: Number, default: 25 },
|
pageSize: { type: Number, default: 25 },
|
||||||
timestamp: { type: Number, default: 0 },
|
timestamp: { type: Number, default: 0 },
|
||||||
@ -191,6 +230,18 @@ export default {
|
|||||||
throwError(msg) {
|
throwError(msg) {
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
},
|
},
|
||||||
|
getCollapseState(transactionId) {
|
||||||
|
return this.collapseStatus.includes('decay-' + transactionId)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$root.$on('bv::collapse::state', (collapseId, isJustShown) => {
|
||||||
|
if (isJustShown) {
|
||||||
|
this.collapseStatus.push(collapseId)
|
||||||
|
} else {
|
||||||
|
this.collapseStatus = this.collapseStatus.filter((id) => id !== collapseId)
|
||||||
|
}
|
||||||
|
})
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentPage() {
|
currentPage() {
|
||||||
|
|||||||
@ -108,7 +108,7 @@
|
|||||||
v-if="showError"
|
v-if="showError"
|
||||||
show
|
show
|
||||||
dismissible
|
dismissible
|
||||||
variant="warning"
|
variant="danger"
|
||||||
@dismissed="closeAlert"
|
@dismissed="closeAlert"
|
||||||
>
|
>
|
||||||
<span class="alert-icon"><i class="ni ni-point"></i></span>
|
<span class="alert-icon"><i class="ni ni-point"></i></span>
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
<p class="tab-tex">{{ $t('transaction.gdd-text') }}</p>
|
<p class="tab-tex">{{ $t('transaction.gdd-text') }}</p>
|
||||||
|
|
||||||
<gdd-transaction-list
|
<gdd-transaction-list
|
||||||
|
:gddbalance="balance"
|
||||||
:timestamp="timestamp"
|
:timestamp="timestamp"
|
||||||
:transactionCount="transactionCount"
|
:transactionCount="transactionCount"
|
||||||
:transactions="transactions"
|
:transactions="transactions"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.6.5",
|
"version": "1.6.6",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user