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..981f9c90e 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) { diff --git a/backend/src/apis/humhub/ExportUsers.ts b/backend/src/apis/humhub/ExportUsers.ts index b616cbb2e..dc1f4fb5d 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 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..eec525bac 100644 --- a/backend/src/util/executeKlicktipp.ts +++ b/backend/src/util/executeKlicktipp.ts @@ -1,12 +1,13 @@ -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 = AppDatabase.getInstance() + await connection.init() + if (connection.isConnected()) { await exportEventDataToKlickTipp() - await connection.close() + await connection.destroy() return true } else { return false 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 2e1249299..45509db09 100644 --- a/bun.lock +++ b/bun.lock @@ -15,7 +15,7 @@ }, "admin": { "name": "admin", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "@iconify/json": "^2.2.228", "@popperjs/core": "^2.11.8", @@ -84,7 +84,7 @@ }, "backend": { "name": "backend", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "cross-env": "^7.0.3", "email-templates": "^10.0.1", @@ -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", @@ -170,7 +170,7 @@ }, "database": { "name": "database", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "@types/uuid": "^8.3.4", "cross-env": "^7.0.3", @@ -178,11 +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", }, @@ -196,7 +198,7 @@ }, "dht-node": { "name": "dht-node", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "cross-env": "^7.0.3", "dht-rpc": "6.18.1", @@ -227,7 +229,7 @@ }, "federation": { "name": "federation", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "cross-env": "^7.0.3", "sodium-native": "^3.4.1", @@ -270,14 +272,14 @@ "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", }, }, "frontend": { "name": "frontend", - "version": "2.5.2", + "version": "2.6.0", "dependencies": { "@morev/vue-transitions": "^3.0.2", "@types/leaflet": "^1.9.12", @@ -560,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=="], @@ -922,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=="], @@ -2188,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=="], @@ -3280,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=="], @@ -3302,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 ebdb10e2a..98cb7543f 100644 --- a/database/esbuild.config.ts +++ b/database/esbuild.config.ts @@ -1,9 +1,9 @@ import { build } from 'esbuild' import fs from 'node:fs' -import { latestDbVersion } from './src/config/detectLastDBVersion' +import { latestDbVersion } from './src/detectLastDBVersion' build({ - entryPoints: ['entity/index.ts'], + entryPoints: ['src/index.ts'], bundle: true, target: 'node18.20.7', platform: 'node', diff --git a/database/src/clear.ts b/database/migration/clear.ts similarity index 91% rename from database/src/clear.ts rename to database/migration/clear.ts index e2999bbf9..a78d35890 100644 --- a/database/src/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 new file mode 100644 index 000000000..2c84fb594 --- /dev/null +++ b/database/migration/index.ts @@ -0,0 +1,103 @@ +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 '../src/detectLastDBVersion' +import { MIGRATIONS_TABLE } from '../src/config/const' + +const run = async (command: string) => { + if (command === 'clear') { + if (CONFIG.NODE_ENV === 'production') { + throw new Error('Clearing database in production is not allowed') + } + await clearDatabase() + return + } + // Database actions not supported by our migration library + // await createDatabase() + const state = await getDatabaseState() + if (state === DatabaseState.NOT_CONNECTED) { + throw new Error( + `Database not connected, is database server running? + host: ${CONFIG.DB_HOST} + port: ${CONFIG.DB_PORT} + user: ${CONFIG.DB_USER} + password: ${CONFIG.DB_PASSWORD.slice(-2)} + database: ${CONFIG.DB_DATABASE}`, + ) + } + if (state === DatabaseState.HIGHER_VERSION) { + throw new Error('Database version is higher than required, please switch to the correct branch') + } + if (state === DatabaseState.SAME_VERSION) { + if (command === 'up') { + // biome-ignore lint/suspicious/noConsole: no logger present + console.log('Database is up to date') + return + } + } + // Initialize Migrations + const pool = createPool({ + host: CONFIG.DB_HOST, + port: CONFIG.DB_PORT, + user: CONFIG.DB_USER, + password: CONFIG.DB_PASSWORD, + database: CONFIG.DB_DATABASE, + }) + const migration = new Migration({ + conn: pool, + tableName: MIGRATIONS_TABLE, + silent: true, + dir: path.join(__dirname, 'migrations'), + }) + await migration.initialize() + + // Execute command + switch (command) { + case 'up': + await migration.up() // use for upgrade script + break + case 'down': + await migration.down() // use for downgrade script + break + case 'reset': + if (CONFIG.NODE_ENV === 'production') { + throw new Error('Resetting database in production is not allowed') + } + await migration.reset() + break + default: + throw new Error(`Unsupported command ${command}`) + } + if (command === 'reset') { + // biome-ignore lint/suspicious/noConsole: no logger present + console.log('Database was reset') + } else { + const currentDbVersion = await migration.getLastVersion() + // biome-ignore lint/suspicious/noConsole: no logger present + console.log(`Database was ${command} migrated to version: ${currentDbVersion.fileName}`) + if (latestDbVersion === currentDbVersion.fileName.split('.')[0]) { + // biome-ignore lint/suspicious/noConsole: no logger present + console.log('Database is now up to date') + } else { + // biome-ignore lint/suspicious/noConsole: no logger present + console.log('The latest database version is: ', latestDbVersion) + } + } + + // Terminate connections gracefully + pool.end() +} + +run(process.argv[2]) + .catch((err) => { + // biome-ignore lint/suspicious/noConsole: no logger present + console.log(err) + process.exit(1) + }) + .then(() => { + process.exit() + }) diff --git a/database/migrations/0001-init_db.ts b/database/migration/migrations/0001-init_db.ts similarity index 100% rename from database/migrations/0001-init_db.ts rename to database/migration/migrations/0001-init_db.ts diff --git a/database/migrations/0002-add_settings.ts b/database/migration/migrations/0002-add_settings.ts similarity index 100% rename from database/migrations/0002-add_settings.ts rename to database/migration/migrations/0002-add_settings.ts diff --git a/database/migrations/0003-login_server_tables.ts b/database/migration/migrations/0003-login_server_tables.ts similarity index 100% rename from database/migrations/0003-login_server_tables.ts rename to database/migration/migrations/0003-login_server_tables.ts diff --git a/database/migrations/0004-login_server_data.ts b/database/migration/migrations/0004-login_server_data.ts similarity index 100% rename from database/migrations/0004-login_server_data.ts rename to database/migration/migrations/0004-login_server_data.ts diff --git a/database/migrations/0005-admin_tables.ts b/database/migration/migrations/0005-admin_tables.ts similarity index 100% rename from database/migrations/0005-admin_tables.ts rename to database/migration/migrations/0005-admin_tables.ts diff --git a/database/migrations/0006-login_users_collation.ts b/database/migration/migrations/0006-login_users_collation.ts similarity index 100% rename from database/migrations/0006-login_users_collation.ts rename to database/migration/migrations/0006-login_users_collation.ts diff --git a/database/migrations/0007-login_pending_tasks_delete.ts b/database/migration/migrations/0007-login_pending_tasks_delete.ts similarity index 100% rename from database/migrations/0007-login_pending_tasks_delete.ts rename to database/migration/migrations/0007-login_pending_tasks_delete.ts diff --git a/database/migrations/0008-state_users_plug_holes.ts b/database/migration/migrations/0008-state_users_plug_holes.ts similarity index 100% rename from database/migrations/0008-state_users_plug_holes.ts rename to database/migration/migrations/0008-state_users_plug_holes.ts diff --git a/database/migrations/0009-login_users_plug_holes.ts b/database/migration/migrations/0009-login_users_plug_holes.ts similarity index 100% rename from database/migrations/0009-login_users_plug_holes.ts rename to database/migration/migrations/0009-login_users_plug_holes.ts diff --git a/database/migrations/0010-login_users_state_users_sync.ts b/database/migration/migrations/0010-login_users_state_users_sync.ts similarity index 100% rename from database/migrations/0010-login_users_state_users_sync.ts rename to database/migration/migrations/0010-login_users_state_users_sync.ts diff --git a/database/migrations/0011-login_user_backups_plug_holes.ts b/database/migration/migrations/0011-login_user_backups_plug_holes.ts similarity index 100% rename from database/migrations/0011-login_user_backups_plug_holes.ts rename to database/migration/migrations/0011-login_user_backups_plug_holes.ts diff --git a/database/migrations/0012-login_user_backups_unify_wordlist.ts b/database/migration/migrations/0012-login_user_backups_unify_wordlist.ts similarity index 88% rename from database/migrations/0012-login_user_backups_unify_wordlist.ts rename to database/migration/migrations/0012-login_user_backups_unify_wordlist.ts index ad8244f66..914197baf 100644 --- a/database/migrations/0012-login_user_backups_unify_wordlist.ts +++ b/database/migration/migrations/0012-login_user_backups_unify_wordlist.ts @@ -11,15 +11,15 @@ import path from 'path' const TARGET_MNEMONIC_TYPE = 2 const PHRASE_WORD_COUNT = 24 const WORDS_MNEMONIC_0 = fs - .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18112.txt')) + .readFileSync(path.resolve(__dirname, '../../src/config/mnemonic.uncompressed_buffer18112.txt')) .toString() .split(',') const WORDS_MNEMONIC_1 = fs - .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer18113.txt')) + .readFileSync(path.resolve(__dirname, '../../src/config/mnemonic.uncompressed_buffer18113.txt')) .toString() .split(',') const WORDS_MNEMONIC_2 = fs - .readFileSync(path.resolve(__dirname, '../src/config/mnemonic.uncompressed_buffer13116.txt')) + .readFileSync(path.resolve(__dirname, '../../src/config/mnemonic.uncompressed_buffer13116.txt')) .toString() .split(',') const WORDS_MNEMONIC = [WORDS_MNEMONIC_0, WORDS_MNEMONIC_1, WORDS_MNEMONIC_2] diff --git a/database/migrations/0013-drop_unused_tables.ts b/database/migration/migrations/0013-drop_unused_tables.ts similarity index 100% rename from database/migrations/0013-drop_unused_tables.ts rename to database/migration/migrations/0013-drop_unused_tables.ts diff --git a/database/migrations/0014-drop_unused_tables_with_data.ts b/database/migration/migrations/0014-drop_unused_tables_with_data.ts similarity index 100% rename from database/migrations/0014-drop_unused_tables_with_data.ts rename to database/migration/migrations/0014-drop_unused_tables_with_data.ts diff --git a/database/migrations/0015-admin_pending_creations.ts b/database/migration/migrations/0015-admin_pending_creations.ts similarity index 100% rename from database/migrations/0015-admin_pending_creations.ts rename to database/migration/migrations/0015-admin_pending_creations.ts diff --git a/database/migrations/0016-transaction_signatures.ts b/database/migration/migrations/0016-transaction_signatures.ts similarity index 100% rename from database/migrations/0016-transaction_signatures.ts rename to database/migration/migrations/0016-transaction_signatures.ts diff --git a/database/migrations/0017-combine_user_tables.ts b/database/migration/migrations/0017-combine_user_tables.ts similarity index 100% rename from database/migrations/0017-combine_user_tables.ts rename to database/migration/migrations/0017-combine_user_tables.ts diff --git a/database/migrations/0018-combine_login_user_backups_and_user_table.ts b/database/migration/migrations/0018-combine_login_user_backups_and_user_table.ts similarity index 100% rename from database/migrations/0018-combine_login_user_backups_and_user_table.ts rename to database/migration/migrations/0018-combine_login_user_backups_and_user_table.ts diff --git a/database/migrations/0019-replace_login_user_id_with_state_user_id.ts b/database/migration/migrations/0019-replace_login_user_id_with_state_user_id.ts similarity index 100% rename from database/migrations/0019-replace_login_user_id_with_state_user_id.ts rename to database/migration/migrations/0019-replace_login_user_id_with_state_user_id.ts diff --git a/database/migrations/0020-rename_and_clean_state_users.ts b/database/migration/migrations/0020-rename_and_clean_state_users.ts similarity index 100% rename from database/migrations/0020-rename_and_clean_state_users.ts rename to database/migration/migrations/0020-rename_and_clean_state_users.ts diff --git a/database/migrations/0021-elopagebuys_fields_nullable.ts b/database/migration/migrations/0021-elopagebuys_fields_nullable.ts similarity index 100% rename from database/migrations/0021-elopagebuys_fields_nullable.ts rename to database/migration/migrations/0021-elopagebuys_fields_nullable.ts diff --git a/database/migrations/0022-delete_decay_start_block.ts b/database/migration/migrations/0022-delete_decay_start_block.ts similarity index 100% rename from database/migrations/0022-delete_decay_start_block.ts rename to database/migration/migrations/0022-delete_decay_start_block.ts diff --git a/database/migrations/0023-users_disabled_soft_delete.ts b/database/migration/migrations/0023-users_disabled_soft_delete.ts similarity index 100% rename from database/migrations/0023-users_disabled_soft_delete.ts rename to database/migration/migrations/0023-users_disabled_soft_delete.ts diff --git a/database/migrations/0024-combine_transaction_tables.ts b/database/migration/migrations/0024-combine_transaction_tables.ts similarity index 100% rename from database/migrations/0024-combine_transaction_tables.ts rename to database/migration/migrations/0024-combine_transaction_tables.ts diff --git a/database/migrations/0025-emails_to_lower.ts b/database/migration/migrations/0025-emails_to_lower.ts similarity index 100% rename from database/migrations/0025-emails_to_lower.ts rename to database/migration/migrations/0025-emails_to_lower.ts diff --git a/database/migrations/0026-combine_transaction_tables2.ts b/database/migration/migrations/0026-combine_transaction_tables2.ts similarity index 100% rename from database/migrations/0026-combine_transaction_tables2.ts rename to database/migration/migrations/0026-combine_transaction_tables2.ts diff --git a/database/migrations/0027-clean_transaction_table.ts b/database/migration/migrations/0027-clean_transaction_table.ts similarity index 100% rename from database/migrations/0027-clean_transaction_table.ts rename to database/migration/migrations/0027-clean_transaction_table.ts diff --git a/database/migrations/0028-decimal_types.ts b/database/migration/migrations/0028-decimal_types.ts similarity index 100% rename from database/migrations/0028-decimal_types.ts rename to database/migration/migrations/0028-decimal_types.ts diff --git a/database/migrations/0029-clean_transaction_table.ts b/database/migration/migrations/0029-clean_transaction_table.ts similarity index 100% rename from database/migrations/0029-clean_transaction_table.ts rename to database/migration/migrations/0029-clean_transaction_table.ts diff --git a/database/migrations/0030-transaction_link.ts b/database/migration/migrations/0030-transaction_link.ts similarity index 100% rename from database/migrations/0030-transaction_link.ts rename to database/migration/migrations/0030-transaction_link.ts diff --git a/database/migrations/0031-remove_sendEmail_from_transaction_link.ts b/database/migration/migrations/0031-remove_sendEmail_from_transaction_link.ts similarity index 100% rename from database/migrations/0031-remove_sendEmail_from_transaction_link.ts rename to database/migration/migrations/0031-remove_sendEmail_from_transaction_link.ts diff --git a/database/migrations/0032-add-transaction-link-to-transaction.ts b/database/migration/migrations/0032-add-transaction-link-to-transaction.ts similarity index 100% rename from database/migrations/0032-add-transaction-link-to-transaction.ts rename to database/migration/migrations/0032-add-transaction-link-to-transaction.ts diff --git a/database/migrations/0033-add_referrer_id.ts b/database/migration/migrations/0033-add_referrer_id.ts similarity index 100% rename from database/migrations/0033-add_referrer_id.ts rename to database/migration/migrations/0033-add_referrer_id.ts diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migration/migrations/0034-drop_server_user_table.ts similarity index 100% rename from database/migrations/0034-drop_server_user_table.ts rename to database/migration/migrations/0034-drop_server_user_table.ts diff --git a/database/migrations/0035-admin_pending_creations_decimal.ts b/database/migration/migrations/0035-admin_pending_creations_decimal.ts similarity index 100% rename from database/migrations/0035-admin_pending_creations_decimal.ts rename to database/migration/migrations/0035-admin_pending_creations_decimal.ts diff --git a/database/migrations/0036-unique_previous_in_transactions.ts b/database/migration/migrations/0036-unique_previous_in_transactions.ts similarity index 100% rename from database/migrations/0036-unique_previous_in_transactions.ts rename to database/migration/migrations/0036-unique_previous_in_transactions.ts diff --git a/database/migrations/0037-drop_user_setting_table.ts b/database/migration/migrations/0037-drop_user_setting_table.ts similarity index 100% rename from database/migrations/0037-drop_user_setting_table.ts rename to database/migration/migrations/0037-drop_user_setting_table.ts diff --git a/database/migrations/0038-add_contribution_links_table.ts b/database/migration/migrations/0038-add_contribution_links_table.ts similarity index 100% rename from database/migrations/0038-add_contribution_links_table.ts rename to database/migration/migrations/0038-add_contribution_links_table.ts diff --git a/database/migrations/0039-contributions_table.ts b/database/migration/migrations/0039-contributions_table.ts similarity index 100% rename from database/migrations/0039-contributions_table.ts rename to database/migration/migrations/0039-contributions_table.ts diff --git a/database/migrations/0040-add_contribution_link_id_to_user.ts b/database/migration/migrations/0040-add_contribution_link_id_to_user.ts similarity index 100% rename from database/migrations/0040-add_contribution_link_id_to_user.ts rename to database/migration/migrations/0040-add_contribution_link_id_to_user.ts diff --git a/database/migrations/0041-move_users_creation_date.ts b/database/migration/migrations/0041-move_users_creation_date.ts similarity index 100% rename from database/migrations/0041-move_users_creation_date.ts rename to database/migration/migrations/0041-move_users_creation_date.ts diff --git a/database/migrations/0042-update_transactions_for_blockchain.ts b/database/migration/migrations/0042-update_transactions_for_blockchain.ts similarity index 100% rename from database/migrations/0042-update_transactions_for_blockchain.ts rename to database/migration/migrations/0042-update_transactions_for_blockchain.ts diff --git a/database/migrations/0043-add_event_protocol_table.ts b/database/migration/migrations/0043-add_event_protocol_table.ts similarity index 100% rename from database/migrations/0043-add_event_protocol_table.ts rename to database/migration/migrations/0043-add_event_protocol_table.ts diff --git a/database/migrations/0044-insert_missing_contributions.ts b/database/migration/migrations/0044-insert_missing_contributions.ts similarity index 100% rename from database/migrations/0044-insert_missing_contributions.ts rename to database/migration/migrations/0044-insert_missing_contributions.ts diff --git a/database/migrations/0045-add_denied_type_and_status_to_contributions.ts b/database/migration/migrations/0045-add_denied_type_and_status_to_contributions.ts similarity index 100% rename from database/migrations/0045-add_denied_type_and_status_to_contributions.ts rename to database/migration/migrations/0045-add_denied_type_and_status_to_contributions.ts diff --git a/database/migrations/0046-adapt_users_table_for_gradidoid.ts b/database/migration/migrations/0046-adapt_users_table_for_gradidoid.ts similarity index 100% rename from database/migrations/0046-adapt_users_table_for_gradidoid.ts rename to database/migration/migrations/0046-adapt_users_table_for_gradidoid.ts diff --git a/database/migrations/0047-messages_tables.ts b/database/migration/migrations/0047-messages_tables.ts similarity index 100% rename from database/migrations/0047-messages_tables.ts rename to database/migration/migrations/0047-messages_tables.ts diff --git a/database/migrations/0048-add_is_moderator_to_contribution_messages.ts b/database/migration/migrations/0048-add_is_moderator_to_contribution_messages.ts similarity index 100% rename from database/migrations/0048-add_is_moderator_to_contribution_messages.ts rename to database/migration/migrations/0048-add_is_moderator_to_contribution_messages.ts diff --git a/database/migrations/0049-add_user_contacts_table.ts b/database/migration/migrations/0049-add_user_contacts_table.ts similarity index 100% rename from database/migrations/0049-add_user_contacts_table.ts rename to database/migration/migrations/0049-add_user_contacts_table.ts diff --git a/database/migrations/0050-add_messageId_to_event_protocol.ts b/database/migration/migrations/0050-add_messageId_to_event_protocol.ts similarity index 100% rename from database/migrations/0050-add_messageId_to_event_protocol.ts rename to database/migration/migrations/0050-add_messageId_to_event_protocol.ts diff --git a/database/migrations/0051-add_delete_by_to_contributions.ts b/database/migration/migrations/0051-add_delete_by_to_contributions.ts similarity index 100% rename from database/migrations/0051-add_delete_by_to_contributions.ts rename to database/migration/migrations/0051-add_delete_by_to_contributions.ts diff --git a/database/migrations/0052-add_updated_at_to_contributions.ts b/database/migration/migrations/0052-add_updated_at_to_contributions.ts similarity index 100% rename from database/migrations/0052-add_updated_at_to_contributions.ts rename to database/migration/migrations/0052-add_updated_at_to_contributions.ts diff --git a/database/migrations/0053-change_password_encryption.ts b/database/migration/migrations/0053-change_password_encryption.ts similarity index 100% rename from database/migrations/0053-change_password_encryption.ts rename to database/migration/migrations/0053-change_password_encryption.ts diff --git a/database/migrations/0054-recalculate_balance_and_decay.ts b/database/migration/migrations/0054-recalculate_balance_and_decay.ts similarity index 100% rename from database/migrations/0054-recalculate_balance_and_decay.ts rename to database/migration/migrations/0054-recalculate_balance_and_decay.ts diff --git a/database/migrations/0055-consistent_deleted_users.ts b/database/migration/migrations/0055-consistent_deleted_users.ts similarity index 100% rename from database/migrations/0055-consistent_deleted_users.ts rename to database/migration/migrations/0055-consistent_deleted_users.ts diff --git a/database/migrations/0056-consistent_transactions_table.ts b/database/migration/migrations/0056-consistent_transactions_table.ts similarity index 100% rename from database/migrations/0056-consistent_transactions_table.ts rename to database/migration/migrations/0056-consistent_transactions_table.ts diff --git a/database/migrations/0057-clear_old_password_junk.ts b/database/migration/migrations/0057-clear_old_password_junk.ts similarity index 100% rename from database/migrations/0057-clear_old_password_junk.ts rename to database/migration/migrations/0057-clear_old_password_junk.ts diff --git a/database/migrations/0058-add_communities_table.ts b/database/migration/migrations/0058-add_communities_table.ts similarity index 100% rename from database/migrations/0058-add_communities_table.ts rename to database/migration/migrations/0058-add_communities_table.ts diff --git a/database/migrations/0059-add_hide_amount_to_users.ts b/database/migration/migrations/0059-add_hide_amount_to_users.ts similarity index 100% rename from database/migrations/0059-add_hide_amount_to_users.ts rename to database/migration/migrations/0059-add_hide_amount_to_users.ts diff --git a/database/migrations/0060-update_communities_table.ts b/database/migration/migrations/0060-update_communities_table.ts similarity index 100% rename from database/migrations/0060-update_communities_table.ts rename to database/migration/migrations/0060-update_communities_table.ts diff --git a/database/migrations/0061-event_refactoring.ts b/database/migration/migrations/0061-event_refactoring.ts similarity index 100% rename from database/migrations/0061-event_refactoring.ts rename to database/migration/migrations/0061-event_refactoring.ts diff --git a/database/migrations/0062-event_contribution_confirm.ts b/database/migration/migrations/0062-event_contribution_confirm.ts similarity index 100% rename from database/migrations/0062-event_contribution_confirm.ts rename to database/migration/migrations/0062-event_contribution_confirm.ts diff --git a/database/migrations/0063-event_link_fields.ts b/database/migration/migrations/0063-event_link_fields.ts similarity index 100% rename from database/migrations/0063-event_link_fields.ts rename to database/migration/migrations/0063-event_link_fields.ts diff --git a/database/migrations/0064-event_rename.ts b/database/migration/migrations/0064-event_rename.ts similarity index 100% rename from database/migrations/0064-event_rename.ts rename to database/migration/migrations/0064-event_rename.ts diff --git a/database/migrations/0065-refactor_communities_table.ts b/database/migration/migrations/0065-refactor_communities_table.ts similarity index 100% rename from database/migrations/0065-refactor_communities_table.ts rename to database/migration/migrations/0065-refactor_communities_table.ts diff --git a/database/migrations/0066-x-community-sendcoins-transactions_table.ts b/database/migration/migrations/0066-x-community-sendcoins-transactions_table.ts similarity index 100% rename from database/migrations/0066-x-community-sendcoins-transactions_table.ts rename to database/migration/migrations/0066-x-community-sendcoins-transactions_table.ts diff --git a/database/migrations/0067-private_key_in_community_table.ts b/database/migration/migrations/0067-private_key_in_community_table.ts similarity index 100% rename from database/migrations/0067-private_key_in_community_table.ts rename to database/migration/migrations/0067-private_key_in_community_table.ts diff --git a/database/migrations/0068-community_tables_public_key_length.ts b/database/migration/migrations/0068-community_tables_public_key_length.ts similarity index 100% rename from database/migrations/0068-community_tables_public_key_length.ts rename to database/migration/migrations/0068-community_tables_public_key_length.ts diff --git a/database/migrations/0069-add_user_roles_table.ts b/database/migration/migrations/0069-add_user_roles_table.ts similarity index 100% rename from database/migrations/0069-add_user_roles_table.ts rename to database/migration/migrations/0069-add_user_roles_table.ts diff --git a/database/migrations/0070-add_dlt_transactions_table.ts b/database/migration/migrations/0070-add_dlt_transactions_table.ts similarity index 100% rename from database/migrations/0070-add_dlt_transactions_table.ts rename to database/migration/migrations/0070-add_dlt_transactions_table.ts diff --git a/database/migrations/0071-add-pending_transactions-table.ts b/database/migration/migrations/0071-add-pending_transactions-table.ts similarity index 100% rename from database/migrations/0071-add-pending_transactions-table.ts rename to database/migration/migrations/0071-add-pending_transactions-table.ts diff --git a/database/migrations/0072-add_communityuuid_to_transactions_table.ts b/database/migration/migrations/0072-add_communityuuid_to_transactions_table.ts similarity index 100% rename from database/migrations/0072-add_communityuuid_to_transactions_table.ts rename to database/migration/migrations/0072-add_communityuuid_to_transactions_table.ts diff --git a/database/migrations/0073-introduce_foreign_user_in_users_table.ts b/database/migration/migrations/0073-introduce_foreign_user_in_users_table.ts similarity index 100% rename from database/migrations/0073-introduce_foreign_user_in_users_table.ts rename to database/migration/migrations/0073-introduce_foreign_user_in_users_table.ts diff --git a/database/migrations/0074-insert_communityuuid in_existing_users.ts b/database/migration/migrations/0074-insert_communityuuid in_existing_users.ts similarity index 100% rename from database/migrations/0074-insert_communityuuid in_existing_users.ts rename to database/migration/migrations/0074-insert_communityuuid in_existing_users.ts diff --git a/database/migrations/0075-contribution_message_add_index.ts b/database/migration/migrations/0075-contribution_message_add_index.ts similarity index 100% rename from database/migrations/0075-contribution_message_add_index.ts rename to database/migration/migrations/0075-contribution_message_add_index.ts diff --git a/database/migrations/0076-add_updated_by_contribution.ts b/database/migration/migrations/0076-add_updated_by_contribution.ts similarity index 100% rename from database/migrations/0076-add_updated_by_contribution.ts rename to database/migration/migrations/0076-add_updated_by_contribution.ts diff --git a/database/migrations/0077-add_resubmission_date_contribution_message.ts b/database/migration/migrations/0077-add_resubmission_date_contribution_message.ts similarity index 100% rename from database/migrations/0077-add_resubmission_date_contribution_message.ts rename to database/migration/migrations/0077-add_resubmission_date_contribution_message.ts diff --git a/database/migrations/0078-move_resubmission_date.ts b/database/migration/migrations/0078-move_resubmission_date.ts similarity index 100% rename from database/migrations/0078-move_resubmission_date.ts rename to database/migration/migrations/0078-move_resubmission_date.ts diff --git a/database/migrations/0079-fill_linked_user_id_of_contributions.ts b/database/migration/migrations/0079-fill_linked_user_id_of_contributions.ts similarity index 100% rename from database/migrations/0079-fill_linked_user_id_of_contributions.ts rename to database/migration/migrations/0079-fill_linked_user_id_of_contributions.ts diff --git a/database/migrations/0080-fill_linked_user_gradidoId_of_contributions.ts b/database/migration/migrations/0080-fill_linked_user_gradidoId_of_contributions.ts similarity index 100% rename from database/migrations/0080-fill_linked_user_gradidoId_of_contributions.ts rename to database/migration/migrations/0080-fill_linked_user_gradidoId_of_contributions.ts diff --git a/database/migrations/0081-user_join_community.ts b/database/migration/migrations/0081-user_join_community.ts similarity index 100% rename from database/migrations/0081-user_join_community.ts rename to database/migration/migrations/0081-user_join_community.ts diff --git a/database/migrations/0082-introduce_gms_registration.ts b/database/migration/migrations/0082-introduce_gms_registration.ts similarity index 100% rename from database/migrations/0082-introduce_gms_registration.ts rename to database/migration/migrations/0082-introduce_gms_registration.ts diff --git a/database/migrations/0083-join_community_federated_communities.ts b/database/migration/migrations/0083-join_community_federated_communities.ts similarity index 100% rename from database/migrations/0083-join_community_federated_communities.ts rename to database/migration/migrations/0083-join_community_federated_communities.ts diff --git a/database/migrations/0084-introduce_humhub_registration.ts b/database/migration/migrations/0084-introduce_humhub_registration.ts similarity index 100% rename from database/migrations/0084-introduce_humhub_registration.ts rename to database/migration/migrations/0084-introduce_humhub_registration.ts diff --git a/database/migrations/0085-add_index_transactions_user_id.ts b/database/migration/migrations/0085-add_index_transactions_user_id.ts similarity index 100% rename from database/migrations/0085-add_index_transactions_user_id.ts rename to database/migration/migrations/0085-add_index_transactions_user_id.ts diff --git a/database/migrations/0086-add_community_location.ts b/database/migration/migrations/0086-add_community_location.ts similarity index 100% rename from database/migrations/0086-add_community_location.ts rename to database/migration/migrations/0086-add_community_location.ts diff --git a/database/migrations/0087-add_index_on_user_roles.ts b/database/migration/migrations/0087-add_index_on_user_roles.ts similarity index 100% rename from database/migrations/0087-add_index_on_user_roles.ts rename to database/migration/migrations/0087-add_index_on_user_roles.ts diff --git a/database/migrations/0088-create_project_brandings.ts b/database/migration/migrations/0088-create_project_brandings.ts similarity index 100% rename from database/migrations/0088-create_project_brandings.ts rename to database/migration/migrations/0088-create_project_brandings.ts diff --git a/database/migrations/0089-add_openai_threads.ts b/database/migration/migrations/0089-add_openai_threads.ts similarity index 100% rename from database/migrations/0089-add_openai_threads.ts rename to database/migration/migrations/0089-add_openai_threads.ts diff --git a/database/migrations/0090-drop_unused_tables.ts b/database/migration/migrations/0090-drop_unused_tables.ts similarity index 100% rename from database/migrations/0090-drop_unused_tables.ts rename to database/migration/migrations/0090-drop_unused_tables.ts diff --git a/database/src/prepare.ts b/database/migration/prepare.ts similarity index 92% rename from database/src/prepare.ts rename to database/migration/prepare.ts index e29fc1422..d7fd31e2c 100644 --- a/database/src/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 './config/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 d7c7d8fae..fd146fbc5 100644 --- a/database/package.json +++ b/database/package.json @@ -3,7 +3,7 @@ "version": "2.6.0", "description": "Gradido Database Tool to execute database migrations", "main": "./build/index.js", - "types": "./entity/index.ts", + "types": "./src/index.ts", "exports": { ".": { "import": "./build/index.js", @@ -19,13 +19,13 @@ "typecheck": "tsc --noEmit", "lint": "biome check --error-on-warnings .", "lint:fix": "biome check --error-on-warnings . --write", - "clear": "cross-env TZ=UTC tsx src/index.ts clear", - "up": "cross-env TZ=UTC tsx src/index.ts up", - "down": "cross-env TZ=UTC tsx src/index.ts down", - "reset": "cross-env TZ=UTC tsx src/index.ts reset", - "up:backend_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_backend tsx src/index.ts up", - "up:federation_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_federation tsx src/index.ts up", - "up:dht_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_dht tsx src/index.ts up" + "clear": "cross-env TZ=UTC tsx migration/index.ts clear", + "up": "cross-env TZ=UTC tsx migration/index.ts up", + "down": "cross-env TZ=UTC tsx migration/index.ts down", + "reset": "cross-env TZ=UTC tsx migration/index.ts reset", + "up:backend_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_backend tsx migration/index.ts up", + "up:federation_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_federation tsx migration/index.ts up", + "up:dht_test": "cross-env TZ=UTC DB_DATABASE=gradido_test_dht tsx migration/index.ts up" }, "devDependencies": { "@biomejs/biome": "1.9.4", @@ -35,17 +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 new file mode 100644 index 000000000..2a6181a26 --- /dev/null +++ b/database/src/AppDatabase.ts @@ -0,0 +1,114 @@ +import { DataSource as DBDataSource, FileLogger } from 'typeorm' +import { Migration, entities } from './entity' + +import { getLogger } from 'log4js' +import { latestDbVersion } from '.' +import { CONFIG } from './config' +import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' + +const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.AppDatabase`) + +export class AppDatabase { + private static instance: AppDatabase + private dataSource: DBDataSource | undefined + + /** + * 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 getInstance(): AppDatabase { + if (!AppDatabase.instance) { + AppDatabase.instance = new AppDatabase() + } + return AppDatabase.instance + } + + public isConnected(): boolean { + return this.dataSource?.isInitialized ?? false + } + + public getDataSource(): DBDataSource { + if (!this.dataSource) { + throw new Error('Connection not initialized') + } + return this.dataSource + } + + // create database connection, initialize with automatic retry and check for correct database version + public async init(): Promise { + if (this.dataSource?.isInitialized) { + return + } + if (!this.dataSource) { + this.dataSource = new DBDataSource({ + 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: CONFIG.TYPEORM_LOGGING_ACTIVE, + logger: CONFIG.TYPEORM_LOGGING_ACTIVE + ? 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, + }) + : undefined, + extra: { + charset: 'utf8mb4_unicode_ci', + }, + }) + } + // retry connection on failure some times to allow database to catch up + for (let attempt = 1; attempt <= CONFIG.DB_CONNECT_RETRY_COUNT; attempt++) { + try { + await this.dataSource.initialize() + if (this.dataSource.isInitialized) { + logger.info(`Database connection established on attempt ${attempt}`) + break + } + } catch (error) { + logger.warn(`Attempt ${attempt} failed to connect to DB:`, error) + await new Promise((resolve) => setTimeout(resolve, CONFIG.DB_CONNECT_RETRY_DELAY_MS)) + } + } + if (!this.dataSource?.isInitialized) { + throw new Error('Could not connect to database') + } + // check for correct database version + await this.checkDBVersion() + } + + public async destroy(): Promise { + await this.dataSource?.destroy() + } + // ###################################### + // private methods + // ###################################### + private async checkDBVersion(): Promise { + const [dbVersion] = await Migration.find({ order: { version: 'DESC' }, take: 1 }) + if (!dbVersion) { + throw new Error('Could not find database version') + } + if (!dbVersion.fileName.startsWith(latestDbVersion)) { + throw new Error( + `Wrong database version detected - the backend requires '${latestDbVersion}' but found '${ + dbVersion.fileName + }`, + ) + } + } +} + +export const getDataSource = () => AppDatabase.getInstance().getDataSource() 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 fdfb1b57e..3eb93ad66 100644 --- a/database/src/config/index.ts +++ b/database/src/config/index.ts @@ -2,14 +2,6 @@ import dotenv from 'dotenv' dotenv.config() -const constants = { - CONFIG_VERSION: { - DEFAULT: 'DEFAULT', - EXPECTED: 'v1.2022-03-18', - CURRENT: '', - }, -} - const database = { DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT ? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT) @@ -22,24 +14,11 @@ const database = { 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.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/config/detectLastDBVersion.ts b/database/src/detectLastDBVersion.ts similarity index 94% rename from database/src/config/detectLastDBVersion.ts rename to database/src/detectLastDBVersion.ts index b91ac2949..8dedb813f 100644 --- a/database/src/config/detectLastDBVersion.ts +++ b/database/src/detectLastDBVersion.ts @@ -5,7 +5,7 @@ import path from 'node:path' const DB_VERSION_PATTERN = /^(\d{4}-[a-z0-9-_]+)/ // Define the paths to check -const migrationsDir = path.join(__dirname, '..', '..', 'migrations') +const migrationsDir = path.join(__dirname, '..', 'migration', 'migrations') // Helper function to get the highest version number from the directory function getLatestDbVersion(dir: string): string { diff --git a/database/entity/Community.ts b/database/src/entity/Community.ts similarity index 97% rename from database/entity/Community.ts rename to database/src/entity/Community.ts index fde1f0df0..314e96f6a 100644 --- a/database/entity/Community.ts +++ b/database/src/entity/Community.ts @@ -9,9 +9,9 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm' -import { GeometryTransformer } from '../src/typeorm/GeometryTransformer' import { FederatedCommunity } from './FederatedCommunity' import { User } from './User' +import { GeometryTransformer } from './transformer/GeometryTransformer' @Entity('communities') export class Community extends BaseEntity { diff --git a/database/entity/Contribution.ts b/database/src/entity/Contribution.ts similarity index 97% rename from database/entity/Contribution.ts rename to database/src/entity/Contribution.ts index c7173a79c..53ec9f36f 100644 --- a/database/entity/Contribution.ts +++ b/database/src/entity/Contribution.ts @@ -10,10 +10,10 @@ import { OneToOne, PrimaryGeneratedColumn, } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' import { ContributionMessage } from './ContributionMessage' import { Transaction } from './Transaction' import { User } from './User' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('contributions') export class Contribution extends BaseEntity { diff --git a/database/entity/ContributionLink.ts b/database/src/entity/ContributionLink.ts similarity index 96% rename from database/entity/ContributionLink.ts rename to database/src/entity/ContributionLink.ts index 8f79f6b85..fd66c8946 100644 --- a/database/entity/ContributionLink.ts +++ b/database/src/entity/ContributionLink.ts @@ -1,6 +1,6 @@ import { Decimal } from 'decimal.js-light' import { BaseEntity, Column, DeleteDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('contribution_links') export class ContributionLink extends BaseEntity { diff --git a/database/entity/ContributionMessage.ts b/database/src/entity/ContributionMessage.ts similarity index 100% rename from database/entity/ContributionMessage.ts rename to database/src/entity/ContributionMessage.ts diff --git a/database/entity/DltTransaction.ts b/database/src/entity/DltTransaction.ts similarity index 100% rename from database/entity/DltTransaction.ts rename to database/src/entity/DltTransaction.ts diff --git a/database/entity/Event.ts b/database/src/entity/Event.ts similarity index 97% rename from database/entity/Event.ts rename to database/src/entity/Event.ts index 868ddaf60..9d17ffdeb 100644 --- a/database/entity/Event.ts +++ b/database/src/entity/Event.ts @@ -8,13 +8,13 @@ import { ManyToOne, PrimaryGeneratedColumn, } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' import { Contribution } from './Contribution' import { ContributionLink } from './ContributionLink' import { ContributionMessage } from './ContributionMessage' import { Transaction } from './Transaction' import { TransactionLink } from './TransactionLink' import { User } from './User' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('events') export class Event extends BaseEntity { diff --git a/database/entity/FederatedCommunity.ts b/database/src/entity/FederatedCommunity.ts similarity index 100% rename from database/entity/FederatedCommunity.ts rename to database/src/entity/FederatedCommunity.ts diff --git a/database/entity/LoginElopageBuys.ts b/database/src/entity/LoginElopageBuys.ts similarity index 100% rename from database/entity/LoginElopageBuys.ts rename to database/src/entity/LoginElopageBuys.ts diff --git a/database/entity/Migration.ts b/database/src/entity/Migration.ts similarity index 100% rename from database/entity/Migration.ts rename to database/src/entity/Migration.ts diff --git a/database/entity/OpenaiThreads.ts b/database/src/entity/OpenaiThreads.ts similarity index 100% rename from database/entity/OpenaiThreads.ts rename to database/src/entity/OpenaiThreads.ts diff --git a/database/entity/PendingTransaction.ts b/database/src/entity/PendingTransaction.ts similarity index 97% rename from database/entity/PendingTransaction.ts rename to database/src/entity/PendingTransaction.ts index 5281e38ff..71d3b5f30 100644 --- a/database/entity/PendingTransaction.ts +++ b/database/src/entity/PendingTransaction.ts @@ -1,7 +1,7 @@ /* eslint-disable no-use-before-define */ import { Decimal } from 'decimal.js-light' import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('pending_transactions') export class PendingTransaction extends BaseEntity { diff --git a/database/entity/ProjectBranding.ts b/database/src/entity/ProjectBranding.ts similarity index 100% rename from database/entity/ProjectBranding.ts rename to database/src/entity/ProjectBranding.ts diff --git a/database/entity/Transaction.ts b/database/src/entity/Transaction.ts similarity index 98% rename from database/entity/Transaction.ts rename to database/src/entity/Transaction.ts index 196005ef6..b7d83bdf1 100644 --- a/database/entity/Transaction.ts +++ b/database/src/entity/Transaction.ts @@ -1,9 +1,9 @@ /* eslint-disable no-use-before-define */ import { Decimal } from 'decimal.js-light' import { BaseEntity, Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' import { Contribution } from './Contribution' import { DltTransaction } from './DltTransaction' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('transactions') export class Transaction extends BaseEntity { diff --git a/database/entity/TransactionLink.ts b/database/src/entity/TransactionLink.ts similarity index 94% rename from database/entity/TransactionLink.ts rename to database/src/entity/TransactionLink.ts index 68e8c4b68..e1ccfa675 100644 --- a/database/entity/TransactionLink.ts +++ b/database/src/entity/TransactionLink.ts @@ -1,6 +1,6 @@ import { Decimal } from 'decimal.js-light' import { BaseEntity, Column, DeleteDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm' -import { DecimalTransformer } from '../src/typeorm/DecimalTransformer' +import { DecimalTransformer } from './transformer/DecimalTransformer' @Entity('transaction_links') export class TransactionLink extends BaseEntity { diff --git a/database/entity/User.ts b/database/src/entity/User.ts similarity index 98% rename from database/entity/User.ts rename to database/src/entity/User.ts index 9ff7d50f8..9ff6f384b 100644 --- a/database/entity/User.ts +++ b/database/src/entity/User.ts @@ -10,12 +10,12 @@ import { OneToOne, PrimaryGeneratedColumn, } from 'typeorm' -import { GeometryTransformer } from '../src/typeorm/GeometryTransformer' import { Community } from './Community' import { Contribution } from './Contribution' import { ContributionMessage } from './ContributionMessage' import { UserContact } from './UserContact' import { UserRole } from './UserRole' +import { GeometryTransformer } from './transformer/GeometryTransformer' @Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) export class User extends BaseEntity { diff --git a/database/entity/UserContact.ts b/database/src/entity/UserContact.ts similarity index 100% rename from database/entity/UserContact.ts rename to database/src/entity/UserContact.ts diff --git a/database/entity/UserRole.ts b/database/src/entity/UserRole.ts similarity index 100% rename from database/entity/UserRole.ts rename to database/src/entity/UserRole.ts diff --git a/database/entity/index.ts b/database/src/entity/index.ts similarity index 92% rename from database/entity/index.ts rename to database/src/entity/index.ts index b6ecd8f6c..01195c37e 100644 --- a/database/entity/index.ts +++ b/database/src/entity/index.ts @@ -1,4 +1,3 @@ -import { latestDbVersion } from '../src/config/detectLastDBVersion' import { Community } from './Community' import { Contribution } from './Contribution' import { ContributionLink } from './ContributionLink' @@ -35,9 +34,7 @@ export { User, UserContact, UserRole, - latestDbVersion, } -export * from '../logging' export const entities = [ Community, diff --git a/database/src/typeorm/DecimalTransformer.ts b/database/src/entity/transformer/DecimalTransformer.ts similarity index 100% rename from database/src/typeorm/DecimalTransformer.ts rename to database/src/entity/transformer/DecimalTransformer.ts diff --git a/database/src/typeorm/GeometryTransformer.ts b/database/src/entity/transformer/GeometryTransformer.ts similarity index 100% rename from database/src/typeorm/GeometryTransformer.ts rename to database/src/entity/transformer/GeometryTransformer.ts diff --git a/database/src/index.ts b/database/src/index.ts index ca5878f1a..1dd0a84d6 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -1,102 +1,62 @@ -import { CONFIG } from './config' -import { DatabaseState, getDatabaseState } from './prepare' +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' -import path from 'node:path' -import { createPool } from 'mysql' -import { Migration } from 'ts-mysql-migrate' -import { clearDatabase } from './clear' -import { latestDbVersion } from './config/detectLastDBVersion' - -const run = async (command: string) => { - if (command === 'clear') { - if (CONFIG.NODE_ENV === 'production') { - throw new Error('Clearing database in production is not allowed') - } - await clearDatabase() - return - } - // Database actions not supported by our migration library - // await createDatabase() - const state = await getDatabaseState() - if (state === DatabaseState.NOT_CONNECTED) { - throw new Error( - `Database not connected, is database server running? - host: ${CONFIG.DB_HOST} - port: ${CONFIG.DB_PORT} - user: ${CONFIG.DB_USER} - password: ${CONFIG.DB_PASSWORD.slice(-2)} - database: ${CONFIG.DB_DATABASE}`, - ) - } - if (state === DatabaseState.HIGHER_VERSION) { - throw new Error('Database version is higher than required, please switch to the correct branch') - } - if (state === DatabaseState.SAME_VERSION) { - if (command === 'up') { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log('Database is up to date') - return - } - } - // Initialize Migrations - const pool = createPool({ - host: CONFIG.DB_HOST, - port: CONFIG.DB_PORT, - user: CONFIG.DB_USER, - password: CONFIG.DB_PASSWORD, - database: CONFIG.DB_DATABASE, - }) - const migration = new Migration({ - conn: pool, - tableName: CONFIG.MIGRATIONS_TABLE, - silent: true, - dir: path.join(__dirname, '..', 'migrations'), - }) - await migration.initialize() - - // Execute command - switch (command) { - case 'up': - await migration.up() // use for upgrade script - break - case 'down': - await migration.down() // use for downgrade script - break - case 'reset': - if (CONFIG.NODE_ENV === 'production') { - throw new Error('Resetting database in production is not allowed') - } - await migration.reset() - break - default: - throw new Error(`Unsupported command ${command}`) - } - if (command === 'reset') { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log('Database was reset') - } else { - const currentDbVersion = await migration.getLastVersion() - // biome-ignore lint/suspicious/noConsole: no logger present - console.log(`Database was ${command} migrated to version: ${currentDbVersion.fileName}`) - if (latestDbVersion === currentDbVersion.fileName.split('.')[0]) { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log('Database is now up to date') - } else { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log('The latest database version is: ', latestDbVersion) - } - } - - // Terminate connections gracefully - pool.end() +export { + Community, + Contribution, + ContributionLink, + ContributionMessage, + DltTransaction, + Event, + FederatedCommunity, + LoginElopageBuys, + Migration, + ProjectBranding, + OpenaiThreads, + PendingTransaction, + Transaction, + TransactionLink, + User, + UserContact, + UserRole, } -run(process.argv[2]) - .catch((err) => { - // biome-ignore lint/suspicious/noConsole: no logger present - console.log(err) - process.exit(1) - }) - .then(() => { - process.exit() - }) +export const entities = [ + Community, + Contribution, + ContributionLink, + ContributionMessage, + DltTransaction, + Event, + FederatedCommunity, + LoginElopageBuys, + Migration, + ProjectBranding, + OpenaiThreads, + PendingTransaction, + Transaction, + TransactionLink, + User, + UserContact, + UserRole, +] + +export { latestDbVersion } +export * from './logging' +export { AppDatabase } from './AppDatabase' diff --git a/database/logging/AbstractLogging.view.ts b/database/src/logging/AbstractLogging.view.ts similarity index 100% rename from database/logging/AbstractLogging.view.ts rename to database/src/logging/AbstractLogging.view.ts diff --git a/database/logging/CommunityLogging.view.ts b/database/src/logging/CommunityLogging.view.ts similarity index 94% rename from database/logging/CommunityLogging.view.ts rename to database/src/logging/CommunityLogging.view.ts index 0108aefd8..9bd847416 100644 --- a/database/logging/CommunityLogging.view.ts +++ b/database/src/logging/CommunityLogging.view.ts @@ -1,4 +1,4 @@ -import { Community } from '../entity/Community' +import { Community } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' diff --git a/database/logging/ContributionLogging.view.ts b/database/src/logging/ContributionLogging.view.ts similarity index 97% rename from database/logging/ContributionLogging.view.ts rename to database/src/logging/ContributionLogging.view.ts index 9896fac4e..a9dd5a36f 100644 --- a/database/logging/ContributionLogging.view.ts +++ b/database/src/logging/ContributionLogging.view.ts @@ -1,4 +1,4 @@ -import { Contribution } from '../entity/Contribution' +import { Contribution } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionMessageLoggingView } from './ContributionMessageLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/logging/ContributionMessageLogging.view.ts b/database/src/logging/ContributionMessageLogging.view.ts similarity index 93% rename from database/logging/ContributionMessageLogging.view.ts rename to database/src/logging/ContributionMessageLogging.view.ts index 741820a3c..7c59e6213 100644 --- a/database/logging/ContributionMessageLogging.view.ts +++ b/database/src/logging/ContributionMessageLogging.view.ts @@ -1,4 +1,4 @@ -import { ContributionMessage } from '../entity/ContributionMessage' +import { ContributionMessage } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/logging/DltTransactionLogging.view.ts b/database/src/logging/DltTransactionLogging.view.ts similarity index 92% rename from database/logging/DltTransactionLogging.view.ts rename to database/src/logging/DltTransactionLogging.view.ts index 1a51121c8..e990e5ace 100644 --- a/database/logging/DltTransactionLogging.view.ts +++ b/database/src/logging/DltTransactionLogging.view.ts @@ -1,4 +1,4 @@ -import { DltTransaction } from '../entity/DltTransaction' +import { DltTransaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/logging/FederatedCommunityLogging.view.ts b/database/src/logging/FederatedCommunityLogging.view.ts similarity index 91% rename from database/logging/FederatedCommunityLogging.view.ts rename to database/src/logging/FederatedCommunityLogging.view.ts index ccfc9f005..a5bcb3e5a 100644 --- a/database/logging/FederatedCommunityLogging.view.ts +++ b/database/src/logging/FederatedCommunityLogging.view.ts @@ -1,4 +1,4 @@ -import { FederatedCommunity } from '../entity/FederatedCommunity' +import { FederatedCommunity } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' export class FederatedCommunityLoggingView extends AbstractLoggingView { diff --git a/database/logging/PendingTransactionLogging.view.ts b/database/src/logging/PendingTransactionLogging.view.ts similarity index 83% rename from database/logging/PendingTransactionLogging.view.ts rename to database/src/logging/PendingTransactionLogging.view.ts index 78035004b..9cf27be88 100644 --- a/database/logging/PendingTransactionLogging.view.ts +++ b/database/src/logging/PendingTransactionLogging.view.ts @@ -1,5 +1,4 @@ -import { PendingTransaction } from '../entity/PendingTransaction' -import { Transaction } from '../entity/Transaction' +import { PendingTransaction, Transaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { TransactionLoggingView } from './TransactionLogging.view' diff --git a/database/logging/TransactionLogging.view.ts b/database/src/logging/TransactionLogging.view.ts similarity index 97% rename from database/logging/TransactionLogging.view.ts rename to database/src/logging/TransactionLogging.view.ts index e0a039a4b..bc0eea761 100644 --- a/database/logging/TransactionLogging.view.ts +++ b/database/src/logging/TransactionLogging.view.ts @@ -1,4 +1,4 @@ -import { Transaction } from '../entity/Transaction' +import { Transaction } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { DltTransactionLoggingView } from './DltTransactionLogging.view' diff --git a/database/logging/UserContactLogging.view.ts b/database/src/logging/UserContactLogging.view.ts similarity index 95% rename from database/logging/UserContactLogging.view.ts rename to database/src/logging/UserContactLogging.view.ts index e843ca738..d80b17c67 100644 --- a/database/logging/UserContactLogging.view.ts +++ b/database/src/logging/UserContactLogging.view.ts @@ -1,4 +1,4 @@ -import { UserContact } from '../entity/UserContact' +import { UserContact } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/logging/UserLogging.view.ts b/database/src/logging/UserLogging.view.ts similarity index 98% rename from database/logging/UserLogging.view.ts rename to database/src/logging/UserLogging.view.ts index 375068ff7..1aa5e4407 100644 --- a/database/logging/UserLogging.view.ts +++ b/database/src/logging/UserLogging.view.ts @@ -1,4 +1,4 @@ -import { User } from '../entity/User' +import { User } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { ContributionLoggingView } from './ContributionLogging.view' import { ContributionMessageLoggingView } from './ContributionMessageLogging.view' diff --git a/database/logging/UserRoleLogging.view.ts b/database/src/logging/UserRoleLogging.view.ts similarity index 92% rename from database/logging/UserRoleLogging.view.ts rename to database/src/logging/UserRoleLogging.view.ts index 9b4914f15..52684d242 100644 --- a/database/logging/UserRoleLogging.view.ts +++ b/database/src/logging/UserRoleLogging.view.ts @@ -1,4 +1,4 @@ -import { UserRole } from '../entity/UserRole' +import { UserRole } from '../entity' import { AbstractLoggingView } from './AbstractLogging.view' import { UserLoggingView } from './UserLogging.view' diff --git a/database/logging/index.ts b/database/src/logging/index.ts similarity index 87% rename from database/logging/index.ts rename to database/src/logging/index.ts index 9a436ca47..c19bd9a57 100644 --- a/database/logging/index.ts +++ b/database/src/logging/index.ts @@ -1,3 +1,5 @@ +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' @@ -23,3 +25,5 @@ export { UserLoggingView, UserRoleLoggingView, } + +export const logger = getLogger(LOG4JS_BASE_CATEGORY_NAME) diff --git a/database/src/typeorm.ts b/database/src/typeorm.ts deleted file mode 100644 index 4b4f494f1..000000000 --- a/database/src/typeorm.ts +++ /dev/null @@ -1 +0,0 @@ -export * from 'typeorm' diff --git a/database/tsconfig.json b/database/tsconfig.json index 37347b875..fc32bbbab 100644 --- a/database/tsconfig.json +++ b/database/tsconfig.json @@ -45,11 +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": {}, /* 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==