Merge pull request #3503 from gradido/upgrade_move_database_code_into_database_module

refactor(database): move database connection into database module
This commit is contained in:
einhornimmond 2025-06-10 19:50:27 +02:00 committed by GitHub
commit 65ce1b492e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
197 changed files with 724 additions and 894 deletions

View File

@ -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",

View File

@ -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()

View File

@ -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) {

View File

@ -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

View File

@ -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,

View File

@ -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'] })

View File

@ -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')

View File

@ -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', () => {

View File

@ -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

View File

@ -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', () => {

View File

@ -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', () => {

View File

@ -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)
}

View File

@ -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', () => {

View File

@ -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')

View File

@ -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', () => {

View File

@ -24,7 +24,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('KlicktippResolver', () => {

View File

@ -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<Decimal> {
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<Decimal> {
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()

View File

@ -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', () => {

View File

@ -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 {

View File

@ -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...`)

View File

@ -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', () => {

View File

@ -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')

View File

@ -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', () => {

View File

@ -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<DbCommunity> {
return {
@ -115,14 +118,15 @@ export async function getAllCommunities({
pageSize = 25,
currentPage = 1,
}: Paginated): Promise<DbCommunity[]> {
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')

View File

@ -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 => {

View File

@ -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'

View File

@ -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')
}

View File

@ -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', () => {

View File

@ -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<Transaction
}
*/
let con: Connection
let con: DataSource
let testEnv: {
mutate: ApolloServerTestClient['mutate']
query: ApolloServerTestClient['query']
con: Connection
con: DataSource
}
beforeAll(async () => {
@ -343,7 +343,7 @@ beforeAll(async () => {
afterAll(async () => {
await cleanDB()
await con.close()
await con.destroy()
})
describe('create and send Transactions to DltConnector', () => {

View File

@ -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...`)

View File

@ -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 } =

View File

@ -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', () => {

View File

@ -93,7 +93,7 @@ const run = async () => {
}
logger.info('##seed## seeding all contributionLinks successful...')
await con.close()
await con.destroy()
}
run().catch((err) => {

View File

@ -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() }
}

View File

@ -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<DbConnection> {
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<string | null> => {
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<boolean> => {
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 }

View File

@ -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<DbConnection | null> {
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
}
}
}

View File

@ -1,12 +1,13 @@
import { Connection } from '@/typeorm/connection'
import { AppDatabase } from 'database'
import { exportEventDataToKlickTipp } from './klicktipp'
async function executeKlicktipp(): Promise<boolean> {
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

View File

@ -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', () => {

View File

@ -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=="],

View File

@ -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"

View File

@ -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(),
})

View File

@ -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<string>) => {
@ -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")')

View File

@ -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'

View File

@ -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}`,
)
}
})
}
}

View File

@ -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',

View File

@ -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}\``)

103
database/migration/index.ts Normal file
View File

@ -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()
})

View File

@ -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]

Some files were not shown because too many files have changed in this diff Show More