use new appDatabase class everywhere

This commit is contained in:
einhornimmond 2025-06-07 08:01:48 +02:00
parent 39945345ec
commit fb63588361
84 changed files with 490 additions and 784 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) {
@ -77,7 +66,7 @@ async function main() {
}
logger.info('##gms## publishing all local users successful...')
await con.destroy()
await con.close()
}
main().catch((e) => {

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
@ -114,7 +101,7 @@ async function main() {
} while (userCount === USER_BULK_SIZE)
process.stdout.write('\n')
await con.destroy()
await con.close()
const elapsed = new Date().getTime() - start
logger.info('export user to humhub, statistics:', {
timeSeconds: elapsed / 1000.0,

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,10 +1,10 @@
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 = await AppDatabase.getInstance()
if (connection.isConnected()) {
await exportEventDataToKlickTipp()
await connection.close()
return true

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

@ -148,7 +148,7 @@
"tsconfig-paths": "^4.1.1",
"type-graphql": "^1.1.1",
"typed-rest-client": "^1.8.11",
"typeorm": "^0.3.16",
"typeorm": "^0.3.22",
"typescript": "^4.9.5",
"uuid": "^8.3.2",
"workerpool": "^9.2.0",
@ -178,12 +178,13 @@
"dotenv": "^10.0.0",
"esbuild": "^0.25.2",
"geojson": "^0.5.0",
"joi-extract-type": "^15.0.8",
"log4js": "^6.9.1",
"mysql2": "^2.3.0",
"reflect-metadata": "^0.1.13",
"ts-mysql-migrate": "^1.0.2",
"tsx": "^4.19.4",
"typeorm": "^0.3.16",
"typeorm": "^0.3.22",
"uuid": "^8.3.2",
"wkx": "^0.5.0",
},
@ -271,7 +272,7 @@
"ts-jest": "27.0.5",
"tsconfig-paths": "^4.1.1",
"type-graphql": "^1.1.1",
"typeorm": "^0.3.16",
"typeorm": "^0.3.22",
"typescript": "^4.9.5",
"uuid": "8.3.2",
},
@ -561,10 +562,16 @@
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
"@hapi/address": ["@hapi/address@2.1.4", "", {}, "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="],
"@hapi/boom": ["@hapi/boom@10.0.1", "", { "dependencies": { "@hapi/hoek": "^11.0.2" } }, "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA=="],
"@hapi/bourne": ["@hapi/bourne@1.3.2", "", {}, "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA=="],
"@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="],
"@hapi/joi": ["@hapi/joi@15.1.1", "", { "dependencies": { "@hapi/address": "2.x.x", "@hapi/bourne": "1.x.x", "@hapi/hoek": "8.x.x", "@hapi/topo": "3.x.x" } }, "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ=="],
"@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="],
"@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="],
@ -923,6 +930,8 @@
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
"@types/hapi__joi": ["@types/hapi__joi@15.0.4", "", { "dependencies": { "@types/hapi__joi": "*" } }, "sha512-VSS6zc7AIOdHVXmqKaGNPYl8eGrMvWi0R5pt3evJL3UdxO8XS28/XAkBXNyLQoymHxhMd4bF3o1U9mZkWDeN8w=="],
"@types/html-to-text": ["@types/html-to-text@9.0.4", "", {}, "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ=="],
"@types/http-assert": ["@types/http-assert@1.5.6", "", {}, "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw=="],
@ -2189,6 +2198,8 @@
"joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="],
"joi-extract-type": ["joi-extract-type@15.0.8", "", { "dependencies": { "@hapi/joi": "~15", "@types/hapi__joi": "~15" } }, "sha512-Or97aW6QN6YJq0B+x/vYs65+nmcPvYDE7xhlwRl7yHzY+7Z8pVaj0zxjdJlXmIA9zRcbbYQKCGvW+I4g0kUHgA=="],
"jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
"js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js", "js-beautify": "js/bin/js-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="],
@ -3281,6 +3292,8 @@
"zen-observable-ts": ["zen-observable-ts@1.2.5", "", { "dependencies": { "zen-observable": "0.8.15" } }, "sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg=="],
"zod": ["zod@3.25.55", "", {}, "sha512-219huNnkSLQnLsQ3uaRjXsxMrVm5C9W3OOpEVt2k5tvMKuA8nBSu38e0B//a+he9Iq2dvmk2VyYVlHqiHa4YBA=="],
"@apollo/protobufjs/@types/node": ["@types/node@10.17.60", "", {}, "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="],
"@apollographql/graphql-upload-8-fork/http-errors": ["http-errors@1.8.1", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.1" } }, "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g=="],
@ -3303,6 +3316,10 @@
"@hapi/boom/@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="],
"@hapi/joi/@hapi/hoek": ["@hapi/hoek@8.5.1", "", {}, "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow=="],
"@hapi/joi/@hapi/topo": ["@hapi/topo@3.1.6", "", { "dependencies": { "@hapi/hoek": "^8.3.0" } }, "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ=="],
"@hyperswarm/secret-stream/noise-handshake": ["noise-handshake@4.1.0", "", { "dependencies": { "b4a": "^1.1.0", "nanoassert": "^2.0.0", "sodium-universal": "^5.0.0" } }, "sha512-ZHt2+mOXTvjtaWS2h/JPvQjmknfKrEld2xdSsRYWXnYiJmK/N+dtxrDVSt1cr9wGAlhH7Ek43lIZNsL5bVeX9A=="],
"@hyperswarm/secret-stream/sodium-universal": ["sodium-universal@5.0.1", "", { "dependencies": { "sodium-native": "^5.0.1" }, "peerDependencies": { "sodium-javascript": "~0.8.0" }, "optionalPeers": ["sodium-javascript"] }, "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q=="],

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,6 +1,6 @@
import { build } from 'esbuild'
import fs from 'node:fs'
import { latestDbVersion } from '@/detectLastDBVersion'
import { latestDbVersion } from './src/detectLastDBVersion'
build({
entryPoints: ['src/index.ts'],

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

View File

@ -1,11 +1,12 @@
import { CONFIG } from '@/config'
import { CONFIG } from '../src/config'
import { DatabaseState, getDatabaseState } from './prepare'
import path from 'node:path'
import { createPool } from 'mysql'
import { Migration } from 'ts-mysql-migrate'
import { clearDatabase } from './clear'
import { latestDbVersion } from '@/detectLastDBVersion'
import { latestDbVersion } from '../src/detectLastDBVersion'
import { MIGRATIONS_TABLE } from '../src/config/const'
const run = async (command: string) => {
if (command === 'clear') {
@ -48,7 +49,7 @@ const run = async (command: string) => {
})
const migration = new Migration({
conn: pool,
tableName: CONFIG.MIGRATIONS_TABLE,
tableName: MIGRATIONS_TABLE,
silent: true,
dir: path.join(__dirname, 'migrations'),
})

View File

@ -1,7 +1,8 @@
import { Connection, ResultSetHeader, RowDataPacket, createConnection } from 'mysql2/promise'
import { CONFIG } from '@/config'
import { latestDbVersion } from '@/detectLastDBVersion'
import { CONFIG } from '../src/config'
import { latestDbVersion } from '../src/detectLastDBVersion'
import { MIGRATIONS_TABLE } from '../src/config/const'
export enum DatabaseState {
NOT_CONNECTED = 'NOT_CONNECTED',
@ -33,7 +34,7 @@ export async function connectToDatabaseServer(
async function convertJsToTsInMigrations(connection: Connection): Promise<number> {
const [result] = await connection.query<ResultSetHeader>(`
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<DatabaseState> => {
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<DatabaseState> => {
// check if the database is up to date
const [rows] = await connection.query<RowDataPacket[]>(
`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

View File

@ -35,18 +35,19 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@types/uuid": "^8.3.4",
"@types/uuid": "^8.3.4",
"cross-env": "^7.0.3",
"decimal.js-light": "^2.5.1",
"dotenv": "^10.0.0",
"esbuild": "^0.25.2",
"geojson": "^0.5.0",
"joi-extract-type": "^15.0.8",
"log4js": "^6.9.1",
"mysql2": "^2.3.0",
"reflect-metadata": "^0.1.13",
"ts-mysql-migrate": "^1.0.2",
"tsx": "^4.19.4",
"typeorm": "^0.3.16",
"typeorm": "^0.3.22",
"uuid": "^8.3.2",
"wkx": "^0.5.0"
},

View File

@ -1,9 +1,9 @@
import { Migration, entities } from '@/entity'
import { DataSource as DBDataSource, FileLogger } from 'typeorm'
import { Migration, entities } from './entity'
import { CONFIG } from '@/config'
import { logger } from '@/logging'
import { latestDbVersion } from '.'
import { CONFIG } from './config'
import { logger } from './logging'
export class AppDatabase {
private static instance: AppDatabase
@ -28,6 +28,10 @@ export class AppDatabase {
return AppDatabase.instance
}
public isConnected(): boolean {
return this.connection?.isInitialized ?? false
}
public getDataSource(): DBDataSource {
if (!this.connection) {
throw new Error('Connection not initialized')
@ -41,8 +45,6 @@ export class AppDatabase {
return
}
// log sql query only of enable by .env, this produce so much data it should be only used when really needed
const logging: boolean = CONFIG.TYPEORM_LOGGING_ACTIVE
this.connection = new DBDataSource({
type: 'mysql',
legacySpatialSupport: false,
@ -53,10 +55,11 @@ export class AppDatabase {
database: CONFIG.DB_DATABASE,
entities,
synchronize: false,
logging,
logger: logging
logging: CONFIG.TYPEORM_LOGGING_ACTIVE,
logger: CONFIG.TYPEORM_LOGGING_ACTIVE
? new FileLogger('all', {
logPath: CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
// workaround to let previous path working, because with esbuild the script root path has changed
logPath: (CONFIG.PRODUCTION ? '../' : '') + CONFIG.TYPEORM_LOGGING_RELATIVE_PATH,
})
: undefined,
extra: {

View File

@ -0,0 +1,2 @@
export const LOG4JS_BASE_CATEGORY_NAME = 'database'
export const MIGRATIONS_TABLE = 'migrations'

View File

@ -2,15 +2,6 @@ import dotenv from 'dotenv'
dotenv.config()
const constants = {
CONFIG_VERSION: {
DEFAULT: 'DEFAULT',
EXPECTED: 'v1.2022-03-18',
CURRENT: '',
},
LOG4JS_CATEGORY_NAME: 'database',
}
const database = {
DB_CONNECT_RETRY_COUNT: process.env.DB_CONNECT_RETRY_COUNT
? Number.parseInt(process.env.DB_CONNECT_RETRY_COUNT)
@ -27,23 +18,7 @@ const database = {
process.env.TYPEORM_LOGGING_RELATIVE_PATH ?? 'typeorm.database.log',
TYPEORM_LOGGING_ACTIVE: process.env.TYPEORM_LOGGING_ACTIVE === 'true' || false,
}
const migrations = {
MIGRATIONS_TABLE: process.env.MIGRATIONS_TABLE || 'migrations',
}
const PRODUCTION = process.env.NODE_ENV === 'production' || false
const nodeEnv = process.env.NODE_ENV || 'development'
// Check config version
constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT
if (
![constants.CONFIG_VERSION.EXPECTED, constants.CONFIG_VERSION.DEFAULT].includes(
constants.CONFIG_VERSION.CURRENT,
)
) {
throw new Error(
`Fatal: Config Version incorrect - expected "${constants.CONFIG_VERSION.EXPECTED}" or "${constants.CONFIG_VERSION.DEFAULT}", but found "${constants.CONFIG_VERSION.CURRENT}"`,
)
}
export const CONFIG = { ...constants, ...database, ...migrations, NODE_ENV: nodeEnv }
export const CONFIG = { ...database, NODE_ENV: nodeEnv, PRODUCTION }

View File

@ -1,5 +1,62 @@
import { latestDbVersion } from '@/detectLastDBVersion'
import { latestDbVersion } from './detectLastDBVersion'
import { Community } from './entity/Community'
import { Contribution } from './entity/Contribution'
import { ContributionLink } from './entity/ContributionLink'
import { ContributionMessage } from './entity/ContributionMessage'
import { DltTransaction } from './entity/DltTransaction'
import { Event } from './entity/Event'
import { FederatedCommunity } from './entity/FederatedCommunity'
import { LoginElopageBuys } from './entity/LoginElopageBuys'
import { Migration } from './entity/Migration'
import { OpenaiThreads } from './entity/OpenaiThreads'
import { PendingTransaction } from './entity/PendingTransaction'
import { ProjectBranding } from './entity/ProjectBranding'
import { Transaction } from './entity/Transaction'
import { TransactionLink } from './entity/TransactionLink'
import { User } from './entity/User'
import { UserContact } from './entity/UserContact'
import { UserRole } from './entity/UserRole'
export {
Community,
Contribution,
ContributionLink,
ContributionMessage,
DltTransaction,
Event,
FederatedCommunity,
LoginElopageBuys,
Migration,
ProjectBranding,
OpenaiThreads,
PendingTransaction,
Transaction,
TransactionLink,
User,
UserContact,
UserRole,
}
export const entities = [
Community,
Contribution,
ContributionLink,
ContributionMessage,
DltTransaction,
Event,
FederatedCommunity,
LoginElopageBuys,
Migration,
ProjectBranding,
OpenaiThreads,
PendingTransaction,
Transaction,
TransactionLink,
User,
UserContact,
UserRole,
]
export { latestDbVersion }
export * from '@/entity'
export * from '@/logging'
export * from '@/AppDatabase'
export * from './logging'
export { AppDatabase } from './AppDatabase'

View File

@ -1,4 +1,4 @@
import { Community } from '@/entity'
import { Community } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'

View File

@ -1,4 +1,4 @@
import { Contribution } from '@/entity'
import { Contribution } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'

View File

@ -1,4 +1,4 @@
import { ContributionMessage } from '@/entity'
import { ContributionMessage } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { UserLoggingView } from './UserLogging.view'

View File

@ -1,4 +1,4 @@
import { DltTransaction } from '@/entity'
import { DltTransaction } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'

View File

@ -1,4 +1,4 @@
import { FederatedCommunity } from '@/entity'
import { FederatedCommunity } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
export class FederatedCommunityLoggingView extends AbstractLoggingView {

View File

@ -1,4 +1,4 @@
import { PendingTransaction, Transaction } from '@/entity'
import { PendingTransaction, Transaction } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { TransactionLoggingView } from './TransactionLogging.view'

View File

@ -1,4 +1,4 @@
import { Transaction } from '@/entity'
import { Transaction } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { DltTransactionLoggingView } from './DltTransactionLogging.view'

View File

@ -1,4 +1,4 @@
import { UserContact } from '@/entity'
import { UserContact } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'

View File

@ -1,4 +1,4 @@
import { User } from '@/entity'
import { User } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
import { ContributionMessageLoggingView } from './ContributionMessageLogging.view'

View File

@ -1,4 +1,4 @@
import { UserRole } from '@/entity'
import { UserRole } from '../entity'
import { AbstractLoggingView } from './AbstractLogging.view'
import { UserLoggingView } from './UserLogging.view'

View File

@ -1,5 +1,5 @@
import { CONFIG } from '@/config'
import { getLogger } from 'log4js'
import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const'
import { AbstractLoggingView } from './AbstractLogging.view'
import { CommunityLoggingView } from './CommunityLogging.view'
import { ContributionLoggingView } from './ContributionLogging.view'
@ -26,4 +26,4 @@ export {
UserRoleLoggingView,
}
export const logger = getLogger(CONFIG.LOG4JS_CATEGORY_NAME)
export const logger = getLogger(LOG4JS_BASE_CATEGORY_NAME)

View File

@ -45,13 +45,13 @@
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
"paths": {
"@/*": ["src/*"], /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
},
//"baseUrl": ".", /* Base directory to resolve non-absolute module names. */
//"paths": {
//"@/*": ["src/*"], /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
//},
// "rootDirs": [".", "../database"], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["node"], /* Type declaration files to be included in compilation. */
// "types": ["node"], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,3 @@
import { entities } from 'database'
import { createTestClient } from 'apollo-server-testing'

View File

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