From f6264879bae3b0372c20a03a8100a0bce21987d2 Mon Sep 17 00:00:00 2001 From: einhorn_b Date: Tue, 31 Oct 2023 16:12:19 +0100 Subject: [PATCH] fix community resolver test and riddle why db had stopped working in test --- dlt-connector/jest.config.js | 1 + dlt-connector/src/data/Account.factory.ts | 1 + dlt-connector/src/data/Account.repository.ts | 4 +- .../src/data/Community.repository.ts | 16 +-- .../src/data/Transaction.repository.ts | 5 +- dlt-connector/src/data/User.repository.ts | 23 +++++ .../data/proto/3_3/ConfirmedTransaction.ts | 1 - .../resolver/CommunityResolver.test.ts | 36 +++---- .../resolver/TransactionsResolver.test.ts | 42 ++++---- dlt-connector/src/graphql/schema.ts | 1 + .../community/AddCommunity.context.ts | 2 +- .../backendToDb/community/Community.role.ts | 2 +- .../community/HomeCommunity.role.ts | 5 +- .../transaction/TransactionRecipe.role.ts | 8 +- dlt-connector/src/manager/KeyManager.ts | 7 +- dlt-connector/src/server/createServer.ts | 3 + dlt-connector/src/typeorm/DBVersion.ts | 28 ------ dlt-connector/src/typeorm/DataSource.ts | 97 +++++++++++++++---- .../Transaction.ts | 20 ++-- .../0003-refactor_transaction_recipe.ts | 24 ++--- 20 files changed, 197 insertions(+), 129 deletions(-) create mode 100644 dlt-connector/src/data/User.repository.ts delete mode 100644 dlt-connector/src/typeorm/DBVersion.ts diff --git a/dlt-connector/jest.config.js b/dlt-connector/jest.config.js index 723aa840b..ae7931cdf 100644 --- a/dlt-connector/jest.config.js +++ b/dlt-connector/jest.config.js @@ -17,6 +17,7 @@ module.exports = { '@arg/(.*)': '/src/graphql/arg/$1', '@controller/(.*)': '/src/controller/$1', '@enum/(.*)': '/src/graphql/enum/$1', + '@model/(.*)': '/src/graphql/model/$1', '@resolver/(.*)': '/src/graphql/resolver/$1', '@input/(.*)': '/src/graphql/input/$1', '@proto/(.*)': '/src/proto/$1', diff --git a/dlt-connector/src/data/Account.factory.ts b/dlt-connector/src/data/Account.factory.ts index 35555a590..14cc68351 100644 --- a/dlt-connector/src/data/Account.factory.ts +++ b/dlt-connector/src/data/Account.factory.ts @@ -21,6 +21,7 @@ export class AccountFactory { account.type = type.valueOf() account.createdAt = createdAt account.balance = new Decimal(0) + account.balanceCreatedAt = new Decimal(0) return account } diff --git a/dlt-connector/src/data/Account.repository.ts b/dlt-connector/src/data/Account.repository.ts index 2a228c261..ea97080fd 100644 --- a/dlt-connector/src/data/Account.repository.ts +++ b/dlt-connector/src/data/Account.repository.ts @@ -8,12 +8,12 @@ export const AccountRepository = getDataSource() .getRepository(Account) .extend({ findAccountsByPublicKeys(publicKeys: Buffer[]): Promise { - return Account.findBy({ derive2Pubkey: In(publicKeys) }) + return this.findBy({ derive2Pubkey: In(publicKeys) }) }, async findAccountByPublicKey(publicKey: Buffer | undefined): Promise { if (!publicKey) return undefined - return (await Account.findOneBy({ derive2Pubkey: Buffer.from(publicKey) })) ?? undefined + return (await this.findOneBy({ derive2Pubkey: Buffer.from(publicKey) })) ?? undefined }, async findAccountByUserIdentifier({ diff --git a/dlt-connector/src/data/Community.repository.ts b/dlt-connector/src/data/Community.repository.ts index 1624404d3..848d7b962 100644 --- a/dlt-connector/src/data/Community.repository.ts +++ b/dlt-connector/src/data/Community.repository.ts @@ -16,14 +16,14 @@ export const CommunityRepository = getDataSource() async isExist(community: CommunityDraft | string): Promise { const iotaTopic = community instanceof CommunityDraft ? iotaTopicFromCommunityUUID(community.uuid) : community - const result = await Community.find({ + const result = await this.find({ where: { iotaTopic }, }) return result.length > 0 }, async findByCommunityArg({ uuid, foreign, confirmed }: CommunityArg): Promise { - return await Community.find({ + return await this.find({ where: { ...(uuid && { iotaTopic: iotaTopicFromCommunityUUID(uuid) }), ...(foreign && { foreign }), @@ -33,15 +33,15 @@ export const CommunityRepository = getDataSource() }, async findByCommunityUuid(communityUuid: string): Promise { - return await Community.findOneBy({ iotaTopic: iotaTopicFromCommunityUUID(communityUuid) }) + return await this.findOneBy({ iotaTopic: iotaTopicFromCommunityUUID(communityUuid) }) }, async findByIotaTopic(iotaTopic: string): Promise { - return await Community.findOneBy({ iotaTopic }) + return await this.findOneBy({ iotaTopic }) }, findCommunitiesByTopics(topics: string[]): Promise { - return Community.findBy({ iotaTopic: In(topics) }) + return this.findBy({ iotaTopic: In(topics) }) }, async getCommunityForUserIdentifier( @@ -51,18 +51,18 @@ export const CommunityRepository = getDataSource() throw new TransactionError(TransactionErrorType.MISSING_PARAMETER, 'community uuid not set') } return ( - (await Community.findOneBy({ + (await this.findOneBy({ iotaTopic: iotaTopicFromCommunityUUID(identifier.communityUuid), })) ?? undefined ) }, findAll(select: FindOptionsSelect): Promise { - return Community.find({ select }) + return this.find({ select }) }, async loadHomeCommunityKeyPair(): Promise { - const community = await Community.findOneOrFail({ + const community = await this.findOneOrFail({ where: { foreign: false }, select: { rootChaincode: true, rootPubkey: true, rootPrivkey: true }, }) diff --git a/dlt-connector/src/data/Transaction.repository.ts b/dlt-connector/src/data/Transaction.repository.ts index 5a2a185bf..8a1eac02e 100644 --- a/dlt-connector/src/data/Transaction.repository.ts +++ b/dlt-connector/src/data/Transaction.repository.ts @@ -43,6 +43,9 @@ export const TransactionRepository = getDataSource() return { existingTransactions, missingMessageIdsHex } }, async removeConfirmedTransaction(transactions: Transaction[]): Promise { - return transactions.filter((transaction: Transaction) => transaction.runningHash.length === 0) + return transactions.filter( + (transaction: Transaction) => + transaction.runningHash === undefined || transaction.runningHash.length === 0, + ) }, }) diff --git a/dlt-connector/src/data/User.repository.ts b/dlt-connector/src/data/User.repository.ts new file mode 100644 index 000000000..abd64ac46 --- /dev/null +++ b/dlt-connector/src/data/User.repository.ts @@ -0,0 +1,23 @@ +import { UserIdentifier } from '@/graphql/input/UserIdentifier' +import { getDataSource } from '@/typeorm/DataSource' +import { Account } from '@entity/Account' +import { User } from '@entity/User' + +export const UserRepository = getDataSource() + .getRepository(User) + .extend({ + async findAccountByUserIdentifier({ + uuid, + accountNr, + }: UserIdentifier): Promise { + const user = await this.findOne({ + where: { gradidoID: uuid, accounts: { derivationIndex: accountNr ?? 1 } }, + relations: { accounts: true }, + }) + if (user && user.accounts?.length === 1) { + const account = user.accounts[0] + account.user = user + return account + } + }, + }) diff --git a/dlt-connector/src/data/proto/3_3/ConfirmedTransaction.ts b/dlt-connector/src/data/proto/3_3/ConfirmedTransaction.ts index ce88a54ae..9c6491128 100644 --- a/dlt-connector/src/data/proto/3_3/ConfirmedTransaction.ts +++ b/dlt-connector/src/data/proto/3_3/ConfirmedTransaction.ts @@ -2,7 +2,6 @@ import { Field, Message } from 'protobufjs' import { GradidoTransaction } from './GradidoTransaction' import { TimestampSeconds } from './TimestampSeconds' import { base64ToBuffer } from '@/utils/typeConverter' -import Long from 'long' /* id will be set by Node server diff --git a/dlt-connector/src/graphql/resolver/CommunityResolver.test.ts b/dlt-connector/src/graphql/resolver/CommunityResolver.test.ts index 7f1f3ea3c..9b106b3f6 100644 --- a/dlt-connector/src/graphql/resolver/CommunityResolver.test.ts +++ b/dlt-connector/src/graphql/resolver/CommunityResolver.test.ts @@ -1,34 +1,35 @@ import 'reflect-metadata' import { ApolloServer } from '@apollo/server' +import { TestDB } from '@test/TestDB' import { createApolloTestServer } from '@test/ApolloServerMock' import assert from 'assert' -import { TestDB } from '@test/TestDB' -import { TransactionResult } from '../model/TransactionResult' +import { TransactionResult } from '@model/TransactionResult' +import { CONFIG } from '@/config' + +CONFIG.IOTA_HOME_COMMUNITY_SEED = 'aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899' + +const con = TestDB.instance + +jest.mock('@typeorm/DataSource', () => ({ + getDataSource: jest.fn(() => TestDB.instance.dbConnect), +})) let apolloTestServer: ApolloServer -jest.mock('@typeorm/DataSource', () => ({ - getDataSource: () => TestDB.instance.dbConnect, -})) - describe('graphql/resolver/CommunityResolver', () => { beforeAll(async () => { + await con.setupTestDB() apolloTestServer = await createApolloTestServer() }) + afterAll(async () => { + await con.teardownTestDB() + }) describe('tests with db', () => { - beforeAll(async () => { - await TestDB.instance.setupTestDB() - // apolloTestServer = await createApolloTestServer() - }) - - afterAll(async () => { - await TestDB.instance.teardownTestDB() - }) - it('test add foreign community', async () => { const response = await apolloTestServer.executeOperation({ - query: 'mutation ($input: CommunityDraft!) { addCommunity(data: $input) {succeed} }', + query: + 'mutation ($input: CommunityDraft!) { addCommunity(data: $input) {succeed, error {message}} }', variables: { input: { uuid: '3d813cbb-37fb-42ba-91df-831e1593ac29', @@ -45,7 +46,8 @@ describe('graphql/resolver/CommunityResolver', () => { it('test add home community', async () => { const response = await apolloTestServer.executeOperation({ - query: 'mutation ($input: CommunityDraft!) { addCommunity(data: $input) {succeed} }', + query: + 'mutation ($input: CommunityDraft!) { addCommunity(data: $input) {succeed, error {message}} }', variables: { input: { uuid: '3d823cad-37fb-41cd-91df-152e1593ac29', diff --git a/dlt-connector/src/graphql/resolver/TransactionsResolver.test.ts b/dlt-connector/src/graphql/resolver/TransactionsResolver.test.ts index 7c02a4306..716d5d235 100644 --- a/dlt-connector/src/graphql/resolver/TransactionsResolver.test.ts +++ b/dlt-connector/src/graphql/resolver/TransactionsResolver.test.ts @@ -1,8 +1,9 @@ import 'reflect-metadata' import { ApolloServer } from '@apollo/server' +import { TestDB } from '@test/TestDB' import { createApolloTestServer } from '@test/ApolloServerMock' import assert from 'assert' -import { TransactionResult } from '../model/TransactionResult' +import { TransactionResult } from '@model/TransactionResult' let apolloTestServer: ApolloServer @@ -14,26 +15,24 @@ jest.mock('@/client/IotaClient', () => { } }) +jest.mock('@typeorm/DataSource', () => ({ + getDataSource: jest.fn(() => TestDB.instance.dbConnect), +})) + describe('Transaction Resolver Test', () => { beforeAll(async () => { apolloTestServer = await createApolloTestServer() + await TestDB.instance.setupTestDB() }) - it('test version query', async () => { - const response = await apolloTestServer.executeOperation({ - query: '{ version }', - }) - // Note the use of Node's assert rather than Jest's expect; if using - // TypeScript, `assert`` will appropriately narrow the type of `body` - // and `expect` will not. - // Source: https://www.apollographql.com/docs/apollo-server/testing/testing - assert(response.body.kind === 'single') - expect(response.body.singleResult.errors).toBeUndefined() - expect(response.body.singleResult.data?.version).toBe('0.1') + + afterAll(async () => { + await TestDB.instance.teardownTestDB() }) + it('test mocked sendTransaction', async () => { const response = await apolloTestServer.executeOperation({ query: - 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, messageId} }', + 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }', variables: { input: { senderUser: { @@ -45,21 +44,20 @@ describe('Transaction Resolver Test', () => { type: 'SEND', amount: '10', createdAt: '2012-04-17T17:12:00Z', + backendTransactionId: 1, }, }, }) assert(response.body.kind === 'single') expect(response.body.singleResult.errors).toBeUndefined() const transactionResult = response.body.singleResult.data?.sendTransaction as TransactionResult - expect(transactionResult.messageId).toBe( - '5498130bc3918e1a7143969ce05805502417e3e1bd596d3c44d6a0adeea22710', - ) + expect(transactionResult.succeed).toBe(true) }) it('test mocked sendTransaction invalid transactionType ', async () => { const response = await apolloTestServer.executeOperation({ query: - 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, messageId} }', + 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }', variables: { input: { senderUser: { @@ -71,6 +69,7 @@ describe('Transaction Resolver Test', () => { type: 'INVALID', amount: '10', createdAt: '2012-04-17T17:12:00Z', + backendTransactionId: 1, }, }, }) @@ -88,7 +87,7 @@ describe('Transaction Resolver Test', () => { it('test mocked sendTransaction invalid amount ', async () => { const response = await apolloTestServer.executeOperation({ query: - 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, messageId} }', + 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }', variables: { input: { senderUser: { @@ -100,6 +99,7 @@ describe('Transaction Resolver Test', () => { type: 'SEND', amount: 'no number', createdAt: '2012-04-17T17:12:00Z', + backendTransactionId: 1, }, }, }) @@ -117,7 +117,7 @@ describe('Transaction Resolver Test', () => { it('test mocked sendTransaction invalid created date ', async () => { const response = await apolloTestServer.executeOperation({ query: - 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, messageId} }', + 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }', variables: { input: { senderUser: { @@ -129,6 +129,7 @@ describe('Transaction Resolver Test', () => { type: 'SEND', amount: '10', createdAt: 'not valid', + backendTransactionId: 1, }, }, }) @@ -156,7 +157,7 @@ describe('Transaction Resolver Test', () => { it('test mocked sendTransaction missing creationDate for contribution', async () => { const response = await apolloTestServer.executeOperation({ query: - 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, messageId} }', + 'mutation ($input: TransactionDraft!) { sendTransaction(data: $input) {error {type, message}, succeed} }', variables: { input: { senderUser: { @@ -168,6 +169,7 @@ describe('Transaction Resolver Test', () => { type: 'CREATION', amount: '10', createdAt: '2012-04-17T17:12:00Z', + backendTransactionId: 1, }, }, }) diff --git a/dlt-connector/src/graphql/schema.ts b/dlt-connector/src/graphql/schema.ts index fc9c26919..ac3119d1a 100755 --- a/dlt-connector/src/graphql/schema.ts +++ b/dlt-connector/src/graphql/schema.ts @@ -10,6 +10,7 @@ export const schema = async (): Promise => { return buildSchema({ resolvers: [TransactionResolver, CommunityResolver], scalarsMap: [{ type: Decimal, scalar: DecimalScalar }], + emitSchemaFile: true, validate: { validationError: { target: false }, skipMissingProperties: true, diff --git a/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts b/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts index f2518d45a..ff46de4d3 100644 --- a/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts +++ b/dlt-connector/src/interactions/backendToDb/community/AddCommunity.context.ts @@ -24,7 +24,7 @@ export class AddCommunityContext { } public async run(): Promise { - this.communityRole.create(this.communityDraft, this.iotaTopic) + await this.communityRole.create(this.communityDraft, this.iotaTopic) await this.communityRole.store() } } diff --git a/dlt-connector/src/interactions/backendToDb/community/Community.role.ts b/dlt-connector/src/interactions/backendToDb/community/Community.role.ts index f2fa7d0ac..f08dc25a0 100644 --- a/dlt-connector/src/interactions/backendToDb/community/Community.role.ts +++ b/dlt-connector/src/interactions/backendToDb/community/Community.role.ts @@ -10,7 +10,7 @@ export abstract class CommunityRole { this.self = Community.create() } - public create(communityDraft: CommunityDraft, topic: string): void { + public async create(communityDraft: CommunityDraft, topic: string): Promise { this.self.iotaTopic = topic this.self.createdAt = new Date(communityDraft.createdAt) this.self.foreign = communityDraft.foreign diff --git a/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts b/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts index f78abb8f6..a4438f780 100644 --- a/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts +++ b/dlt-connector/src/interactions/backendToDb/community/HomeCommunity.role.ts @@ -35,12 +35,15 @@ export class HomeCommunityRole extends CommunityRole { public async store(): Promise { try { + console.log('store transaction: %s', JSON.stringify(this.transactionRecipe, null, 2)) return await getDataSource().transaction(async (transactionalEntityManager) => { + const community = await transactionalEntityManager.save(this.self) await transactionalEntityManager.save(this.transactionRecipe) - return await transactionalEntityManager.save(this.self) + return community }) } catch (error) { logger.error('error saving home community into db: %s', error) + console.log(error) throw new TransactionError( TransactionErrorType.DB_ERROR, 'error saving home community into db', diff --git a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts index 4107a37b1..9fb53f3be 100644 --- a/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts +++ b/dlt-connector/src/interactions/backendToDb/transaction/TransactionRecipe.role.ts @@ -1,6 +1,7 @@ import { AccountRepository } from '@/data/Account.repository' import { KeyPair } from '@/data/KeyPair' import { TransactionBuilder } from '@/data/Transaction.builder' +import { UserRepository } from '@/data/User.repository' import { TransactionBodyBuilder } from '@/data/proto/TransactionBody.builder' import { TransactionErrorType } from '@/graphql/enum/TransactionErrorType' import { TransactionDraft } from '@/graphql/input/TransactionDraft' @@ -10,7 +11,8 @@ import { Transaction } from '@entity/Transaction' export class TransactionRecipeRole { protected transactionBuilder: TransactionBuilder - construct() { + + public constructor() { this.transactionBuilder = new TransactionBuilder() } @@ -20,14 +22,14 @@ export class TransactionRecipeRole { // loading signing and recipient account // TODO: look for ways to use only one db call for both - const signingAccount = await AccountRepository.findAccountByUserIdentifier(senderUser) + const signingAccount = await UserRepository.findAccountByUserIdentifier(senderUser) if (!signingAccount) { throw new TransactionError( TransactionErrorType.NOT_FOUND, "couldn't found sender user account in db", ) } - const recipientAccount = await AccountRepository.findAccountByUserIdentifier(recipientUser) + const recipientAccount = await UserRepository.findAccountByUserIdentifier(recipientUser) if (!recipientAccount) { throw new TransactionError( TransactionErrorType.NOT_FOUND, diff --git a/dlt-connector/src/manager/KeyManager.ts b/dlt-connector/src/manager/KeyManager.ts index df1e61b33..2e8b615aa 100644 --- a/dlt-connector/src/manager/KeyManager.ts +++ b/dlt-connector/src/manager/KeyManager.ts @@ -3,12 +3,7 @@ import { randombytes_buf } from 'sodium-native' import { CONFIG } from '../config' import { entropyToMnemonic, mnemonicToSeedSync } from 'bip39' // https://www.npmjs.com/package/bip32-ed25519?activeTab=code -import { - generateFromSeed, - derivePrivate, - sign as ed25519Sign, - verify as ed25519Verify, -} from 'bip32-ed25519' +import { generateFromSeed, derivePrivate, sign as ed25519Sign } from 'bip32-ed25519' import { logger } from '@/server/logger' import { LogError } from '@/server/LogError' import { KeyPair } from '@/data/KeyPair' diff --git a/dlt-connector/src/server/createServer.ts b/dlt-connector/src/server/createServer.ts index 00ba0a912..f3a3b1de7 100755 --- a/dlt-connector/src/server/createServer.ts +++ b/dlt-connector/src/server/createServer.ts @@ -11,6 +11,7 @@ import { logger as dltLogger } from './logger' import { Logger } from 'log4js' import cors from 'cors' import bodyParser from 'body-parser' +import { Connection } from '@/typeorm/DataSource' type ServerDef = { apollo: ApolloServer; app: Express } @@ -27,6 +28,8 @@ const createServer = async ( logger.addContext('user', 'unknown') logger.debug('createServer...') + // connect to db and test db version + await Connection.getInstance().init() // Express Server const app = express() diff --git a/dlt-connector/src/typeorm/DBVersion.ts b/dlt-connector/src/typeorm/DBVersion.ts deleted file mode 100644 index 14da39368..000000000 --- a/dlt-connector/src/typeorm/DBVersion.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Migration } from '@entity/Migration' - -import { logger } from '@/server/logger' - -const getDBVersion = async (): Promise => { - try { - const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 }) - return dbVersion ? dbVersion.fileName : null - } catch (error) { - logger.error(error) - return null - } -} - -const checkDBVersion = async (DB_VERSION: string): Promise => { - const dbVersion = await getDBVersion() - if (!dbVersion?.includes(DB_VERSION)) { - logger.error( - `Wrong database version detected - the backend requires '${DB_VERSION}' but found '${ - dbVersion ?? 'None' - }`, - ) - return false - } - return true -} - -export { checkDBVersion, getDBVersion } diff --git a/dlt-connector/src/typeorm/DataSource.ts b/dlt-connector/src/typeorm/DataSource.ts index eafa977aa..1dab441c2 100644 --- a/dlt-connector/src/typeorm/DataSource.ts +++ b/dlt-connector/src/typeorm/DataSource.ts @@ -4,23 +4,84 @@ import { DataSource as DBDataSource, FileLogger } from '@dbTools/typeorm' import { entities } from '@entity/index' import { CONFIG } from '@/config' +import { logger } from '@/server/logger' +import { Migration } from '@entity/Migration' +import { LogError } from '@/server/LogError' -const DataSource = new DBDataSource({ - type: 'mysql', - host: CONFIG.DB_HOST, - port: CONFIG.DB_PORT, - username: CONFIG.DB_USER, - password: CONFIG.DB_PASSWORD, - database: CONFIG.DB_DATABASE, - entities, - synchronize: false, - logging: true, - logger: new FileLogger('all', { - logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, - }), - extra: { - charset: 'utf8mb4_unicode_ci', - }, -}) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class Connection { + // eslint-disable-next-line no-use-before-define + private static instance: Connection + private connection: DBDataSource -export const getDataSource = () => DataSource + /** + * The Singleton's constructor should always be private to prevent direct + * construction calls with the `new` operator. + */ + // eslint-disable-next-line no-useless-constructor, @typescript-eslint/no-empty-function + private constructor() { + this.connection = new DBDataSource({ + type: 'mysql', + host: CONFIG.DB_HOST, + port: CONFIG.DB_PORT, + username: CONFIG.DB_USER, + password: CONFIG.DB_PASSWORD, + database: CONFIG.DB_DATABASE, + entities, + synchronize: false, + logging: true, + logger: new FileLogger('all', { + logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, + }), + extra: { + charset: 'utf8mb4_unicode_ci', + }, + }) + } + + /** + * The static method that controls the access to the singleton instance. + * + * This implementation let you subclass the Singleton class while keeping + * just one instance of each subclass around. + */ + public static getInstance(): Connection { + if (!Connection.instance) { + Connection.instance = new Connection() + } + return Connection.instance + } + + public getDataSource(): DBDataSource { + console.log('production getDataSource called!') + return this.connection + } + + public async init(): Promise { + await this.connection.initialize() + try { + await Connection.getInstance() + } catch (error) { + // try and catch for logging + logger.fatal(`Couldn't open connection to database!`) + throw error + } + + // check for correct database version + await this.checkDBVersion(CONFIG.DB_VERSION) + } + + async checkDBVersion(DB_VERSION: string): Promise { + const dbVersion = await Migration.findOneOrFail({ order: { version: 'DESC' } }) + // return dbVersion ? dbVersion.fileName : null + if (!dbVersion.fileName.includes(DB_VERSION)) { + throw new LogError( + `Wrong database version detected - the backend requires '${DB_VERSION}' but found '${ + dbVersion ?? 'None' + }`, + ) + } + } +} + +export const getDataSource = () => Connection.getInstance().getDataSource() diff --git a/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts b/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts index ac48f07b6..f1167043f 100644 --- a/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts +++ b/dlt-database/entity/0003-refactor_transaction_recipe/Transaction.ts @@ -76,9 +76,10 @@ export class Transaction extends BaseEntity { type: 'decimal', precision: 40, scale: 20, + nullable: true, transformer: DecimalTransformer, }) - accountBalanceCreatedAt: Decimal + accountBalanceCreatedAt?: Decimal @Column({ type: 'tinyint' }) type: number @@ -95,26 +96,25 @@ export class Transaction extends BaseEntity { @Column({ name: 'protocol_version', type: 'varchar', length: 255, default: '1' }) protocolVersion: string - @Column({ type: 'bigint' }) - nr: number + @Column({ type: 'bigint', nullable: true }) + nr?: number - @Column({ name: 'running_hash', type: 'binary', length: 48 }) - runningHash: Buffer + @Column({ name: 'running_hash', type: 'binary', length: 48, nullable: true }) + runningHash?: Buffer @Column({ name: 'account_balance', type: 'decimal', precision: 40, scale: 20, - nullable: false, - default: 0, + nullable: true, transformer: DecimalTransformer, }) - accountBalanceConfirmedAt: Decimal + accountBalanceConfirmedAt?: Decimal @Column({ name: 'iota_milestone', type: 'bigint', nullable: true }) iotaMilestone?: number - @Column({ name: 'confirmed_at', type: 'datetime' }) - confirmedAt: Date + @Column({ name: 'confirmed_at', type: 'datetime', nullable: true }) + confirmedAt?: Date } diff --git a/dlt-database/migrations/0003-refactor_transaction_recipe.ts b/dlt-database/migrations/0003-refactor_transaction_recipe.ts index 577b24938..5a6ba9a78 100644 --- a/dlt-database/migrations/0003-refactor_transaction_recipe.ts +++ b/dlt-database/migrations/0003-refactor_transaction_recipe.ts @@ -19,25 +19,25 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis await queryFn( `CREATE TABLE \`transactions\` ( \`id\` bigint unsigned NOT NULL AUTO_INCREMENT, - \`iota_message_id\` varbinary(32) DEFAULT NULL, - \`backend_transaction_id\` bigint unsigned DEFAULT NULL, - \`paring_transaction_id\` bigint unsigned DEFAULT NULL, - \`signing_account_id\` int unsigned DEFAULT NULL, - \`recipient_account_id\` int unsigned DEFAULT NULL, + \`iota_message_id\` varbinary(32) NULL DEFAULT NULL, + \`backend_transaction_id\` bigint unsigned NULL DEFAULT NULL, + \`paring_transaction_id\` bigint unsigned NULL DEFAULT NULL, + \`signing_account_id\` int unsigned NULL DEFAULT NULL, + \`recipient_account_id\` int unsigned NULL DEFAULT NULL, \`sender_community_id\` int unsigned NOT NULL, - \`recipient_community_id\` int unsigned DEFAULT NULL, - \`amount\` decimal(40, 20) DEFAULT NULL, + \`recipient_community_id\` int unsigned NULL DEFAULT NULL, + \`amount\` decimal(40, 20) NULL DEFAULT NULL, \`account_balance_created_at\` decimal(40, 20) NOT NULL, \`type\` tinyint NOT NULL, \`created_at\` datetime(3) NOT NULL, \`body_bytes\` blob NOT NULL, \`signature\` varbinary(64) NOT NULL, \`protocol_version\` varchar(255) NOT NULL DEFAULT '1', - \`nr\` bigint NOT NULL, - \`running_hash\` varbinary(48) NOT NULL, - \`account_balance\` decimal(40, 20) NOT NULL DEFAULT 0.00000000000000000000, - \`iota_milestone\` bigint DEFAULT NULL, - \`confirmed_at\` datetime NOT NULL, + \`nr\` bigint NULL DEFAULT NULL, + \`running_hash\` varbinary(48) NULL DEFAULT NULL, + \`account_balance\` decimal(40, 20) NULL DEFAULT 0.00000000000000000000, + \`iota_milestone\` bigint NULL DEFAULT NULL, + \`confirmed_at\` datetime NULL DEFAULT NULL, PRIMARY KEY (\`id\`), UNIQUE KEY \`signature\` (\`signature\`), FOREIGN KEY (\`signing_account_id\`) REFERENCES accounts(id),