From fb63588361e604efac07e76b5e1cd6f881836fab Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sat, 7 Jun 2025 08:01:48 +0200 Subject: [PATCH] use new appDatabase class everywhere --- backend/package.json | 2 +- .../dltConnector/DltConnectorClient.test.ts | 8 +- backend/src/apis/gms/ExportUsers.ts | 19 +--- backend/src/apis/humhub/ExportUsers.ts | 21 +---- backend/src/config/index.ts | 19 ---- backend/src/config/schema.ts | 18 ---- backend/src/emails/sendEmailVariants.test.ts | 8 +- .../federation/validateCommunities.test.ts | 8 +- .../resolver/CommunityResolver.test.ts | 8 +- .../resolver/ContributionLinkResolver.test.ts | 8 +- .../ContributionMessageResolver.test.ts | 8 +- .../resolver/ContributionMessageResolver.ts | 23 ++--- .../resolver/ContributionResolver.test.ts | 9 +- .../graphql/resolver/ContributionResolver.ts | 10 ++- .../graphql/resolver/EmailOptinCodes.test.ts | 8 +- .../resolver/KlicktippResolver.test.ts | 2 +- .../graphql/resolver/StatisticsResolver.ts | 11 +-- .../resolver/TransactionLinkResolver.test.ts | 8 +- .../resolver/TransactionLinkResolver.ts | 4 +- .../graphql/resolver/TransactionResolver.ts | 7 +- .../src/graphql/resolver/UserResolver.test.ts | 8 +- backend/src/graphql/resolver/UserResolver.ts | 10 ++- .../src/graphql/resolver/semaphore.test.ts | 8 +- .../src/graphql/resolver/util/communities.ts | 12 ++- .../graphql/resolver/util/creations.test.ts | 8 +- .../src/graphql/resolver/util/creations.ts | 6 +- .../resolver/util/findContributions.ts | 12 +-- .../util/findUserByIdentifiers.test.ts | 8 +- .../sendTransactionsToDltConnector.test.ts | 8 +- .../util/settlePendingSenderTransaction.ts | 6 +- .../resolver/util/transactionLinkSummary.ts | 7 +- .../resolver/util/validateAlias.test.ts | 8 +- backend/src/seeds/index.ts | 2 +- backend/src/server/createServer.ts | 24 +++-- backend/src/typeorm/DBVersion.ts | 55 ------------ backend/src/typeorm/connection.ts | 55 ------------ backend/src/util/executeKlicktipp.ts | 6 +- backend/src/util/klicktipp.test.ts | 8 +- bun.lock | 23 ++++- config-schema/package.json | 4 +- config-schema/src/DatabaseConfigSchema.ts | 87 +++++++++++++++++++ config-schema/src/commonSchema.ts | 83 ------------------ config-schema/src/index.ts | 36 +------- config-schema/src/validate.ts | 34 ++++++++ database/esbuild.config.ts | 2 +- database/migration/clear.ts | 5 +- database/migration/index.ts | 7 +- database/migration/prepare.ts | 11 +-- database/package.json | 5 +- database/src/AppDatabase.ts | 19 ++-- database/src/config/const.ts | 2 + database/src/config/index.ts | 29 +------ database/src/index.ts | 65 +++++++++++++- database/src/logging/CommunityLogging.view.ts | 2 +- .../src/logging/ContributionLogging.view.ts | 2 +- .../ContributionMessageLogging.view.ts | 2 +- .../src/logging/DltTransactionLogging.view.ts | 2 +- .../logging/FederatedCommunityLogging.view.ts | 2 +- .../logging/PendingTransactionLogging.view.ts | 2 +- .../src/logging/TransactionLogging.view.ts | 2 +- .../src/logging/UserContactLogging.view.ts | 2 +- database/src/logging/UserLogging.view.ts | 2 +- database/src/logging/UserRoleLogging.view.ts | 2 +- database/src/logging/index.ts | 4 +- database/tsconfig.json | 10 +-- dht-node/src/config/index.ts | 19 ---- dht-node/src/config/schema.ts | 18 ---- dht-node/src/index.ts | 8 +- dht-node/src/typeorm/DBVersion.ts | 55 ------------ dht-node/src/typeorm/connection.ts | 33 ------- dht-node/test/helpers.ts | 8 +- federation/package.json | 2 +- federation/src/config/index.ts | 17 ---- federation/src/config/schema.ts | 18 ---- .../PublicCommunityInfoResolver.test.ts | 4 +- .../1_0/resolver/SendCoinsResolver.test.ts | 4 +- .../api/1_0/resolver/SendCoinsResolver.ts | 2 +- .../util/revertSettledReceiveTransaction.ts | 6 +- .../util/settlePendingReceiveTransaction.ts | 6 +- federation/src/server/createServer.ts | 19 ++-- federation/src/typeorm/DBVersion.ts | 53 ----------- federation/src/typeorm/connection.ts | 35 -------- federation/test/helpers.ts | 7 -- yarn.lock | 54 +++++++++++- 84 files changed, 490 insertions(+), 784 deletions(-) delete mode 100644 backend/src/typeorm/DBVersion.ts delete mode 100644 backend/src/typeorm/connection.ts create mode 100644 config-schema/src/DatabaseConfigSchema.ts create mode 100644 config-schema/src/validate.ts create mode 100644 database/src/config/const.ts delete mode 100644 dht-node/src/typeorm/DBVersion.ts delete mode 100644 dht-node/src/typeorm/connection.ts delete mode 100644 federation/src/typeorm/DBVersion.ts delete mode 100644 federation/src/typeorm/connection.ts diff --git a/backend/package.json b/backend/package.json index 5d8f546b7..1ab660238 100644 --- a/backend/package.json +++ b/backend/package.json @@ -90,7 +90,7 @@ "tsconfig-paths": "^4.1.1", "type-graphql": "^1.1.1", "typed-rest-client": "^1.8.11", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "typescript": "^4.9.5", "uuid": "^8.3.2", "workerpool": "^9.2.0", diff --git a/backend/src/apis/dltConnector/DltConnectorClient.test.ts b/backend/src/apis/dltConnector/DltConnectorClient.test.ts index 4dcc991ec..0367c6350 100644 --- a/backend/src/apis/dltConnector/DltConnectorClient.test.ts +++ b/backend/src/apis/dltConnector/DltConnectorClient.test.ts @@ -1,6 +1,6 @@ import { Transaction as DbTransaction } from 'database' import { Decimal } from 'decimal.js-light' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, testEnvironment } from '@test/helpers' @@ -10,10 +10,10 @@ import { backendLogger as logger } from '@/server/logger' import { DltConnectorClient } from './DltConnectorClient' -let con: Connection +let con: DataSource let testEnv: { - con: Connection + con: DataSource } // Mock the GraphQLClient @@ -83,7 +83,7 @@ describe('transmitTransaction', () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) const transaction = new DbTransaction() diff --git a/backend/src/apis/gms/ExportUsers.ts b/backend/src/apis/gms/ExportUsers.ts index 29e6dc6fe..b5b79b479 100644 --- a/backend/src/apis/gms/ExportUsers.ts +++ b/backend/src/apis/gms/ExportUsers.ts @@ -8,8 +8,7 @@ import { getHomeCommunity } from '@/graphql/resolver/util/communities' import { sendUserToGms } from '@/graphql/resolver/util/sendUserToGms' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' -import { checkDBVersion } from '@/typeorm/DBVersion' -import { Connection } from '@/typeorm/connection' +import { AppDatabase } from 'database' CONFIG.EMAIL = false // use force to copy over all user even if gmsRegistered is set to true @@ -17,18 +16,8 @@ const forceMode = process.argv.includes('--force') async function main() { // open mysql connection - const con = await Connection.getInstance() - if (!con?.isConnected) { - logger.fatal(`Couldn't open connection to database!`) - throw new Error(`Fatal: Couldn't open connection to database`) - } - - // check for correct database version - const dbVersion = await checkDBVersion(CONFIG.DB_VERSION) - if (!dbVersion) { - logger.fatal('Fatal: Database Version incorrect') - throw new Error('Fatal: Database Version incorrect') - } + const con = AppDatabase.getInstance() + await con.init() const homeCom = await getHomeCommunity() if (homeCom.gmsApiKey === null) { @@ -77,7 +66,7 @@ async function main() { } logger.info('##gms## publishing all local users successful...') - await con.destroy() + await con.close() } main().catch((e) => { diff --git a/backend/src/apis/humhub/ExportUsers.ts b/backend/src/apis/humhub/ExportUsers.ts index b616cbb2e..908e3f424 100644 --- a/backend/src/apis/humhub/ExportUsers.ts +++ b/backend/src/apis/humhub/ExportUsers.ts @@ -1,11 +1,8 @@ -import { User } from 'database' +import { AppDatabase, User } from 'database' import { IsNull, Not } from 'typeorm' -import { CONFIG } from '@/config' import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' -import { checkDBVersion } from '@/typeorm/DBVersion' -import { Connection } from '@/typeorm/connection' import { HumHubClient } from './HumHubClient' import { GetUser } from './model/GetUser' @@ -66,18 +63,8 @@ async function main() { const start = new Date().getTime() // open mysql connection - const con = await Connection.getInstance() - if (!con?.isConnected) { - logger.fatal(`Couldn't open connection to database!`) - throw new Error(`Fatal: Couldn't open connection to database`) - } - - // check for correct database version - const dbVersion = await checkDBVersion(CONFIG.DB_VERSION) - if (!dbVersion) { - logger.fatal('Fatal: Database Version incorrect') - throw new Error('Fatal: Database Version incorrect') - } + const con = AppDatabase.getInstance() + await con.init() let userCount = 0 let page = 0 @@ -114,7 +101,7 @@ async function main() { } while (userCount === USER_BULK_SIZE) process.stdout.write('\n') - await con.destroy() + await con.close() const elapsed = new Date().getTime() - start logger.info('export user to humhub, statistics:', { timeSeconds: elapsed / 1000.0, diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 2eba8c8c1..f29f5ed4b 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,7 +1,6 @@ // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env) import { validate } from 'config-schema' -import { latestDbVersion } from 'database' import { Decimal } from 'decimal.js-light' import dotenv from 'dotenv' @@ -15,8 +14,6 @@ Decimal.set({ }) const constants = { - // DB_VERSION: '0087-add_index_on_user_roles', - DB_VERSION: latestDbVersion, DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', } @@ -34,21 +31,6 @@ const server = { LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', } -const database = { - DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT - ? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT) - : 15, - DB_CONNECT_RETRY_DELAY_MS: process.env.DB_CONNECT_RETRY_DELAY_MS - ? Number.parseInt(process.env.DB_CONNECT_RETRY_DELAY_MS) - : 500, - DB_HOST: process.env.DB_HOST ?? 'localhost', - DB_PORT: process.env.DB_PORT ? Number.parseInt(process.env.DB_PORT) : 3306, - DB_USER: process.env.DB_USER ?? 'root', - DB_PASSWORD: process.env.DB_PASSWORD ?? '', - DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community', - TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.backend.log', -} - const klicktipp = { KLICKTIPP: process.env.KLICKTIPP === 'true' || false, KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL ?? 'https://api.klicktipp.com', @@ -163,7 +145,6 @@ const openai = { export const CONFIG = { ...constants, ...server, - ...database, ...klicktipp, ...dltConnector, ...community, diff --git a/backend/src/config/schema.ts b/backend/src/config/schema.ts index 4bfe2a551..f805d02a7 100644 --- a/backend/src/config/schema.ts +++ b/backend/src/config/schema.ts @@ -3,14 +3,6 @@ import { COMMUNITY_NAME, COMMUNITY_SUPPORT_MAIL, COMMUNITY_URL, - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, - DB_DATABASE, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, DECAY_START_TIME, GDT_ACTIVE, GDT_API_URL, @@ -25,7 +17,6 @@ import { NODE_ENV, OPENAI_ACTIVE, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, } from 'config-schema' import Joi from 'joi' @@ -34,14 +25,6 @@ export const schema = Joi.object({ COMMUNITY_URL, COMMUNITY_DESCRIPTION, COMMUNITY_SUPPORT_MAIL, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, - DB_DATABASE, - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, DECAY_START_TIME, GDT_API_URL, GDT_ACTIVE, @@ -56,7 +39,6 @@ export const schema = Joi.object({ NODE_ENV, OPENAI_ACTIVE, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, COMMUNITY_REDEEM_URL: Joi.string() .uri({ scheme: ['http', 'https'] }) diff --git a/backend/src/emails/sendEmailVariants.test.ts b/backend/src/emails/sendEmailVariants.test.ts index 1afa49eeb..2c2885ebf 100644 --- a/backend/src/emails/sendEmailVariants.test.ts +++ b/backend/src/emails/sendEmailVariants.test.ts @@ -1,6 +1,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Decimal } from 'decimal.js-light' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { testEnvironment } from '@test/helpers' import { i18n as localization, logger } from '@test/testSetup' @@ -45,11 +45,11 @@ jest.mock('nodemailer', () => { } }) -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -58,7 +58,7 @@ beforeAll(async () => { }) afterAll(async () => { - await con.close() + await con.destroy() }) const sendEmailTranslatedSpy = jest.spyOn(sendEmailTranslatedApi, 'sendEmailTranslated') diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index 9e5120578..731ca72a1 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -2,18 +2,18 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLClient } from 'graphql-request' import { Response } from 'graphql-request/dist/types' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' import { validateCommunities } from './validateCommunities' -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -24,7 +24,7 @@ beforeAll(async () => { afterAll(async () => { // await cleanDB() - await con.close() + await con.destroy() }) describe('validate Communities', () => { diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index 2400fdc75..f636bf53d 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -1,7 +1,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLError } from 'graphql/error/GraphQLError' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { cleanDB, testEnvironment } from '@test/helpers' @@ -25,12 +25,12 @@ jest.mock('@/password/EncryptorUtils') // to do: We need a setup for the tests that closes the connection let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } const peterLoginData = { @@ -49,7 +49,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) // real valid ed25519 key pairs diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts index abcf231be..9c9bdfa55 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts @@ -2,7 +2,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { ContributionLink as DbContributionLink, Event as DbEvent } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { logger } from '@test/testSetup' @@ -23,11 +23,11 @@ jest.mock('@/password/EncryptorUtils') let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -42,7 +42,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('Contribution Links', () => { diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index f43639101..00b7032dc 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -1,7 +1,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Contribution as DbContribution, Event as DbEvent } from 'database' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { ContributionStatus } from '@enum/ContributionStatus' import { cleanDB, resetToken, testEnvironment } from '@test/helpers' @@ -34,11 +34,11 @@ jest.mock('@/emails/sendEmailVariants', () => { }) let mutate: ApolloServerTestClient['mutate'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } let result: any @@ -51,7 +51,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('ContributionMessageResolver', () => { diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.ts b/backend/src/graphql/resolver/ContributionMessageResolver.ts index a1701f048..c1d25bd19 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.ts @@ -1,10 +1,11 @@ import { + AppDatabase, Contribution as DbContribution, ContributionMessage as DbContributionMessage, User as DbUser, } from 'database' import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql' -import { EntityManager, FindOptionsRelations, getConnection } from 'typeorm' +import { EntityManager, FindOptionsRelations } from 'typeorm' import { ContributionMessageArgs } from '@arg/ContributionMessageArgs' import { Paginated } from '@arg/Paginated' @@ -25,6 +26,8 @@ import { backendLogger as logger } from '@/server/logger' import { findContributionMessages } from './util/findContributionMessages' +const db = AppDatabase.getInstance() + @Resolver() export class ContributionMessageResolver { @Authorized([RIGHTS.CREATE_CONTRIBUTION_MESSAGE]) @@ -43,9 +46,9 @@ export class ContributionMessageResolver { let finalContributionMessage: DbContributionMessage | undefined try { - await getConnection().transaction( - 'REPEATABLE READ', - async (transactionalEntityManager: EntityManager) => { + await db + .getDataSource() + .transaction('REPEATABLE READ', async (transactionalEntityManager: EntityManager) => { const { contribution, contributionMessage, contributionChanged } = await updateUnconfirmedContributionContext.run(transactionalEntityManager) @@ -62,8 +65,7 @@ export class ContributionMessageResolver { finalContribution = contribution finalContributionMessage = contributionMessage - }, - ) + }) } catch (e) { throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e) } @@ -137,9 +139,9 @@ export class ContributionMessageResolver { let finalContributionMessage: DbContributionMessage | undefined try { - await getConnection().transaction( - 'REPEATABLE READ', - async (transactionalEntityManager: EntityManager) => { + await db + .getDataSource() + .transaction('REPEATABLE READ', async (transactionalEntityManager: EntityManager) => { const { contribution, contributionMessage, contributionChanged } = await updateUnconfirmedContributionContext.run(transactionalEntityManager, relations) if (contributionChanged) { @@ -159,8 +161,7 @@ export class ContributionMessageResolver { } finalContribution = contribution finalContributionMessage = contributionMessage - }, - ) + }) } catch (e) { throw new LogError(`ContributionMessage was not sent successfully: ${e}`, e) } diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index b9dd50560..02ec62748 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -1,9 +1,8 @@ -import { UserInputError } from 'apollo-server-express' import { ApolloServerTestClient } from 'apollo-server-testing' import { Contribution, Event as DbEvent, Transaction as DbTransaction, User } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { Connection, Equal } from 'typeorm' +import { DataSource, Equal } from 'typeorm' import { ContributionMessageType } from '@enum/ContributionMessageType' import { ContributionStatus } from '@enum/ContributionStatus' @@ -57,11 +56,11 @@ jest.mock('@/password/EncryptorUtils') let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } let creation: Contribution | null let admin: User @@ -82,7 +81,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('ContributionResolver', () => { diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 319678157..32bb0ff82 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -48,7 +48,7 @@ import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK' import { calculateDecay } from '@/util/decay' import { fullName } from '@/util/utilities' -import { start } from 'repl' +import { AppDatabase } from 'database' import { ContributionMessageType } from '../enum/ContributionMessageType' import { loadAllContributions, loadUserContributions } from './util/contributions' import { getOpenCreations, getUserCreation, validateContribution } from './util/creations' @@ -57,6 +57,8 @@ import { findContributions } from './util/findContributions' import { getLastTransaction } from './util/getLastTransaction' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' +const db = AppDatabase.getInstance() + @Resolver(() => Contribution) export class ContributionResolver { @Authorized([RIGHTS.ADMIN_LIST_CONTRIBUTIONS]) @@ -191,7 +193,7 @@ export class ContributionResolver { context, ) const { contribution, contributionMessage } = await updateUnconfirmedContributionContext.run() - await getConnection().transaction(async (transactionalEntityManager: EntityManager) => { + await db.getDataSource().transaction(async (transactionalEntityManager: EntityManager) => { await transactionalEntityManager.save(contribution) if (contributionMessage) { await transactionalEntityManager.save(contributionMessage) @@ -267,7 +269,7 @@ export class ContributionResolver { ) const { contribution, contributionMessage, createdByUserChangedByModerator } = await updateUnconfirmedContributionContext.run() - await getConnection().transaction(async (transactionalEntityManager: EntityManager) => { + await db.getDataSource().transaction(async (transactionalEntityManager: EntityManager) => { await transactionalEntityManager.save(contribution) // TODO: move into specialized view or formatting for logging class logger.debug('saved changed contribution', { @@ -449,7 +451,7 @@ export class ContributionResolver { ) const receivedCallDate = new Date() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') // 'READ COMMITTED') diff --git a/backend/src/graphql/resolver/EmailOptinCodes.test.ts b/backend/src/graphql/resolver/EmailOptinCodes.test.ts index b9cdaa513..37bf6cc8b 100644 --- a/backend/src/graphql/resolver/EmailOptinCodes.test.ts +++ b/backend/src/graphql/resolver/EmailOptinCodes.test.ts @@ -1,7 +1,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { User as DbUser } from 'database' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, testEnvironment } from '@test/helpers' @@ -12,11 +12,11 @@ import { queryOptIn } from '@/seeds/graphql/queries' let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } CONFIG.EMAIL_CODE_VALID_TIME = 1440 @@ -33,7 +33,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('EmailOptinCodes', () => { diff --git a/backend/src/graphql/resolver/KlicktippResolver.test.ts b/backend/src/graphql/resolver/KlicktippResolver.test.ts index 8c127f10f..6e6cdefca 100644 --- a/backend/src/graphql/resolver/KlicktippResolver.test.ts +++ b/backend/src/graphql/resolver/KlicktippResolver.test.ts @@ -24,7 +24,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('KlicktippResolver', () => { diff --git a/backend/src/graphql/resolver/StatisticsResolver.ts b/backend/src/graphql/resolver/StatisticsResolver.ts index 6d2aa2e20..6713cbb54 100644 --- a/backend/src/graphql/resolver/StatisticsResolver.ts +++ b/backend/src/graphql/resolver/StatisticsResolver.ts @@ -1,13 +1,14 @@ -import { Transaction as DbTransaction, User as DbUser } from 'database' +import { AppDatabase, Transaction as DbTransaction, User as DbUser } from 'database' import { Decimal } from 'decimal.js-light' import { Authorized, FieldResolver, Query, Resolver } from 'type-graphql' -import { getConnection } from 'typeorm' import { CommunityStatistics, DynamicStatisticsFields } from '@model/CommunityStatistics' import { RIGHTS } from '@/auth/RIGHTS' import { calculateDecay } from '@/util/decay' +const db = AppDatabase.getInstance() + @Resolver(() => CommunityStatistics) export class StatisticsResolver { @Authorized([RIGHTS.COMMUNITY_STATISTICS]) @@ -33,7 +34,7 @@ export class StatisticsResolver { @FieldResolver() async totalGradidoCreated(): Promise { - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() try { await queryRunner.connect() const { totalGradidoCreated } = await queryRunner.manager @@ -50,7 +51,7 @@ export class StatisticsResolver { @FieldResolver() async totalGradidoDecayed(): Promise { - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() try { await queryRunner.connect() const { totalGradidoDecayed } = await queryRunner.manager @@ -72,7 +73,7 @@ export class StatisticsResolver { const receivedCallDate = new Date() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() try { await queryRunner.connect() diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index c6592164f..92c147389 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -8,7 +8,7 @@ import { } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import { cleanDB, resetEntity, resetToken, testEnvironment } from '@test/helpers' @@ -45,11 +45,11 @@ TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn()) let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } let user: User @@ -66,7 +66,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('TransactionLinkResolver', () => { diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 0f375b387..01be0fef1 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -15,6 +15,7 @@ import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink' import { User } from '@model/User' import { QueryLinkResult } from '@union/QueryLinkResult' import { + AppDatabase, Contribution as DbContribution, ContributionLink as DbContributionLink, Transaction as DbTransaction, @@ -66,6 +67,7 @@ export const transactionLinkCode = (date: Date): string => { } const CODE_VALID_DAYS_DURATION = 14 +const db = AppDatabase.getInstance() export const transactionLinkExpireDate = (date: Date): Date => { const validUntil = new Date(date) @@ -203,7 +205,7 @@ export class TransactionLinkResolver { try { logger.info('redeem contribution link...') const now = new Date() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') try { diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index d92d24638..b842a2d34 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -1,4 +1,5 @@ import { + AppDatabase, Community as DbCommunity, PendingTransaction as DbPendingTransaction, Transaction as dbTransaction, @@ -7,7 +8,7 @@ import { } from 'database' import { Decimal } from 'decimal.js-light' import { Args, Authorized, Ctx, Mutation, Query, Resolver } from 'type-graphql' -import { In, IsNull, getConnection } from 'typeorm' +import { In, IsNull } from 'typeorm' import { Paginated } from '@arg/Paginated' import { TransactionSendArgs } from '@arg/TransactionSendArgs' @@ -49,6 +50,8 @@ import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConn import { storeForeignUser } from './util/storeForeignUser' import { transactionLinkSummary } from './util/transactionLinkSummary' +const db = AppDatabase.getInstance() + export const executeTransaction = async ( amount: Decimal, memo: string, @@ -96,7 +99,7 @@ export const executeTransaction = async ( throw new LogError('User has not enough GDD or amount is < 0', sendBalance) } - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') logger.debug(`open Transaction to write...`) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index e7b64213f..e6c38c442 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -9,7 +9,7 @@ import { UserRole, } from 'database' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid' import { GmsPublishLocationType } from '@enum/GmsPublishLocationType' @@ -99,11 +99,11 @@ let admin: User let user: User let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -117,7 +117,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('UserResolver', () => { diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 063f3d56a..ee83937f0 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -1,4 +1,5 @@ import { + AppDatabase, ContributionLink as DbContributionLink, TransactionLink as DbTransactionLink, User as DbUser, @@ -22,7 +23,7 @@ import { Root, } from 'type-graphql' import { IRestResponse } from 'typed-rest-client' -import { In, Point, getConnection } from 'typeorm' +import { In, Point } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { UserArgs } from '@arg//UserArgs' @@ -105,6 +106,7 @@ import { validateAlias } from './util/validateAlias' const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl'] const DEFAULT_LANGUAGE = 'de' +const db = AppDatabase.getInstance() const isLanguage = (language: string): boolean => { return LANGUAGES.includes(language) } @@ -394,7 +396,7 @@ export class UserResolver { } } - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') let projectBranding: ProjectBranding | null | undefined @@ -566,7 +568,7 @@ export class UserResolver { user.password = await encryptPassword(user, password) logger.debug('User credentials updated ...') - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') @@ -735,7 +737,7 @@ export class UserResolver { // } catch (err) { // console.log('error:', err) // } - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index 7ed1ea40e..3917efd31 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -2,7 +2,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { cleanDB, contributionDateFormatter, testEnvironment } from '@test/helpers' @@ -25,11 +25,11 @@ import { peterLustig } from '@/seeds/users/peter-lustig' jest.mock('@/password/EncryptorUtils') let mutate: ApolloServerTestClient['mutate'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -41,7 +41,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('semaphore', () => { diff --git a/backend/src/graphql/resolver/util/communities.ts b/backend/src/graphql/resolver/util/communities.ts index 31189bebc..028116c99 100644 --- a/backend/src/graphql/resolver/util/communities.ts +++ b/backend/src/graphql/resolver/util/communities.ts @@ -1,10 +1,13 @@ -import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database' +import { + AppDatabase, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, +} from 'database' import { FindOneOptions, IsNull, Not } from 'typeorm' import { Paginated } from '@arg/Paginated' import { LogError } from '@/server/LogError' -import { Connection } from '@/typeorm/connection' function findWithCommunityIdentifier(communityIdentifier: string): FindOneOptions { return { @@ -115,14 +118,15 @@ export async function getAllCommunities({ pageSize = 25, currentPage = 1, }: Paginated): Promise { - const connection = await Connection.getInstance() - if (!connection) { + const connection = AppDatabase.getInstance() + if (!connection.isConnected()) { throw new LogError('Cannot connect to db') } // foreign: 'ASC', // createdAt: 'DESC', // lastAnnouncedAt: 'DESC', const result = await connection + .getDataSource() .getRepository(DbFederatedCommunity) .createQueryBuilder('federatedCommunity') .leftJoinAndSelect('federatedCommunity.community', 'community') diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 9a1fddd22..26c09fb79 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -1,6 +1,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Contribution, User } from 'database' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, contributionDateFormatter, testEnvironment } from '@test/helpers' @@ -17,11 +17,11 @@ jest.mock('@/password/EncryptorUtils') CONFIG.HUMHUB_ACTIVE = false let mutate: ApolloServerTestClient['mutate'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -33,7 +33,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) const setZeroHours = (date: Date): Date => { diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 74c2987cf..9dadaa8e3 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -1,6 +1,5 @@ import { Contribution } from 'database' import { Decimal } from 'decimal.js-light' -import { getConnection } from 'typeorm' import { OpenCreation } from '@model/OpenCreation' @@ -8,6 +7,9 @@ import { FULL_CREATION_AVAILABLE, MAX_CREATION_AMOUNT } from '@/graphql/resolver import { LogError } from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { getFirstDayOfPreviousNMonth } from '@/util/utilities' +import { AppDatabase } from 'database' + +const db = AppDatabase.getInstance() interface CreationMap { id: number @@ -46,7 +48,7 @@ export const getUserCreations = async ( const months = getCreationMonths(timezoneOffset) logger.trace('getUserCreations months', months) - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day' diff --git a/backend/src/graphql/resolver/util/findContributions.ts b/backend/src/graphql/resolver/util/findContributions.ts index 1334f468b..dd89b2199 100644 --- a/backend/src/graphql/resolver/util/findContributions.ts +++ b/backend/src/graphql/resolver/util/findContributions.ts @@ -1,9 +1,8 @@ -import { Contribution as DbContribution } from 'database' +import { AppDatabase, Contribution as DbContribution } from 'database' import { Brackets, In, IsNull, LessThanOrEqual, Like, Not, SelectQueryBuilder } from 'typeorm' import { Paginated } from '@arg/Paginated' import { SearchContributionsFilterArgs } from '@arg/SearchContributionsFilterArgs' -import { Connection } from '@typeorm/connection' import { LogError } from '@/server/LogError' @@ -32,11 +31,14 @@ export const findContributions = async ( relations: Relations | undefined = undefined, countOnly = false, ): Promise<[DbContribution[], number]> => { - const connection = await Connection.getInstance() - if (!connection) { + const connection = AppDatabase.getInstance() + if (!connection.isConnected()) { throw new LogError('Cannot connect to db') } - const queryBuilder = connection.getRepository(DbContribution).createQueryBuilder('Contribution') + const queryBuilder = connection + .getDataSource() + .getRepository(DbContribution) + .createQueryBuilder('Contribution') if (relations) { joinRelationsRecursive(relations, queryBuilder, 'Contribution') } diff --git a/backend/src/graphql/resolver/util/findUserByIdentifiers.test.ts b/backend/src/graphql/resolver/util/findUserByIdentifiers.test.ts index 74e9d27e8..94010b846 100644 --- a/backend/src/graphql/resolver/util/findUserByIdentifiers.test.ts +++ b/backend/src/graphql/resolver/util/findUserByIdentifiers.test.ts @@ -1,6 +1,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity, User as DbUser } from 'database' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, testEnvironment } from '@test/helpers' @@ -14,11 +14,11 @@ import { findUserByIdentifier } from './findUserByIdentifier' jest.mock('@/password/EncryptorUtils') -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -29,7 +29,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('graphql/resolver/util/findUserByIdentifier', () => { diff --git a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts index 151b583f5..72a458642 100644 --- a/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts +++ b/backend/src/graphql/resolver/util/sendTransactionsToDltConnector.test.ts @@ -5,7 +5,7 @@ import { Decimal } from 'decimal.js-light' // import { Response } from 'graphql-request/dist/types' import { GraphQLClient } from 'graphql-request' import { Response } from 'graphql-request/dist/types' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { cleanDB, testEnvironment } from '@test/helpers' @@ -328,11 +328,11 @@ async function createTxReceive1FromSend3(verified: boolean): Promise { @@ -343,7 +343,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('create and send Transactions to DltConnector', () => { diff --git a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts index 429c9c08b..82c95ba2e 100644 --- a/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts +++ b/backend/src/graphql/resolver/util/settlePendingSenderTransaction.ts @@ -1,11 +1,11 @@ import { + AppDatabase, Community as DbCommunity, PendingTransaction as DbPendingTransaction, User as DbUser, Transaction as dbTransaction, } from 'database' import { Decimal } from 'decimal.js-light' -import { getConnection } from 'typeorm' import { PendingTransactionState } from '@/graphql/enum/PendingTransactionState' import { LogError } from '@/server/LogError' @@ -15,6 +15,8 @@ import { calculateSenderBalance } from '@/util/calculateSenderBalance' import { getLastTransaction } from './getLastTransaction' +const db = AppDatabase.getInstance() + export async function settlePendingSenderTransaction( homeCom: DbCommunity, senderUser: DbUser, @@ -23,7 +25,7 @@ export async function settlePendingSenderTransaction( // TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!! // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') logger.debug(`start Transaction for write-access...`) diff --git a/backend/src/graphql/resolver/util/transactionLinkSummary.ts b/backend/src/graphql/resolver/util/transactionLinkSummary.ts index 86382fc96..74cac7f32 100644 --- a/backend/src/graphql/resolver/util/transactionLinkSummary.ts +++ b/backend/src/graphql/resolver/util/transactionLinkSummary.ts @@ -1,9 +1,10 @@ -import { TransactionLink as DbTransactionLink } from 'database' +import { AppDatabase, TransactionLink as DbTransactionLink } from 'database' import { Decimal } from 'decimal.js-light' -import { getConnection } from 'typeorm' import { LogError } from '@/server/LogError' +const db = AppDatabase.getInstance() + export const transactionLinkSummary = async ( userId: number, date: Date, @@ -14,7 +15,7 @@ export const transactionLinkSummary = async ( firstDate: Date | null transactionLinkcount: number }> => { - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() try { await queryRunner.connect() const { sumHoldAvailableAmount, sumAmount, lastDate, firstDate, count } = diff --git a/backend/src/graphql/resolver/util/validateAlias.test.ts b/backend/src/graphql/resolver/util/validateAlias.test.ts index 75e599e07..c57a6332f 100644 --- a/backend/src/graphql/resolver/util/validateAlias.test.ts +++ b/backend/src/graphql/resolver/util/validateAlias.test.ts @@ -1,6 +1,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { User } from 'database' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, testEnvironment } from '@test/helpers' import { i18n as localization, logger } from '@test/testSetup' @@ -10,11 +10,11 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { validateAlias } from './validateAlias' -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -25,7 +25,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('validate alias', () => { diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index b21370833..8036d49e5 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -93,7 +93,7 @@ const run = async () => { } logger.info('##seed## seeding all contributionLinks successful...') - await con.close() + await con.destroy() } run().catch((err) => { diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts index b87f4cb24..40d0144fa 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -1,29 +1,27 @@ +import { CONFIG } from '@/config' +import { schema } from '@/graphql/schema' +import { elopageWebhook } from '@/webhook/elopage' +import { gmsWebhook } from '@/webhook/gms' import { ApolloServer } from 'apollo-server-express' import express, { Express, json, urlencoded } from 'express' import { slowDown } from 'express-slow-down' import helmet from 'helmet' import { Logger } from 'log4js' -import { Connection as DbConnection } from 'typeorm' - -import { CONFIG } from '@/config' -import { schema } from '@/graphql/schema' -import { checkDBVersionUntil } from '@/typeorm/DBVersion' -import { elopageWebhook } from '@/webhook/elopage' -import { gmsWebhook } from '@/webhook/gms' +import { DataSource } from 'typeorm' +import { AppDatabase } from 'database' import { context as serverContext } from './context' import { cors } from './cors' import { i18n } from './localization' import { apolloLogger } from './logger' import { plugins } from './plugins' - // TODO implement // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; interface ServerDef { apollo: ApolloServer app: Express - con: DbConnection + con: DataSource } export const createServer = async ( @@ -37,10 +35,8 @@ export const createServer = async ( // open mariadb connection, retry connecting with mariadb // check for correct database version // retry max CONFIG.DB_CONNECT_RETRY_COUNT times, wait CONFIG.DB_CONNECT_RETRY_DELAY ms between tries - const con = await checkDBVersionUntil( - CONFIG.DB_CONNECT_RETRY_COUNT, - CONFIG.DB_CONNECT_RETRY_DELAY_MS, - ) + const db = AppDatabase.getInstance() + await db.init() // Express Server const app = express() @@ -103,5 +99,5 @@ export const createServer = async ( ) logger.debug('createServer...successful') - return { apollo, app, con } + return { apollo, app, con: db.getDataSource() } } diff --git a/backend/src/typeorm/DBVersion.ts b/backend/src/typeorm/DBVersion.ts deleted file mode 100644 index f60af6d9e..000000000 --- a/backend/src/typeorm/DBVersion.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Migration } from 'database' - -import { backendLogger as logger } from '@/server/logger' - -import { CONFIG } from '@/config' -import { Connection } from '@/typeorm/connection' -import { Connection as DbConnection } from 'typeorm' - -async function checkDBVersionUntil(maxRetries: number, delayMs: number): Promise { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const connection = await Connection.getInstance() - if (connection?.isInitialized) { - const dbVersion = await checkDBVersion(CONFIG.DB_VERSION) - if (dbVersion) { - logger.info('Database connection and version check succeeded.') - return connection - } - } - } catch (err) { - logger.warn(`Attempt ${attempt}: Waiting for DB...`, err) - } - await new Promise((resolve) => setTimeout(resolve, delayMs)) - } - - logger.fatal( - `Fatal: Could not connect to database or version check failed after ${maxRetries} attempts.`, - ) - throw new Error('Fatal: Database not ready.') -} - -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, checkDBVersionUntil } diff --git a/backend/src/typeorm/connection.ts b/backend/src/typeorm/connection.ts deleted file mode 100644 index d56a95778..000000000 --- a/backend/src/typeorm/connection.ts +++ /dev/null @@ -1,55 +0,0 @@ -// TODO This is super weird - since the entities are defined in another project they have their own globals. -// We cannot use our connection here, but must use the external typeorm installation -import { entities } from 'database' -import { Connection as DbConnection, FileLogger, createConnection } from 'typeorm' - -import { CONFIG } from '@/config' - -export class Connection { - private static instance: DbConnection - - /** - * The Singleton's constructor should always be private to prevent direct - * construction calls with the `new` operator. - */ - private constructor() {} - - /** - * 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 async getInstance(): Promise { - if (Connection.instance) { - return Connection.instance - } - try { - Connection.instance = await createConnection({ - name: 'default', - type: 'mysql', - legacySpatialSupport: false, - 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', { - // workaround to let previous path working, because with esbuild the script root path has changed - logPath: (CONFIG.PRODUCTION ? '../' : '') + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, - }), - extra: { - charset: 'utf8mb4_unicode_ci', - }, - }) - return Connection.instance - } catch (error) { - // biome-ignore lint/suspicious/noConsole: maybe logger isn't initialized yet - console.log(error) - return null - } - } -} diff --git a/backend/src/util/executeKlicktipp.ts b/backend/src/util/executeKlicktipp.ts index ade1c3470..41e3624b5 100644 --- a/backend/src/util/executeKlicktipp.ts +++ b/backend/src/util/executeKlicktipp.ts @@ -1,10 +1,10 @@ -import { Connection } from '@/typeorm/connection' +import { AppDatabase } from 'database' import { exportEventDataToKlickTipp } from './klicktipp' async function executeKlicktipp(): Promise { - const connection = await Connection.getInstance() - if (connection) { + const connection = await AppDatabase.getInstance() + if (connection.isConnected()) { await exportEventDataToKlickTipp() await connection.close() return true diff --git a/backend/src/util/klicktipp.test.ts b/backend/src/util/klicktipp.test.ts index a2d7029d0..497753c9f 100644 --- a/backend/src/util/klicktipp.test.ts +++ b/backend/src/util/klicktipp.test.ts @@ -1,6 +1,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Event as DbEvent } from 'database' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { cleanDB, resetToken, testEnvironment } from '@test/helpers' @@ -18,11 +18,11 @@ jest.mock('@/apis/KlicktippController') jest.mock('@/password/EncryptorUtils') let mutate: ApolloServerTestClient['mutate'] -let con: Connection +let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } beforeAll(async () => { @@ -34,7 +34,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.close() + await con.destroy() }) describe('klicktipp', () => { diff --git a/bun.lock b/bun.lock index c62d46703..45509db09 100644 --- a/bun.lock +++ b/bun.lock @@ -148,7 +148,7 @@ "tsconfig-paths": "^4.1.1", "type-graphql": "^1.1.1", "typed-rest-client": "^1.8.11", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "typescript": "^4.9.5", "uuid": "^8.3.2", "workerpool": "^9.2.0", @@ -178,12 +178,13 @@ "dotenv": "^10.0.0", "esbuild": "^0.25.2", "geojson": "^0.5.0", + "joi-extract-type": "^15.0.8", "log4js": "^6.9.1", "mysql2": "^2.3.0", "reflect-metadata": "^0.1.13", "ts-mysql-migrate": "^1.0.2", "tsx": "^4.19.4", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "uuid": "^8.3.2", "wkx": "^0.5.0", }, @@ -271,7 +272,7 @@ "ts-jest": "27.0.5", "tsconfig-paths": "^4.1.1", "type-graphql": "^1.1.1", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "typescript": "^4.9.5", "uuid": "8.3.2", }, @@ -561,10 +562,16 @@ "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], + "@hapi/address": ["@hapi/address@2.1.4", "", {}, "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="], + "@hapi/boom": ["@hapi/boom@10.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA=="], + "@hapi/bourne": ["@hapi/bourne@1.3.2", "", {}, "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="], + "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="], + "@hapi/joi": ["@hapi/joi@15.1.1", "", { "dependencies": { "@hapi/address": "2.x.x", "@hapi/bourne": "1.x.x", "@hapi/hoek": "8.x.x", "@hapi/topo": "3.x.x" } }, "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ=="], + "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="], "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], @@ -923,6 +930,8 @@ "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], + "@types/hapi__joi": ["@types/hapi__joi@15.0.4", "", { "dependencies": { "@types/hapi__joi": "*" } }, "sha512-VSS6zc7AIOdHVXmqKaGNPYl8eGrMvWi0R5pt3evJL3UdxO8XS28/XAkBXNyLQoymHxhMd4bF3o1U9mZkWDeN8w=="], + "@types/html-to-text": ["@types/html-to-text@9.0.4", "", {}, "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ=="], "@types/http-assert": ["@types/http-assert@1.5.6", "", {}, "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw=="], @@ -2189,6 +2198,8 @@ "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="], + "joi-extract-type": ["joi-extract-type@15.0.8", "", { "dependencies": { "@hapi/joi": "~15", "@types/hapi__joi": "~15" } }, "sha512-Or97aW6QN6YJq0B+x/vYs65+nmcPvYDE7xhlwRl7yHzY+7Z8pVaj0zxjdJlXmIA9zRcbbYQKCGvW+I4g0kUHgA=="], + "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], @@ -3281,6 +3292,8 @@ "zen-observable-ts": ["zen-observable-ts@1.2.5", "", { "dependencies": { "zen-observable": "0.8.15" } }, "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg=="], + "zod": ["zod@3.25.55", "", {}, "sha512-219huNnkSLQnLsQ3uaRjXsxMrVm5C9W3OOpEVt2k5tvMKuA8nBSu38e0B//a+he9Iq2dvmk2VyYVlHqiHa4YBA=="], + "@apollo/protobufjs/@types/node": ["@types/node@10.17.60", "", {}, "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="], "@apollographql/graphql-upload-8-fork/http-errors": ["http-errors@1.8.1", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.1" } }, "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g=="], @@ -3303,6 +3316,10 @@ "@hapi/boom/@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="], + "@hapi/joi/@hapi/hoek": ["@hapi/hoek@8.5.1", "", {}, "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow=="], + + "@hapi/joi/@hapi/topo": ["@hapi/topo@3.1.6", "", { "dependencies": { "@hapi/hoek": "^8.3.0" } }, "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ=="], + "@hyperswarm/secret-stream/noise-handshake": ["noise-handshake@4.1.0", "", { "dependencies": { "b4a": "^1.1.0", "nanoassert": "^2.0.0", "sodium-universal": "^5.0.0" } }, "sha512-ZHt2+mOXTvjtaWS2h/JPvQjmknfKrEld2xdSsRYWXnYiJmK/N+dtxrDVSt1cr9wGAlhH7Ek43lIZNsL5bVeX9A=="], "@hyperswarm/secret-stream/sodium-universal": ["sodium-universal@5.0.1", "", { "dependencies": { "sodium-native": "^5.0.1" }, "peerDependencies": { "sodium-javascript": "~0.8.0" }, "optionalPeers": ["sodium-javascript"] }, "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q=="], diff --git a/config-schema/package.json b/config-schema/package.json index e0cccb0eb..097cb329e 100644 --- a/config-schema/package.json +++ b/config-schema/package.json @@ -27,8 +27,8 @@ "typescript": "^4.9.5" }, "dependencies": { - "joi": "^17.13.3", - "esbuild": "^0.25.2" + "esbuild": "^0.25.2", + "joi": "^17.13.3" }, "engines": { "node": ">=18" diff --git a/config-schema/src/DatabaseConfigSchema.ts b/config-schema/src/DatabaseConfigSchema.ts new file mode 100644 index 000000000..59d0101e1 --- /dev/null +++ b/config-schema/src/DatabaseConfigSchema.ts @@ -0,0 +1,87 @@ +import Joi from 'joi' + +export const DatabaseConfigSchema = Joi.object({ + DB_CONNECT_RETRY_COUNT: Joi.number() + .default(15) + .min(1) + .max(1000) + .description('Number of retries to connect to the database') + .optional(), + + DB_CONNECT_RETRY_DELAY_MS: Joi.number() + .default(500) + .min(100) + .max(10000) + .description('Delay in milliseconds between retries to connect to the database') + .optional(), + + TYPEORM_LOGGING_RELATIVE_PATH: Joi.string() + .pattern(/^[a-zA-Z0-9-_\.\/]+\.log$/) + .message('TYPEORM_LOGGING_RELATIVE_PATH must be a valid filename ending with .log') + .description('log file name for logging typeorm activities') + .default('typeorm.log') + .optional(), + + DB_HOST: Joi.string() + .hostname() + .message('must be a valid host with alphanumeric characters, numbers, points and -') + .description("database host like 'localhost' or 'mariadb' in docker setup") + .default('localhost') + .optional(), + + DB_LOGGING_ACTIVE: Joi.boolean() + .default(false) + .description('Enable sql query logging, only for debug, because produce many log entries') + .optional(), + + DB_LOG_LEVEL: Joi.string() + .valid('all', 'query', 'schema', 'error', 'warn', 'info', 'log', 'migration') + .description('set log level') + .default('info') + .optional(), + + DB_PORT: Joi.number() + .integer() + .min(1024) + .max(49151) + .description('database port, default: 3306') + .default(3306) + .optional(), + + DB_USER: Joi.string() + .pattern(/^[A-Za-z0-9]([A-Za-z0-9-_\.]*[A-Za-z0-9])?$/) // Validates MariaDB username rules + .min(1) // Minimum length 1 + .max(16) // Maximum length 16 + .message( + 'Valid database username (letters, numbers, hyphens, underscores, dots allowed; no spaces, must not start or end with hyphen, dot, or underscore)', + ) + .description('database username for mariadb') + .default('root') + .optional(), + + DB_PASSWORD: Joi.string() + .when(Joi.ref('NODE_ENV'), { + is: 'development', + then: Joi.string().allow(''), + otherwise: Joi.string() + .min(8) + .max(32) + .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*(),.?":{}|<>]).+$/) + .message( + 'Password must be between 8 and 32 characters long, and contain at least one uppercase letter, one lowercase letter, one number, and one special character (e.g., !@#$%^&*).', + ), + }) + .description( + 'Password for the database user. In development mode, an empty password is allowed. In other environments, a complex password is required.', + ) + .default('') + .optional(), + + DB_DATABASE: Joi.string() + .pattern(/^[a-zA-Z][a-zA-Z0-9_-]{1,63}$/) + .description( + 'Database name like gradido_community (must start with a letter, and can only contain letters, numbers, underscores, or dashes)', + ) + .default('gradido_community') + .optional(), +}) diff --git a/config-schema/src/commonSchema.ts b/config-schema/src/commonSchema.ts index fa1afb473..cd5716392 100644 --- a/config-schema/src/commonSchema.ts +++ b/config-schema/src/commonSchema.ts @@ -27,30 +27,6 @@ export const DECAY_START_TIME = Joi.date() .default(new Date('2021-05-13T17:46:31Z')) // default to the specified date if not provided .required() -export const DB_VERSION = Joi.string() - .pattern(/^\d{4}-[a-z0-9-_]+$/) - .message( - 'DB_VERSION must be in the format: YYYY-description, e.g. "0087-add_index_on_user_roles".', - ) - .description( - 'db version string, last migration file name without ending or last folder in entity', - ) - .required() - -export const DB_CONNECT_RETRY_COUNT = Joi.number() - .default(15) - .min(1) - .max(1000) - .description('Number of retries to connect to the database') - .optional() - -export const DB_CONNECT_RETRY_DELAY_MS = Joi.number() - .default(500) - .min(100) - .max(10000) - .description('Delay in milliseconds between retries to connect to the database') - .optional() - export const COMMUNITY_URL = Joi.string() .uri({ scheme: ['http', 'https'] }) .custom((value: string, helpers: Joi.CustomHelpers) => { @@ -177,65 +153,6 @@ export const OPENAI_ACTIVE = Joi.boolean() .description('Flag to enable or disable OpenAI API') .required() -export const TYPEORM_LOGGING_RELATIVE_PATH = Joi.string() - .pattern(/^[a-zA-Z0-9-_\.\/]+\.log$/) - .message('TYPEORM_LOGGING_RELATIVE_PATH must be a valid filename ending with .log') - .description('log file name for logging typeorm activities') - .default('typeorm.log') - .required() - -export const DB_HOST = Joi.string() - .hostname() - .message('must be a valid host with alphanumeric characters, numbers, points and -') - .description("database host like 'localhost' or 'mariadb' in docker setup") - .default('localhost') - .required() - -export const DB_PORT = Joi.number() - .integer() - .min(1024) - .max(49151) - .description('database port, default: 3306') - .default(3306) - .required() - -export const DB_USER = Joi.string() - .pattern(/^[A-Za-z0-9]([A-Za-z0-9-_\.]*[A-Za-z0-9])?$/) // Validates MariaDB username rules - .min(1) // Minimum length 1 - .max(16) // Maximum length 16 - .message( - 'Valid database username (letters, numbers, hyphens, underscores, dots allowed; no spaces, must not start or end with hyphen, dot, or underscore)', - ) - .description('database username for mariadb') - .default('root') - .required() - -export const DB_PASSWORD = Joi.string() - .when(Joi.ref('NODE_ENV'), { - is: 'development', - then: Joi.string().allow(''), - otherwise: Joi.string() - .min(8) - .max(32) - .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*(),.?":{}|<>]).+$/) - .message( - 'Password must be between 8 and 32 characters long, and contain at least one uppercase letter, one lowercase letter, one number, and one special character (e.g., !@#$%^&*).', - ), - }) - .description( - 'Password for the database user. In development mode, an empty password is allowed. In other environments, a complex password is required.', - ) - .default('') - .required() - -export const DB_DATABASE = Joi.string() - .pattern(/^[a-zA-Z][a-zA-Z0-9_-]{1,63}$/) - .description( - 'Database name like gradido_community (must start with a letter, and can only contain letters, numbers, underscores, or dashes)', - ) - .default('gradido_community') - .required() - export const APP_VERSION = Joi.string() .pattern(/^\d+\.\d+\.\d+$/) .message('Version must be in the format "major.minor.patch" (e.g., "2.4.1")') diff --git a/config-schema/src/index.ts b/config-schema/src/index.ts index 3da59fa79..f9e136a34 100644 --- a/config-schema/src/index.ts +++ b/config-schema/src/index.ts @@ -1,35 +1,3 @@ -import { ObjectSchema } from 'joi' export * from './commonSchema' - -export function validate(schema: ObjectSchema, data: any) { - const { error } = schema.validate(data) - const schemaJson = schema.describe() - if (error) { - error.details.forEach((err) => { - const details = JSON.stringify(err, null, 2) - if (!err.context) { - throw new Error('missing context in config validation with joi: ' + details) - } - if (!schemaJson) { - throw new Error('invalid schema in config validation with joi: ' + details) - } - const key = err.context.key - if (key === undefined) { - throw new Error('missing key in config validation with joi: ' + details) - } - const value = err.context.value - const description = schemaJson.keys[key] - ? schema.describe().keys[key].flags.description - : 'No description available' - if (data[key] === undefined) { - throw new Error( - `Environment Variable '${key}' is missing. ${description}, details: ${details}`, - ) - } else { - throw new Error( - `Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`, - ) - } - }) - } -} +export { DatabaseConfigSchema } from './DatabaseConfigSchema' +export { validate } from './validate' diff --git a/config-schema/src/validate.ts b/config-schema/src/validate.ts new file mode 100644 index 000000000..c566dfab7 --- /dev/null +++ b/config-schema/src/validate.ts @@ -0,0 +1,34 @@ +import { ObjectSchema } from 'joi' + +export function validate(schema: ObjectSchema, data: any) { + const { error } = schema.validate(data) + const schemaJson = schema.describe() + if (error) { + error.details.forEach((err) => { + const details = JSON.stringify(err, null, 2) + if (!err.context) { + throw new Error('missing context in config validation with joi: ' + details) + } + if (!schemaJson) { + throw new Error('invalid schema in config validation with joi: ' + details) + } + const key = err.context.key + if (key === undefined) { + throw new Error('missing key in config validation with joi: ' + details) + } + const value = err.context.value + const description = schemaJson.keys[key] + ? schema.describe().keys[key].flags.description + : 'No description available' + if (data[key] === undefined) { + throw new Error( + `Environment Variable '${key}' is missing. ${description}, details: ${details}`, + ) + } else { + throw new Error( + `Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`, + ) + } + }) + } +} diff --git a/database/esbuild.config.ts b/database/esbuild.config.ts index 790a2094f..98cb7543f 100644 --- a/database/esbuild.config.ts +++ b/database/esbuild.config.ts @@ -1,6 +1,6 @@ import { build } from 'esbuild' import fs from 'node:fs' -import { latestDbVersion } from '@/detectLastDBVersion' +import { latestDbVersion } from './src/detectLastDBVersion' build({ entryPoints: ['src/index.ts'], diff --git a/database/migration/clear.ts b/database/migration/clear.ts index 8a3696ffd..a78d35890 100644 --- a/database/migration/clear.ts +++ b/database/migration/clear.ts @@ -1,6 +1,7 @@ import { Connection } from 'mysql2/promise' -import { CONFIG } from '@/config' +import { CONFIG } from '../src/config' import { connectToDatabaseServer } from './prepare' +import { MIGRATIONS_TABLE } from '../src/config/const' export async function truncateTables(connection: Connection) { const [tables] = await connection.query('SHOW TABLES') @@ -16,7 +17,7 @@ export async function truncateTables(connection: Connection) { // Truncating all tables... for (const tableName of tableNames) { - if (tableName === CONFIG.MIGRATIONS_TABLE) { + if (tableName === MIGRATIONS_TABLE) { continue } await connection.query(`TRUNCATE TABLE \`${tableName}\``) diff --git a/database/migration/index.ts b/database/migration/index.ts index c5e055a36..2c84fb594 100644 --- a/database/migration/index.ts +++ b/database/migration/index.ts @@ -1,11 +1,12 @@ -import { CONFIG } from '@/config' +import { CONFIG } from '../src/config' import { DatabaseState, getDatabaseState } from './prepare' import path from 'node:path' import { createPool } from 'mysql' import { Migration } from 'ts-mysql-migrate' import { clearDatabase } from './clear' -import { latestDbVersion } from '@/detectLastDBVersion' +import { latestDbVersion } from '../src/detectLastDBVersion' +import { MIGRATIONS_TABLE } from '../src/config/const' const run = async (command: string) => { if (command === 'clear') { @@ -48,7 +49,7 @@ const run = async (command: string) => { }) const migration = new Migration({ conn: pool, - tableName: CONFIG.MIGRATIONS_TABLE, + tableName: MIGRATIONS_TABLE, silent: true, dir: path.join(__dirname, 'migrations'), }) diff --git a/database/migration/prepare.ts b/database/migration/prepare.ts index 8db24b3a4..d7fd31e2c 100644 --- a/database/migration/prepare.ts +++ b/database/migration/prepare.ts @@ -1,7 +1,8 @@ import { Connection, ResultSetHeader, RowDataPacket, createConnection } from 'mysql2/promise' -import { CONFIG } from '@/config' -import { latestDbVersion } from '@/detectLastDBVersion' +import { CONFIG } from '../src/config' +import { latestDbVersion } from '../src/detectLastDBVersion' +import { MIGRATIONS_TABLE } from '../src/config/const' export enum DatabaseState { NOT_CONNECTED = 'NOT_CONNECTED', @@ -33,7 +34,7 @@ export async function connectToDatabaseServer( async function convertJsToTsInMigrations(connection: Connection): Promise { const [result] = await connection.query(` - UPDATE ${CONFIG.MIGRATIONS_TABLE} + UPDATE ${MIGRATIONS_TABLE} SET fileName = REPLACE(fileName, '.js', '.ts') WHERE fileName LIKE '%.js' `) @@ -85,7 +86,7 @@ export const getDatabaseState = async (): Promise => { SELECT SUM(fileName LIKE '%.js') AS jsCount, SUM(fileName LIKE '%.ts') AS tsCount - FROM ${CONFIG.MIGRATIONS_TABLE} + FROM ${MIGRATIONS_TABLE} `) if (counts[0].jsCount > 0 && counts[0].tsCount > 0) { @@ -100,7 +101,7 @@ export const getDatabaseState = async (): Promise => { // check if the database is up to date const [rows] = await connection.query( - `SELECT fileName FROM ${CONFIG.MIGRATIONS_TABLE} ORDER BY version DESC LIMIT 1`, + `SELECT fileName FROM ${MIGRATIONS_TABLE} ORDER BY version DESC LIMIT 1`, ) if (rows.length === 0) { return DatabaseState.LOWER_VERSION diff --git a/database/package.json b/database/package.json index cf9d1963b..fd146fbc5 100644 --- a/database/package.json +++ b/database/package.json @@ -35,18 +35,19 @@ "typescript": "^4.9.5" }, "dependencies": { - "@types/uuid": "^8.3.4", + "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", "dotenv": "^10.0.0", "esbuild": "^0.25.2", "geojson": "^0.5.0", + "joi-extract-type": "^15.0.8", "log4js": "^6.9.1", "mysql2": "^2.3.0", "reflect-metadata": "^0.1.13", "ts-mysql-migrate": "^1.0.2", "tsx": "^4.19.4", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "uuid": "^8.3.2", "wkx": "^0.5.0" }, diff --git a/database/src/AppDatabase.ts b/database/src/AppDatabase.ts index 7754a1f61..d06a02230 100644 --- a/database/src/AppDatabase.ts +++ b/database/src/AppDatabase.ts @@ -1,9 +1,9 @@ -import { Migration, entities } from '@/entity' import { DataSource as DBDataSource, FileLogger } from 'typeorm' +import { Migration, entities } from './entity' -import { CONFIG } from '@/config' -import { logger } from '@/logging' import { latestDbVersion } from '.' +import { CONFIG } from './config' +import { logger } from './logging' export class AppDatabase { private static instance: AppDatabase @@ -28,6 +28,10 @@ export class AppDatabase { return AppDatabase.instance } + public isConnected(): boolean { + return this.connection?.isInitialized ?? false + } + public getDataSource(): DBDataSource { if (!this.connection) { throw new Error('Connection not initialized') @@ -41,8 +45,6 @@ export class AppDatabase { return } - // log sql query only of enable by .env, this produce so much data it should be only used when really needed - const logging: boolean = CONFIG.TYPEORM_LOGGING_ACTIVE this.connection = new DBDataSource({ type: 'mysql', legacySpatialSupport: false, @@ -53,10 +55,11 @@ export class AppDatabase { database: CONFIG.DB_DATABASE, entities, synchronize: false, - logging, - logger: logging + logging: CONFIG.TYPEORM_LOGGING_ACTIVE, + logger: CONFIG.TYPEORM_LOGGING_ACTIVE ? new FileLogger('all', { - logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, + // workaround to let previous path working, because with esbuild the script root path has changed + logPath: (CONFIG.PRODUCTION ? '../' : '') + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, }) : undefined, extra: { diff --git a/database/src/config/const.ts b/database/src/config/const.ts new file mode 100644 index 000000000..a02bb7918 --- /dev/null +++ b/database/src/config/const.ts @@ -0,0 +1,2 @@ +export const LOG4JS_BASE_CATEGORY_NAME = 'database' +export const MIGRATIONS_TABLE = 'migrations' diff --git a/database/src/config/index.ts b/database/src/config/index.ts index 906f22b79..3eb93ad66 100644 --- a/database/src/config/index.ts +++ b/database/src/config/index.ts @@ -2,15 +2,6 @@ import dotenv from 'dotenv' dotenv.config() -const constants = { - CONFIG_VERSION: { - DEFAULT: 'DEFAULT', - EXPECTED: 'v1.2022-03-18', - CURRENT: '', - }, - LOG4JS_CATEGORY_NAME: 'database', -} - const database = { DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT ? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT) @@ -27,23 +18,7 @@ const database = { process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.database.log', TYPEORM_LOGGING_ACTIVE: process.env.TYPEORM_LOGGING_ACTIVE === 'true' || false, } - -const migrations = { - MIGRATIONS_TABLE: process.env.MIGRATIONS_TABLE || 'migrations', -} - +const PRODUCTION = process.env.NODE_ENV === 'production' || false const nodeEnv = process.env.NODE_ENV || 'development' -// Check config version -constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT -if ( - ![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes( - constants.CONFIG_VERSION.CURRENT, - ) -) { - throw new Error( - `Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`, - ) -} - -export const CONFIG = { ...constants, ...database, ...migrations, NODE_ENV: nodeEnv } +export const CONFIG = { ...database, NODE_ENV: nodeEnv, PRODUCTION } diff --git a/database/src/index.ts b/database/src/index.ts index 0eb4bb14d..1dd0a84d6 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -1,5 +1,62 @@ -import { latestDbVersion } from '@/detectLastDBVersion' +import { latestDbVersion } from './detectLastDBVersion' +import { Community } from './entity/Community' +import { Contribution } from './entity/Contribution' +import { ContributionLink } from './entity/ContributionLink' +import { ContributionMessage } from './entity/ContributionMessage' +import { DltTransaction } from './entity/DltTransaction' +import { Event } from './entity/Event' +import { FederatedCommunity } from './entity/FederatedCommunity' +import { LoginElopageBuys } from './entity/LoginElopageBuys' +import { Migration } from './entity/Migration' +import { OpenaiThreads } from './entity/OpenaiThreads' +import { PendingTransaction } from './entity/PendingTransaction' +import { ProjectBranding } from './entity/ProjectBranding' +import { Transaction } from './entity/Transaction' +import { TransactionLink } from './entity/TransactionLink' +import { User } from './entity/User' +import { UserContact } from './entity/UserContact' +import { UserRole } from './entity/UserRole' + +export { + Community, + Contribution, + ContributionLink, + ContributionMessage, + DltTransaction, + Event, + FederatedCommunity, + LoginElopageBuys, + Migration, + ProjectBranding, + OpenaiThreads, + PendingTransaction, + Transaction, + TransactionLink, + User, + UserContact, + UserRole, +} + +export const entities = [ + Community, + Contribution, + ContributionLink, + ContributionMessage, + DltTransaction, + Event, + FederatedCommunity, + LoginElopageBuys, + Migration, + ProjectBranding, + OpenaiThreads, + PendingTransaction, + Transaction, + TransactionLink, + User, + UserContact, + UserRole, +] + export { latestDbVersion } -export * from '@/entity' -export * from '@/logging' -export * from '@/AppDatabase' +export * from './logging' +export { AppDatabase } from './AppDatabase' diff --git a/database/src/logging/CommunityLogging.view.ts b/database/src/logging/CommunityLogging.view.ts index ce167e87b..9bd847416 100644 --- a/database/src/logging/CommunityLogging.view.ts +++ b/database/src/logging/CommunityLogging.view.ts @@ -1,4 +1,4 @@ -import { Community } from '@/entity' +import { Community } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' diff --git a/database/src/logging/ContributionLogging.view.ts b/database/src/logging/ContributionLogging.view.ts index 19b9e0de3..a9dd5a36f 100644 --- a/database/src/logging/ContributionLogging.view.ts +++ b/database/src/logging/ContributionLogging.view.ts @@ -1,4 +1,4 @@ -import { Contribution } from '@/entity' +import { Contribution } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionMessageLoggingView } from './ContributionMessageLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/src/logging/ContributionMessageLogging.view.ts b/database/src/logging/ContributionMessageLogging.view.ts index b18f8eff5..7c59e6213 100644 --- a/database/src/logging/ContributionMessageLogging.view.ts +++ b/database/src/logging/ContributionMessageLogging.view.ts @@ -1,4 +1,4 @@ -import { ContributionMessage } from '@/entity' +import { ContributionMessage } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/src/logging/DltTransactionLogging.view.ts b/database/src/logging/DltTransactionLogging.view.ts index 8f55e9b98..e990e5ace 100644 --- a/database/src/logging/DltTransactionLogging.view.ts +++ b/database/src/logging/DltTransactionLogging.view.ts @@ -1,4 +1,4 @@ -import { DltTransaction } from '@/entity' +import { DltTransaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/src/logging/FederatedCommunityLogging.view.ts b/database/src/logging/FederatedCommunityLogging.view.ts index b5e603a50..a5bcb3e5a 100644 --- a/database/src/logging/FederatedCommunityLogging.view.ts +++ b/database/src/logging/FederatedCommunityLogging.view.ts @@ -1,4 +1,4 @@ -import { FederatedCommunity } from '@/entity' +import { FederatedCommunity } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' export class FederatedCommunityLoggingView extends AbstractLoggingView { diff --git a/database/src/logging/PendingTransactionLogging.view.ts b/database/src/logging/PendingTransactionLogging.view.ts index 4f7ba905a..9cf27be88 100644 --- a/database/src/logging/PendingTransactionLogging.view.ts +++ b/database/src/logging/PendingTransactionLogging.view.ts @@ -1,4 +1,4 @@ -import { PendingTransaction, Transaction } from '@/entity' +import { PendingTransaction, Transaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/src/logging/TransactionLogging.view.ts b/database/src/logging/TransactionLogging.view.ts index ce7b82852..bc0eea761 100644 --- a/database/src/logging/TransactionLogging.view.ts +++ b/database/src/logging/TransactionLogging.view.ts @@ -1,4 +1,4 @@ -import { Transaction } from '@/entity' +import { Transaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { DltTransactionLoggingView } from './DltTransactionLogging.view' diff --git a/database/src/logging/UserContactLogging.view.ts b/database/src/logging/UserContactLogging.view.ts index 7fbe258bf..d80b17c67 100644 --- a/database/src/logging/UserContactLogging.view.ts +++ b/database/src/logging/UserContactLogging.view.ts @@ -1,4 +1,4 @@ -import { UserContact } from '@/entity' +import { UserContact } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/src/logging/UserLogging.view.ts b/database/src/logging/UserLogging.view.ts index a930a6f16..1aa5e4407 100644 --- a/database/src/logging/UserLogging.view.ts +++ b/database/src/logging/UserLogging.view.ts @@ -1,4 +1,4 @@ -import { User } from '@/entity' +import { User } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { ContributionMessageLoggingView } from './ContributionMessageLogging.view' diff --git a/database/src/logging/UserRoleLogging.view.ts b/database/src/logging/UserRoleLogging.view.ts index 98a0c60ea..52684d242 100644 --- a/database/src/logging/UserRoleLogging.view.ts +++ b/database/src/logging/UserRoleLogging.view.ts @@ -1,4 +1,4 @@ -import { UserRole } from '@/entity' +import { UserRole } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/src/logging/index.ts b/database/src/logging/index.ts index ee49d3831..c19bd9a57 100644 --- a/database/src/logging/index.ts +++ b/database/src/logging/index.ts @@ -1,5 +1,5 @@ -import { CONFIG } from '@/config' import { getLogger } from 'log4js' +import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' import { AbstractLoggingView } from './AbstractLogging.view' import { CommunityLoggingView } from './CommunityLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' @@ -26,4 +26,4 @@ export { UserRoleLoggingView, } -export const logger = getLogger(CONFIG.LOG4JS_CATEGORY_NAME) +export const logger = getLogger(LOG4JS_BASE_CATEGORY_NAME) diff --git a/database/tsconfig.json b/database/tsconfig.json index a7b8834cd..fc32bbbab 100644 --- a/database/tsconfig.json +++ b/database/tsconfig.json @@ -45,13 +45,13 @@ /* Module Resolution Options */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ - "paths": { - "@/*": ["src/*"], /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - }, + //"baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + //"paths": { + //"@/*": ["src/*"], /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + //}, // "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ - "types": ["node"], /* Type declaration files to be included in compilation. */ + // "types": ["node"], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ diff --git a/dht-node/src/config/index.ts b/dht-node/src/config/index.ts index 14b4a789b..018f074aa 100644 --- a/dht-node/src/config/index.ts +++ b/dht-node/src/config/index.ts @@ -1,5 +1,4 @@ import { validate } from 'config-schema' -import { latestDbVersion } from 'database' import dotenv from 'dotenv' import { schema } from './schema' @@ -7,7 +6,6 @@ import { schema } from './schema' dotenv.config() const constants = { - DB_VERSION: latestDbVersion, LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info LOG_LEVEL: process.env.LOG_LEVEL ?? 'info', @@ -17,22 +15,6 @@ const server = { PRODUCTION: process.env.NODE_ENV === 'production', } -const database = { - DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT - ? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT) - : 15, - DB_CONNECT_RETRY_DELAY_MS: process.env.DB_CONNECT_RETRY_DELAY_MS - ? Number.parseInt(process.env.DB_CONNECT_RETRY_DELAY_MS) - : 500, - DB_HOST: process.env.DB_HOST ?? 'localhost', - DB_PORT: process.env.DB_PORT ? Number.parseInt(process.env.DB_PORT) : 3306, - DB_USER: process.env.DB_USER ?? 'root', - DB_PASSWORD: process.env.DB_PASSWORD ?? '', - DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community', - TYPEORM_LOGGING_RELATIVE_PATH: - process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.dht-node.log', -} - const community = { COMMUNITY_NAME: process.env.COMMUNITY_NAME ?? 'Gradido Entwicklung', COMMUNITY_DESCRIPTION: @@ -53,7 +35,6 @@ const federation = { export const CONFIG = { ...constants, ...server, - ...database, ...community, ...federation, } diff --git a/dht-node/src/config/schema.ts b/dht-node/src/config/schema.ts index 67dac73a4..b65525ac7 100644 --- a/dht-node/src/config/schema.ts +++ b/dht-node/src/config/schema.ts @@ -1,38 +1,20 @@ import { COMMUNITY_DESCRIPTION, COMMUNITY_NAME, - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, - DB_DATABASE, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, LOG4JS_CONFIG, LOG_LEVEL, NODE_ENV, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, } from 'config-schema' import Joi from 'joi' export const schema = Joi.object({ COMMUNITY_NAME, COMMUNITY_DESCRIPTION, - DB_DATABASE, - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, LOG4JS_CONFIG, LOG_LEVEL, NODE_ENV, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, FEDERATION_DHT_TOPIC: Joi.string() .default('GRADIDO_HUB') diff --git a/dht-node/src/index.ts b/dht-node/src/index.ts index e7058f152..ec2d30554 100644 --- a/dht-node/src/index.ts +++ b/dht-node/src/index.ts @@ -1,12 +1,12 @@ import { startDHT } from '@/dht_node/index' -import { CONFIG } from './config' -import { logger } from './server/logger' -import { checkDBVersionUntil } from './typeorm/DBVersion' +import { CONFIG } from '@/config' +import { logger } from '@/server/logger' +import { AppDatabase } from 'database' async function main() { // open mysql connection - await checkDBVersionUntil(CONFIG.DB_CONNECT_RETRY_COUNT, CONFIG.DB_CONNECT_RETRY_DELAY_MS) + await AppDatabase.getInstance().init() logger.debug(`dhtseed set by CONFIG.FEDERATION_DHT_SEED=${CONFIG.FEDERATION_DHT_SEED}`) logger.info( `starting Federation on ${CONFIG.FEDERATION_DHT_TOPIC} ${ diff --git a/dht-node/src/typeorm/DBVersion.ts b/dht-node/src/typeorm/DBVersion.ts deleted file mode 100644 index be9f0c612..000000000 --- a/dht-node/src/typeorm/DBVersion.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Migration } from 'database' - -import { logger } from '@/server/logger' - -import { CONFIG } from '@/config' -import { Connection as DbConnection } from 'typeorm' -import { connection as connectionFunc } from './connection' - -async function checkDBVersionUntil(maxRetries: number, delayMs: number): Promise { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const connection = await connectionFunc() - if (connection?.isInitialized) { - const dbVersion = await checkDBVersion(CONFIG.DB_VERSION) - if (dbVersion) { - logger.info('Database connection and version check succeeded.') - return connection - } - } - } catch (err) { - logger.warn(`Attempt ${attempt}: Waiting for DB...`, err) - } - await new Promise((resolve) => setTimeout(resolve, delayMs)) - } - - logger.fatal( - `Fatal: Could not connect to database or version check failed after ${maxRetries} attempts.`, - ) - throw new Error('Fatal: Database not ready.') -} - -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 || dbVersion.indexOf(DB_VERSION) === -1) { - logger.error( - `Wrong database version detected - the dht-node requires '${DB_VERSION}' but found '${ - dbVersion || 'None' - }`, - ) - return false - } - return true -} - -export { checkDBVersion, getDBVersion, checkDBVersionUntil } diff --git a/dht-node/src/typeorm/connection.ts b/dht-node/src/typeorm/connection.ts deleted file mode 100644 index 14c6195f9..000000000 --- a/dht-node/src/typeorm/connection.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CONFIG } from '@/config' -// TODO This is super weird - since the entities are defined in another project they have their own globals. -// We cannot use our connection here, but must use the external typeorm installation -import { entities } from 'database' -import { Connection, FileLogger, createConnection } from 'typeorm' - -export const connection = async (): Promise => { - try { - return createConnection({ - name: 'default', - 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', { - // workaround to let previous path working, because with esbuild the script root path has changed - logPath: '../' + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, - }), - extra: { - charset: 'utf8mb4_unicode_ci', - }, - }) - } catch (error) { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log(error) - return null - } -} diff --git a/dht-node/test/helpers.ts b/dht-node/test/helpers.ts index 1fd42066a..9d1829a88 100644 --- a/dht-node/test/helpers.ts +++ b/dht-node/test/helpers.ts @@ -1,6 +1,4 @@ -import { entities } from 'database' - -import { checkDBVersionUntil } from '@/typeorm/DBVersion' +import { AppDatabase, entities } from 'database' import { CONFIG } from '@/config' export const headerPushMock = jest.fn((t) => { @@ -24,7 +22,9 @@ export const cleanDB = async () => { } export const testEnvironment = async () => { - return { con: await checkDBVersionUntil(CONFIG.DB_CONNECT_RETRY_COUNT, CONFIG.DB_CONNECT_RETRY_DELAY_MS) } + const appDB = AppDatabase.getInstance() + await appDB.init() + return { con: appDB.getDataSource() } } export const resetEntity = async (entity: any) => { diff --git a/federation/package.json b/federation/package.json index 2a543c17e..f98b6adf7 100644 --- a/federation/package.json +++ b/federation/package.json @@ -60,7 +60,7 @@ "ts-jest": "27.0.5", "tsconfig-paths": "^4.1.1", "type-graphql": "^1.1.1", - "typeorm": "^0.3.16", + "typeorm": "^0.3.22", "typescript": "^4.9.5", "uuid": "8.3.2" }, diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 3c759d702..2d0ab8b87 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -1,4 +1,3 @@ -import { latestDbVersion } from 'database' // ATTENTION: DO NOT PUT ANY SECRETS IN HERE (or the .env) import { Decimal } from 'decimal.js-light' import dotenv from 'dotenv' @@ -15,7 +14,6 @@ Decimal.set({ }) const constants = { - DB_VERSION: latestDbVersion, DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 LOG4JS_CONFIG: 'log4js-config.json', // default log level on production should be info @@ -29,20 +27,6 @@ const server = { // GDT_API_URL: process.env.GDT_API_URL || 'https://gdt.gradido.net', PRODUCTION: process.env.NODE_ENV === 'production', } -const database = { - DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT - ? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT) - : 15, - DB_CONNECT_RETRY_DELAY_MS: process.env.DB_CONNECT_RETRY_DELAY_MS - ? Number.parseInt(process.env.DB_CONNECT_RETRY_DELAY_MS) - : 500, - DB_HOST: process.env.DB_HOST ?? 'localhost', - DB_PORT: process.env.DB_PORT ? Number.parseInt(process.env.DB_PORT) : 3306, - DB_USER: process.env.DB_USER ?? 'root', - DB_PASSWORD: process.env.DB_PASSWORD ?? '', - DB_DATABASE: process.env.DB_DATABASE ?? 'gradido_community', - TYPEORM_LOGGING_RELATIVE_PATH: process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.backend.log', -} const COMMUNITY_HOST = process.env.COMMUNITY_HOST ?? 'localhost' const URL_PROTOCOL = process.env.URL_PROTOCOL ?? 'http' @@ -62,7 +46,6 @@ const federation = { export const CONFIG = { ...constants, ...server, - ...database, // ...community, // ...eventProtocol, ...federation, diff --git a/federation/src/config/schema.ts b/federation/src/config/schema.ts index 812728cff..14e6168cd 100644 --- a/federation/src/config/schema.ts +++ b/federation/src/config/schema.ts @@ -1,38 +1,20 @@ import { - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, - DB_DATABASE, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, DECAY_START_TIME, GRAPHIQL, LOG4JS_CONFIG, LOG_LEVEL, NODE_ENV, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, } from 'config-schema' import Joi from 'joi' export const schema = Joi.object({ - DB_DATABASE, - DB_CONNECT_RETRY_COUNT, - DB_CONNECT_RETRY_DELAY_MS, - DB_HOST, - DB_PASSWORD, - DB_PORT, - DB_USER, - DB_VERSION, DECAY_START_TIME, GRAPHIQL, LOG4JS_CONFIG, LOG_LEVEL, NODE_ENV, PRODUCTION, - TYPEORM_LOGGING_RELATIVE_PATH, FEDERATION_API: Joi.string() .valid('1_0', '1_1') diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index af3b022e5..b3a094b4f 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -2,12 +2,12 @@ import { CONFIG } from '@/config' import { createServer } from '@/server/createServer' import { createTestClient } from 'apollo-server-testing' import { Community as DbCommunity } from 'database' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' let query: any // to do: We need a setup for the tests that closes the connection -let con: Connection +let con: DataSource CONFIG.FEDERATION_API = '1_0' diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index 8129ee702..44273b4c5 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -6,7 +6,7 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { Community as DbCommunity, User as DbUser, UserContact as DbUserContact } from 'database' import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { Connection } from 'typeorm' +import { DataSource } from 'typeorm' import { SendCoinsArgs } from '../model/SendCoinsArgs' let mutate: ApolloServerTestClient['mutate'] // , con: Connection @@ -15,7 +15,7 @@ let mutate: ApolloServerTestClient['mutate'] // , con: Connection let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: Connection + con: DataSource } CONFIG.FEDERATION_API = '1_0' diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts index 5ade497a4..7f9d1a684 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.ts @@ -232,7 +232,7 @@ export class SendCoinsResolver { ) } - logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successfull`) + logger.debug(`XCom: settlePendingReceiveTransaction()-1_0... successful`) return true } else { logger.debug('XCom: settlePendingReceiveTransaction NOT matching pendingTX for settlement...') diff --git a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts index 4d69210ec..8a2bd7663 100644 --- a/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/revertSettledReceiveTransaction.ts @@ -1,4 +1,5 @@ import { + AppDatabase, CommunityLoggingView, Community as DbCommunity, PendingTransaction as DbPendingTransaction, @@ -8,7 +9,6 @@ import { UserLoggingView, Transaction as dbTransaction, } from 'database' -import { getConnection } from 'typeorm' import { PendingTransactionState } from '../enum/PendingTransactionState' @@ -18,6 +18,8 @@ import { federationLogger as logger } from '@/server/logger' import { TRANSACTIONS_LOCK } from '@/graphql/util/TRANSACTIONS_LOCK' import { getLastTransaction } from '@/graphql/util/getLastTransaction' +const db = AppDatabase.getInstance() + export async function revertSettledReceiveTransaction( homeCom: DbCommunity, receiverUser: DbUser, @@ -26,7 +28,7 @@ export async function revertSettledReceiveTransaction( // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') logger.debug(`start Transaction for write-access...`) diff --git a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts index 6a3e6b159..306313f82 100644 --- a/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts +++ b/federation/src/graphql/api/1_0/util/settlePendingReceiveTransaction.ts @@ -1,4 +1,5 @@ import { + AppDatabase, CommunityLoggingView, Community as DbCommunity, PendingTransaction as DbPendingTransaction, @@ -8,7 +9,6 @@ import { UserLoggingView, Transaction as dbTransaction, } from 'database' -import { getConnection } from 'typeorm' import { PendingTransactionState } from '../enum/PendingTransactionState' import { LogError } from '@/server/LogError' @@ -19,6 +19,8 @@ import { getLastTransaction } from '@/graphql/util/getLastTransaction' import Decimal from 'decimal.js-light' import { calculateRecipientBalance } from './calculateRecipientBalance' +const db = AppDatabase.getInstance() + export async function settlePendingReceiveTransaction( homeCom: DbCommunity, receiverUser: DbUser, @@ -27,7 +29,7 @@ export async function settlePendingReceiveTransaction( // TODO: synchronisation with TRANSACTION_LOCK of backend-modul necessary!!! // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - const queryRunner = getConnection().createQueryRunner() + const queryRunner = db.getDataSource().createQueryRunner() await queryRunner.connect() await queryRunner.startTransaction('REPEATABLE READ') logger.debug(`start Transaction for write-access...`) diff --git a/federation/src/server/createServer.ts b/federation/src/server/createServer.ts index e737d2a61..0575e72fc 100644 --- a/federation/src/server/createServer.ts +++ b/federation/src/server/createServer.ts @@ -1,9 +1,7 @@ import 'reflect-metadata' import { ApolloServer } from 'apollo-server-express' -import express, { Express, RequestHandler } from 'express' - -import { checkDBVersionUntil } from '@/typeorm/DBVersion' +import express, { Express } from 'express' // server import cors from './cors' @@ -15,21 +13,20 @@ import { schema } from '@/graphql/schema' // webhooks // import { elopageWebhook } from '@/webhook/elopage' -import { Connection } from 'typeorm' -import { CONFIG } from '@/config' +import { AppDatabase } from 'database' import { slowDown } from 'express-slow-down' import helmet from 'helmet' import { Logger } from 'log4js' +import { DataSource } from 'typeorm' import { apolloLogger } from './logger' - // i18n // import { i18n } from './localization' // TODO implement // import queryComplexity, { simpleEstimator, fieldConfigEstimator } from "graphql-query-complexity"; -type ServerDef = { apollo: ApolloServer; app: Express; con: Connection } +type ServerDef = { apollo: ApolloServer; app: Express; con: DataSource } export const createServer = async ( // context: any = serverContext, @@ -40,10 +37,8 @@ export const createServer = async ( logger.debug('createServer...') // open mysql connection - const con = await checkDBVersionUntil( - CONFIG.DB_CONNECT_RETRY_COUNT, - CONFIG.DB_CONNECT_RETRY_DELAY_MS, - ) + const db = AppDatabase.getInstance() + await db.init() // Express Server const app = express() @@ -97,5 +92,5 @@ export const createServer = async ( apollo.applyMiddleware({ app, path: '/' }) logger.debug('createServer...successful') - return { apollo, app, con } + return { apollo, app, con: db.getDataSource() } } diff --git a/federation/src/typeorm/DBVersion.ts b/federation/src/typeorm/DBVersion.ts deleted file mode 100644 index 712c4ee4f..000000000 --- a/federation/src/typeorm/DBVersion.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { CONFIG } from '@/config' -import { federationLogger as logger } from '@/server/logger' -import { Migration } from 'database' -import { Connection as DbConnection } from 'typeorm' -import { connection as connectionFunc } from './connection' - -async function checkDBVersionUntil(maxRetries: number, delayMs: number): Promise { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - const connection = await connectionFunc() - if (connection?.isInitialized) { - const dbVersion = await checkDBVersion(CONFIG.DB_VERSION) - if (dbVersion) { - logger.info('Database connection and version check succeeded.') - return connection - } - } - } catch (err) { - logger.warn(`Attempt ${attempt}: Waiting for DB...`, err) - } - await new Promise((resolve) => setTimeout(resolve, delayMs)) - } - - logger.fatal( - `Fatal: Could not connect to database or version check failed after ${maxRetries} attempts.`, - ) - throw new Error('Fatal: Database not ready.') -} - -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 || dbVersion.indexOf(DB_VERSION) === -1) { - logger.error( - `Wrong database version detected - the backend requires '${DB_VERSION}' but found '${ - dbVersion || 'None' - }`, - ) - return false - } - return true -} - -export { checkDBVersion, getDBVersion, checkDBVersionUntil } diff --git a/federation/src/typeorm/connection.ts b/federation/src/typeorm/connection.ts deleted file mode 100644 index 827e8d430..000000000 --- a/federation/src/typeorm/connection.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CONFIG } from '@/config' -// TODO This is super weird - since the entities are defined in another project they have their own globals. -// We cannot use our connection here, but must use the external typeorm installation -import { entities } from 'database' -import { Connection, FileLogger, createConnection } from 'typeorm' - -const connection = async (): Promise => { - try { - return createConnection({ - name: 'default', - 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', { - // workaround to let previous path working, because with esbuild the script root path has changed - logPath: '../' + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH, - }), - extra: { - charset: 'utf8mb4_unicode_ci', - }, - }) - } catch (error) { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log(error) - return null - } -} - -export { connection } diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts index 9dad4f49c..9a56caedd 100644 --- a/federation/test/helpers.ts +++ b/federation/test/helpers.ts @@ -1,10 +1,3 @@ - - - - - - - import { entities } from 'database' import { createTestClient } from 'apollo-server-testing' diff --git a/yarn.lock b/yarn.lock index f8a50cf0d..f93aec1bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -787,6 +787,11 @@ resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== +"@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + "@hapi/boom@^10.0.0": version "10.0.1" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" @@ -794,6 +799,16 @@ dependencies: "@hapi/hoek" "^11.0.2" +"@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + "@hapi/hoek@^11.0.2": version "11.0.7" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002" @@ -804,6 +819,23 @@ resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== +"@hapi/joi@~15": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + +"@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + "@hapi/topo@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" @@ -2272,6 +2304,18 @@ dependencies: "@types/node" "*" +"@types/hapi__joi@*": + version "17.1.15" + resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-17.1.15.tgz#f1daacb67386fb6e86393da811971721d0437e28" + integrity sha512-Ehq/YQB0ZqZGObrGngztxtThTiShrG0jlqyUSsNK3cebJSoyYgE/hdZvYt6lH4Wimi28RowDwnr87XseiemqAg== + +"@types/hapi__joi@~15": + version "15.0.4" + resolved "https://registry.yarnpkg.com/@types/hapi__joi/-/hapi__joi-15.0.4.tgz#49e2e1e6da15ade0fdd6db4daf94aecb07bb391b" + integrity sha512-VSS6zc7AIOdHVXmqKaGNPYl8eGrMvWi0R5pt3evJL3UdxO8XS28/XAkBXNyLQoymHxhMd4bF3o1U9mZkWDeN8w== + dependencies: + "@types/hapi__joi" "*" + "@types/html-to-text@*": version "9.0.4" resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-9.0.4.tgz#4a83dd8ae8bfa91457d0b1ffc26f4d0537eff58c" @@ -7884,6 +7928,14 @@ jiti@^2.4.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560" integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== +joi-extract-type@^15.0.8: + version "15.0.8" + resolved "https://registry.yarnpkg.com/joi-extract-type/-/joi-extract-type-15.0.8.tgz#29b42d79717b8fec6841b2bef76f97542e58e687" + integrity sha512-Or97aW6QN6YJq0B+x/vYs65+nmcPvYDE7xhlwRl7yHzY+7Z8pVaj0zxjdJlXmIA9zRcbbYQKCGvW+I4g0kUHgA== + dependencies: + "@hapi/joi" "~15" + "@types/hapi__joi" "~15" + joi@*, joi@^17.13.3: version "17.13.3" resolved "https://registry.yarnpkg.com/joi/-/joi-17.13.3.tgz#0f5cc1169c999b30d344366d384b12d92558bcec" @@ -11544,7 +11596,7 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typeorm@^0.3.16, typeorm@^0.3.22: +typeorm@^0.3.22: version "0.3.22" resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.22.tgz#74b56af4a495b6c8eda887dc9aa4670782b991ff" integrity sha512-P/Tsz3UpJ9+K0oryC0twK5PO27zejLYYwMsE8SISfZc1lVHX+ajigiOyWsKbuXpEFMjD9z7UjLzY3+ElVOMMDA==