diff --git a/backend/jest.config.js b/backend/jest.config.js index ddf94f977..c2196091a 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -23,21 +23,22 @@ module.exports = { '@repository/(.*)': '/src/typeorm/repository/$1', '@typeorm/(.*)': '/src/typeorm/$1', '@test/(.*)': '/test/$1', - '@entity/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/entity/$1' - : '/../database/build/entity/$1', - '@logging/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/logging/$1' - : '/../database/build/logging/$1', - '@dbTools/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/src/$1' - : '/../database/build/src/$1', - '@config/(.*)': - process.env.NODE_ENV === 'development' - ? '/../config/src/$1' - : '/../config/build/$1', }, + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + tsc: true, + }, + transform: { + decoratorMetadata: true, + }, + } + }], + }, + transformIgnorePatterns: [ + '/node_modules/(?!drizzle-orm/)', + ], } diff --git a/backend/package.json b/backend/package.json index 7335973e8..9420a155a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,6 +39,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.24", "@swc/helpers": "^0.5.17", + "@swc/jest": "^0.2.38", "@types/cors": "^2.8.19", "@types/email-templates": "^10.0.4", "@types/express": "^4.17.21", diff --git a/backend/src/apis/openai/OpenaiClient.ts b/backend/src/apis/openai/OpenaiClient.ts index 527a3a90c..4476fb2fe 100644 --- a/backend/src/apis/openai/OpenaiClient.ts +++ b/backend/src/apis/openai/OpenaiClient.ts @@ -1,10 +1,15 @@ -import { OpenaiThreads, User } from 'database' +import { + dbDeleteOpenaiThread, + dbFindNewestCreatedOpenaiThreadByUserId, + dbInsertOpenaiThread, + dbUpdateOpenaiThread, + User, +} from 'database' import { getLogger } from 'log4js' import { OpenAI } from 'openai' import { Message } from 'openai/resources/beta/threads/messages' import { httpsAgent } from '@/apis/ConnectionAgents' import { CONFIG } from '@/config' - import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { Message as MessageModel } from './model/Message' @@ -64,10 +69,7 @@ export class OpenaiClient { messages: [initialMessage], }) // store id in db because it isn't possible to list all open threads via openai api - const openaiThreadEntity = OpenaiThreads.create() - openaiThreadEntity.id = messageThread.id - openaiThreadEntity.userId = user.id - await openaiThreadEntity.save() + await dbInsertOpenaiThread(messageThread.id, user.id) logger.info(`Created new message thread: ${messageThread.id}`) return messageThread.id @@ -79,10 +81,7 @@ export class OpenaiClient { * @returns */ public async resumeThread(user: User): Promise { - const openaiThreadEntity = await OpenaiThreads.findOne({ - where: { userId: user.id }, - order: { createdAt: 'DESC' }, - }) + const openaiThreadEntity = await dbFindNewestCreatedOpenaiThreadByUserId(user.id) if (!openaiThreadEntity) { logger.warn(`No openai thread found for user: ${user.id}`) return [] @@ -126,7 +125,7 @@ export class OpenaiClient { public async deleteThread(threadId: string): Promise { const [, result] = await Promise.all([ - OpenaiThreads.delete({ id: threadId }), + dbDeleteOpenaiThread(threadId), this.openai.beta.threads.del(threadId), ]) if (result.deleted) { @@ -144,10 +143,7 @@ export class OpenaiClient { } public async runAndGetLastNewMessage(threadId: string): Promise { - const updateOpenAiThreadResolver = OpenaiThreads.update( - { id: threadId }, - { updatedAt: new Date() }, - ) + const updateOpenAiThreadResolver = dbUpdateOpenaiThread(threadId) const run = await this.openai.beta.threads.runs.createAndPoll(threadId, { assistant_id: CONFIG.OPENAI_ASSISTANT_ID, }) diff --git a/backend/src/federation/validateCommunities.test.ts b/backend/src/federation/validateCommunities.test.ts index 3d226a2f5..4627178c6 100644 --- a/backend/src/federation/validateCommunities.test.ts +++ b/backend/src/federation/validateCommunities.test.ts @@ -4,7 +4,7 @@ import { getLogger } from 'config-schema/test/testSetup' import { AppDatabase, FederatedCommunity as DbFederatedCommunity } from 'database' import { GraphQLClient } from 'graphql-request' import { Response } from 'graphql-request/dist/types' -import { DataSource, Not } from 'typeorm' +import { Not } from 'typeorm' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { validateCommunities } from './validateCommunities' @@ -13,26 +13,22 @@ const federationClientLogger = getLogger( `${LOG4JS_BASE_CATEGORY_NAME}.federation.client.1_0.FederationClient`, ) -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } beforeAll(async () => { testEnv = await testEnvironment(logger) - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { // await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('validate Communities', () => { diff --git a/backend/src/graphql/resolver/CommunityResolver.test.ts b/backend/src/graphql/resolver/CommunityResolver.test.ts index d83d57d9e..7aaf2fe71 100644 --- a/backend/src/graphql/resolver/CommunityResolver.test.ts +++ b/backend/src/graphql/resolver/CommunityResolver.test.ts @@ -9,7 +9,6 @@ import { } from 'database' import { createCommunity, createVerifiedFederatedCommunity } from 'database/src/seeds/community' import { GraphQLError } from 'graphql/error/GraphQLError' -import { DataSource } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { CONFIG } from '@/config' import { userFactory } from '@/seeds/factory/user' @@ -29,12 +28,10 @@ CONFIG.FEDERATION_VALIDATE_COMMUNITY_TIMER = 1000 // to do: We need a setup for the tests that closes the connection let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -48,7 +45,6 @@ beforeAll(async () => { testEnv = await testEnvironment(getLogger('apollo')) mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db await cleanDB() // reset id auto increment @@ -57,8 +53,7 @@ beforeAll(async () => { }) afterAll(async () => { - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) // real valid ed25519 key pairs diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts index 3318b285f..6262f893f 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts @@ -4,7 +4,6 @@ import { getLogger } from 'config-schema/test/testSetup' import { AppDatabase, ContributionLink as DbContributionLink, Event as DbEvent } from 'database' import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' -import { DataSource } from 'typeorm' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { EventType } from '@/event/Events' import { userFactory } from '@/seeds/factory/user' @@ -24,12 +23,10 @@ const logErrorLogger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`) let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -37,7 +34,6 @@ beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db await cleanDB() await userFactory(testEnv, bibiBloxberg) @@ -46,8 +42,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('Contribution Links', () => { diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts index 65d1b02df..4d2d534ce 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.test.ts @@ -5,7 +5,6 @@ import { getLogger } from 'config-schema/test/testSetup' import { sendAddedContributionMessageEmail } from 'core' import { AppDatabase, Contribution as DbContribution, Event as DbEvent } from 'database' import { GraphQLError } from 'graphql' -import { DataSource } from 'typeorm' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { EventType } from '@/event/Events' import { userFactory } from '@/seeds/factory/user' @@ -39,12 +38,10 @@ jest.mock('core', () => { }) let mutate: ApolloServerTestClient['mutate'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } let result: any @@ -52,15 +49,13 @@ let result: any beforeAll(async () => { testEnv = await testEnvironment(logger) mutate = testEnv.mutate - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('ContributionMessageResolver', () => { diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index e750f7185..34189971e 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -26,7 +26,7 @@ import { import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' import { getLogger as originalGetLogger } from 'log4js' -import { DataSource, Equal } from 'typeorm' +import { Equal } from 'typeorm' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { EventType } from '@/event/Events' import { creations } from '@/seeds/creation/index' @@ -74,12 +74,10 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.server.LogError`) let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } let creation: Contribution | null @@ -95,15 +93,13 @@ beforeAll(async () => { testEnv = await testEnvironment(originalGetLogger('apollo')) mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('ContributionResolver', () => { diff --git a/backend/src/graphql/resolver/EmailOptinCodes.test.ts b/backend/src/graphql/resolver/EmailOptinCodes.test.ts index da2181230..e408eb377 100644 --- a/backend/src/graphql/resolver/EmailOptinCodes.test.ts +++ b/backend/src/graphql/resolver/EmailOptinCodes.test.ts @@ -3,7 +3,6 @@ import { ApolloServerTestClient } from 'apollo-server-testing' import { CONFIG as CORE_CONFIG } from 'core' import { AppDatabase, User as DbUser } from 'database' import { GraphQLError } from 'graphql' -import { DataSource } from 'typeorm' import { CONFIG } from '@/config' import { writeHomeCommunityEntry } from '@/seeds/community' import { createUser, forgotPassword, setPassword } from '@/seeds/graphql/mutations' @@ -11,12 +10,10 @@ import { queryOptIn } from '@/seeds/graphql/queries' let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -28,15 +25,13 @@ beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('EmailOptinCodes', () => { diff --git a/backend/src/graphql/resolver/KlicktippResolver.test.ts b/backend/src/graphql/resolver/KlicktippResolver.test.ts index 8ad4e2447..58d60a54e 100644 --- a/backend/src/graphql/resolver/KlicktippResolver.test.ts +++ b/backend/src/graphql/resolver/KlicktippResolver.test.ts @@ -14,21 +14,18 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.Klicktip let testEnv: any let mutate: any -let con: any let db: AppDatabase beforeAll(async () => { testEnv = await testEnvironment(logger) mutate = testEnv.mutate - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('KlicktippResolver', () => { diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 6538dd89e..1a72eb741 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -48,12 +48,10 @@ CONFIG.DLT_ACTIVE = false let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -63,7 +61,6 @@ beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db await cleanDB() await userFactory(testEnv, bibiBloxberg) @@ -72,8 +69,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('TransactionLinkResolver', () => { diff --git a/backend/src/graphql/resolver/TransactionResolver.test.ts b/backend/src/graphql/resolver/TransactionResolver.test.ts index 3f6bb5047..982cb3612 100644 --- a/backend/src/graphql/resolver/TransactionResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionResolver.test.ts @@ -6,13 +6,10 @@ import { AppDatabase, Community as DbCommunity, Event as DbEvent, - FederatedCommunity as DbFederatedCommunity, - DltTransaction, Transaction, User, } from 'database' import { GraphQLError } from 'graphql' -import { DataSource, In } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { CONFIG } from '@/config' // import { CONFIG } from '@/config' @@ -43,11 +40,9 @@ CORE_CONFIG.EMAIL = false let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -55,14 +50,12 @@ beforeAll(async () => { testEnv = await testEnvironment(logger) mutate = testEnv.mutate query = testEnv.query - con = testEnv.con await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() // close() - await testEnv.db.getRedisClient().quit() + await testEnv.db.destroy() }) let bobData: any diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 792f8591b..7194e18ed 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -25,7 +25,6 @@ import { UserRole, } from 'database' import { GraphQLError } from 'graphql' -import { DataSource } from 'typeorm' import { v4 as uuidv4, validate as validateUUID, version as versionUUID } from 'uuid' import { subscribe } from '@/apis/KlicktippController' import { CONFIG } from '@/config' @@ -100,12 +99,10 @@ let admin: User let user: User let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } @@ -113,7 +110,6 @@ beforeAll(async () => { testEnv = await testEnvironment(getLogger('apollo')) mutate = testEnv.mutate query = testEnv.query - con = testEnv.con db = testEnv.db CONFIG.HUMHUB_ACTIVE = false CONFIG.DLT_ACTIVE = false @@ -122,8 +118,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('UserResolver', () => { diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index c2bdee9f8..e56580d0a 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -6,7 +6,6 @@ import { Decimal } from 'decimal.js-light' import { GraphQLError } from 'graphql' // import { TRANSACTIONS_LOCK } from 'database' import { Mutex } from 'redis-semaphore' -import { DataSource } from 'typeorm' import { v4 as uuidv4 } from 'uuid' import { CONFIG } from '@/config' import { creationFactory, nMonthsBefore } from '@/seeds/factory/creation' @@ -30,24 +29,20 @@ CONFIG.DLT_ACTIVE = false CORE_CONFIG.EMAIL = false let mutate: ApolloServerTestClient['mutate'] -let con: DataSource let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate - con = testEnv.con await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await testEnv.db.getRedisClient().quit() + await testEnv.db.destroy() }) type WorkData = { start: number; end: number } diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 0f5cc9fcb..bc1379294 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -1,7 +1,6 @@ import { cleanDB, contributionDateFormatter, testEnvironment } from '@test/helpers' import { ApolloServerTestClient } from 'apollo-server-testing' import { AppDatabase, Contribution, User } from 'database' -import { DataSource } from 'typeorm' import { CONFIG } from '@/config' import { userFactory } from '@/seeds/factory/user' @@ -16,27 +15,23 @@ jest.mock('@/password/EncryptorUtils') CONFIG.HUMHUB_ACTIVE = false let mutate: ApolloServerTestClient['mutate'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate - con = testEnv.con db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) const setZeroHours = (date: Date): Date => { diff --git a/backend/src/util/klicktipp.test.ts b/backend/src/util/klicktipp.test.ts index 0368a58b3..bb53f628a 100644 --- a/backend/src/util/klicktipp.test.ts +++ b/backend/src/util/klicktipp.test.ts @@ -1,7 +1,6 @@ import { cleanDB, resetToken, testEnvironment } from '@test/helpers' import { ApolloServerTestClient } from 'apollo-server-testing' import { AppDatabase, Event as DbEvent } from 'database' -import { DataSource } from 'typeorm' import { addFieldsToSubscriber } from '@/apis/KlicktippController' import { creations } from '@/seeds/creation' @@ -17,27 +16,23 @@ jest.mock('@/apis/KlicktippController') jest.mock('@/password/EncryptorUtils') let mutate: ApolloServerTestClient['mutate'] -let con: DataSource let db: AppDatabase let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] - con: DataSource db: AppDatabase } beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate - con = testEnv.con db = testEnv.db await DbEvent.clear() }) afterAll(async () => { await cleanDB() - await con.destroy() - await db.getRedisClient().quit() + await db.destroy() }) describe('klicktipp', () => { diff --git a/bun.lock b/bun.lock index 4e45e053b..9a4a4c2bb 100644 --- a/bun.lock +++ b/bun.lock @@ -101,6 +101,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.24", "@swc/helpers": "^0.5.17", + "@swc/jest": "^0.2.38", "@types/cors": "^2.8.19", "@types/email-templates": "^10.0.4", "@types/express": "^4.17.21", @@ -226,6 +227,7 @@ "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", "dotenv": "^10.0.0", + "drizzle-orm": "^0.44.7", "esbuild": "^0.25.2", "geojson": "^0.5.0", "ioredis": "^5.8.2", @@ -247,6 +249,7 @@ "@types/geojson": "^7946.0.13", "@types/mysql": "^2.15.27", "@types/node": "^18.7.14", + "drizzle-kit": "^0.31.7", "random-bigint": "^0.0.1", "ts-node": "^10.9.2", "typescript": "^4.9.5", @@ -304,6 +307,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.24", "@swc/helpers": "^0.5.17", + "@swc/jest": "^0.2.38", "@types/cors": "^2.8.19", "@types/express": "4.17.21", "@types/jest": "27.0.2", @@ -660,8 +664,14 @@ "@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], + "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], + "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], + + "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -1892,6 +1902,10 @@ "dotenv-webpack": ["dotenv-webpack@7.1.1", "", { "dependencies": { "dotenv-defaults": "^2.0.2" }, "peerDependencies": { "webpack": "^4 || ^5" } }, "sha512-xw/19VqHDkXALtBOJAnnrSU/AZDIQRXczAmJyp0lZv6SH2aBLzUTl96W1MVryJZ7okZ+djZS4Gj4KlZ0xP7deA=="], + "drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="], + + "drizzle-orm": ["drizzle-orm@0.44.7", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-quIpnYznjU9lHshEOAYLoZ9s3jweleHlZIAWR/jX9gAWNg/JhQ1wj0KGRf7/Zm+obRrYd9GjPVJg790QY9N5AQ=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], @@ -1946,6 +1960,8 @@ "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-goat": ["escape-goat@3.0.0", "", {}, "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="], @@ -3622,6 +3638,8 @@ "@csstools/selector-specificity/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], + "@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@hapi/boom/@hapi/hoek": ["@hapi/hoek@11.0.7", "", {}, "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ=="], @@ -4304,6 +4322,50 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], + + "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], + "@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], diff --git a/database/drizzle.config.ts b/database/drizzle.config.ts new file mode 100644 index 000000000..0df85f9de --- /dev/null +++ b/database/drizzle.config.ts @@ -0,0 +1,17 @@ +import 'dotenv/config' +import { defineConfig } from 'drizzle-kit' +import { CONFIG } from './src/config' + +export default defineConfig({ + out: './drizzle', + schema: './src/schemas/drizzle.schema.ts', + dialect: 'mysql', + dbCredentials: { + // url: process.env.DATABASE_URL!, + host: CONFIG.DB_HOST, + user: CONFIG.DB_USER, + password: CONFIG.DB_PASSWORD.length > 0 ? CONFIG.DB_PASSWORD : undefined, + database: CONFIG.DB_DATABASE, + port: CONFIG.DB_PORT, + }, +}) \ No newline at end of file diff --git a/database/package.json b/database/package.json index 7f35c7e98..ae578809c 100644 --- a/database/package.json +++ b/database/package.json @@ -37,6 +37,7 @@ "@types/geojson": "^7946.0.13", "@types/mysql": "^2.15.27", "@types/node": "^18.7.14", + "drizzle-kit": "^0.31.7", "random-bigint": "^0.0.1", "ts-node": "^10.9.2", "typescript": "^4.9.5" @@ -46,6 +47,7 @@ "cross-env": "^7.0.3", "decimal.js-light": "^2.5.1", "dotenv": "^10.0.0", + "drizzle-orm": "^0.44.7", "esbuild": "^0.25.2", "geojson": "^0.5.0", "ioredis": "^5.8.2", diff --git a/database/src/AppDatabase.ts b/database/src/AppDatabase.ts index 6357b8a3c..e35043991 100644 --- a/database/src/AppDatabase.ts +++ b/database/src/AppDatabase.ts @@ -1,5 +1,7 @@ +import { drizzle, MySql2Database } from 'drizzle-orm/mysql2' import Redis from 'ioredis' import { getLogger } from 'log4js' +import { Connection, createConnection } from 'mysql2/promise' import { DataSource as DBDataSource, FileLogger } from 'typeorm' import { latestDbVersion } from '.' import { CONFIG } from './config' @@ -11,6 +13,8 @@ const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.AppDatabase`) export class AppDatabase { private static instance: AppDatabase private dataSource: DBDataSource | undefined + private drizzleDataSource: MySql2Database | undefined + private drizzleConnection: Connection | undefined private redisClient: Redis | undefined /** @@ -43,6 +47,13 @@ export class AppDatabase { return this.dataSource } + public getDrizzleDataSource(): MySql2Database { + if (!this.drizzleDataSource) { + throw new Error('Drizzle connection not initialized') + } + return this.drizzleDataSource + } + // create database connection, initialize with automatic retry and check for correct database version public async init(): Promise { if (this.dataSource?.isInitialized) { @@ -71,6 +82,7 @@ export class AppDatabase { }, }) } + // retry connection on failure some times to allow database to catch up for (let attempt = 1; attempt <= CONFIG.DB_CONNECT_RETRY_COUNT; attempt++) { try { @@ -92,10 +104,24 @@ export class AppDatabase { this.redisClient = new Redis(CONFIG.REDIS_URL) logger.info('Redis status=', this.redisClient.status) + + if (!this.drizzleDataSource) { + this.drizzleConnection = await createConnection({ + host: CONFIG.DB_HOST, + user: CONFIG.DB_USER, + password: CONFIG.DB_PASSWORD, + database: CONFIG.DB_DATABASE, + port: CONFIG.DB_PORT, + }) + this.drizzleDataSource = drizzle({ client: this.drizzleConnection }) + } } public async destroy(): Promise { - await this.dataSource?.destroy() + await Promise.all([this.dataSource?.destroy(), this.drizzleConnection?.end()]) + this.dataSource = undefined + this.drizzleConnection = undefined + this.drizzleDataSource = undefined if (this.redisClient) { await this.redisClient.quit() this.redisClient = undefined @@ -128,3 +154,4 @@ export class AppDatabase { } export const getDataSource = () => AppDatabase.getInstance().getDataSource() +export const drizzleDb = () => AppDatabase.getInstance().getDrizzleDataSource() diff --git a/database/src/config/index.ts b/database/src/config/index.ts index cac1f8e44..3f7e24787 100644 --- a/database/src/config/index.ts +++ b/database/src/config/index.ts @@ -13,7 +13,7 @@ const database = { 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_HOST: process.env.DB_HOST ?? '127.0.0.1', 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 ?? '', diff --git a/database/src/entity/OpenaiThreads.ts b/database/src/entity/OpenaiThreads.ts deleted file mode 100644 index 09eb26908..000000000 --- a/database/src/entity/OpenaiThreads.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - BaseEntity, - Column, - CreateDateColumn, - Entity, - PrimaryColumn, - UpdateDateColumn, -} from 'typeorm' - -@Entity('openai_threads') -export class OpenaiThreads extends BaseEntity { - @PrimaryColumn({ type: 'char', length: 30 }) - id: string - - @CreateDateColumn({ type: 'timestamp' }) - createdAt: Date - - @UpdateDateColumn({ type: 'timestamp' }) - updatedAt: Date - - @Column({ name: 'user_id', type: 'int', unsigned: true }) - userId: number -} diff --git a/database/src/entity/index.ts b/database/src/entity/index.ts index f700e64d3..d2534af86 100644 --- a/database/src/entity/index.ts +++ b/database/src/entity/index.ts @@ -8,7 +8,6 @@ import { Event } from './Event' import { FederatedCommunity } from './FederatedCommunity' import { LoginElopageBuys } from './LoginElopageBuys' import { Migration } from './Migration' -import { OpenaiThreads } from './OpenaiThreads' import { PendingTransaction } from './PendingTransaction' import { ProjectBranding } from './ProjectBranding' import { Transaction } from './Transaction' @@ -29,7 +28,6 @@ export { LoginElopageBuys, Migration, ProjectBranding, - OpenaiThreads, PendingTransaction, Transaction, TransactionLink, @@ -50,7 +48,6 @@ export const entities = [ LoginElopageBuys, Migration, ProjectBranding, - OpenaiThreads, PendingTransaction, Transaction, TransactionLink, diff --git a/database/src/index.ts b/database/src/index.ts index 5276b6ac8..37cd476a2 100644 --- a/database/src/index.ts +++ b/database/src/index.ts @@ -6,4 +6,5 @@ export * from './entity' export * from './enum' export * from './logging' export * from './queries' +export * from './schemas' export * from './seeds' diff --git a/database/src/queries/index.ts b/database/src/queries/index.ts index 112f82466..0a7eb9ac3 100644 --- a/database/src/queries/index.ts +++ b/database/src/queries/index.ts @@ -3,6 +3,7 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../config/const' export * from './communities' export * from './communityHandshakes' export * from './events' +export * from './openaiThreads' export * from './pendingTransactions' export * from './transactionLinks' export * from './transactions' diff --git a/database/src/queries/openaiThreads.test.ts b/database/src/queries/openaiThreads.test.ts new file mode 100644 index 000000000..f6eb94c6e --- /dev/null +++ b/database/src/queries/openaiThreads.test.ts @@ -0,0 +1,70 @@ +import { eq } from 'drizzle-orm' +import { MySql2Database } from 'drizzle-orm/mysql2' +import { AppDatabase, drizzleDb } from '../AppDatabase' +import { openaiThreadsTable } from '../schemas' +import { + dbDeleteOpenaiThread, + dbFindNewestCreatedOpenaiThreadByUserId, + dbInsertOpenaiThread, + dbUpdateOpenaiThread, +} from './openaiThreads' + +const appDB = AppDatabase.getInstance() +let db: MySql2Database + +beforeAll(async () => { + await appDB.init() + db = drizzleDb() + await db.delete(openaiThreadsTable) +}) +afterAll(async () => { + await appDB.destroy() +}) + +describe('openaiThreads query test', () => { + it('should insert a new openai thread', async () => { + await Promise.resolve([dbInsertOpenaiThread('7', 1), dbInsertOpenaiThread('72', 6)]) + const result = await db.select().from(openaiThreadsTable) + expect(result).toHaveLength(2) + expect(result).toMatchObject([ + { id: '7', userId: 1 }, + { id: '72', userId: 6 }, + ]) + }) + + it('should find the newest created openai thread by user id', async () => { + await db.insert(openaiThreadsTable).values([ + { id: '75', userId: 2, createdAt: new Date('2025-01-01T00:00:00.000Z') }, + { id: '172', userId: 2, createdAt: new Date('2025-01-02T00:00:00.000Z') }, + ]) + const result = await dbFindNewestCreatedOpenaiThreadByUserId(2) + expect(result).toBeDefined() + expect(result).toMatchObject({ + id: '172', + userId: 2, + createdAt: new Date('2025-01-02T00:00:00.000Z'), + }) + }) + + it('should update an existing openai thread', async () => { + const now = new Date() + now.setMilliseconds(0) + await dbUpdateOpenaiThread('172') + const result = await db + .select() + .from(openaiThreadsTable) + .where(eq(openaiThreadsTable.id, '172')) + expect(result).toHaveLength(1) + expect(result[0].updatedAt.getTime()).toBeGreaterThanOrEqual(now.getTime()) + expect(result).toMatchObject([{ id: '172', userId: 2, updatedAt: expect.any(Date) }]) + }) + + it('should delete an existing openai thread', async () => { + await dbDeleteOpenaiThread('172') + const result = await db + .select() + .from(openaiThreadsTable) + .where(eq(openaiThreadsTable.id, '172')) + expect(result).toHaveLength(0) + }) +}) diff --git a/database/src/queries/openaiThreads.ts b/database/src/queries/openaiThreads.ts new file mode 100644 index 000000000..ba3a0b7e5 --- /dev/null +++ b/database/src/queries/openaiThreads.ts @@ -0,0 +1,32 @@ +import { desc, eq } from 'drizzle-orm' +import { drizzleDb } from '../AppDatabase' +import { openaiThreadsTable } from '../schemas/drizzle.schema' + +// TODO: replace results with valibot schema after update to typescript 5 is possible + +export async function dbInsertOpenaiThread(id: string, userId: number): Promise { + await drizzleDb().insert(openaiThreadsTable).values({ id, userId }) +} + +export async function dbUpdateOpenaiThread(id: string): Promise { + await drizzleDb() + .update(openaiThreadsTable) + .set({ updatedAt: new Date() }) + .where(eq(openaiThreadsTable.id, id)) +} + +export async function dbFindNewestCreatedOpenaiThreadByUserId( + userId: number, +): Promise { + const result = await drizzleDb() + .select() + .from(openaiThreadsTable) + .where(eq(openaiThreadsTable.userId, userId)) + .orderBy(desc(openaiThreadsTable.createdAt)) + .limit(1) + return result.at(0) +} + +export async function dbDeleteOpenaiThread(id: string): Promise { + await drizzleDb().delete(openaiThreadsTable).where(eq(openaiThreadsTable.id, id)) +} diff --git a/database/src/schemas/drizzle.schema.ts b/database/src/schemas/drizzle.schema.ts new file mode 100644 index 000000000..3dabc98e8 --- /dev/null +++ b/database/src/schemas/drizzle.schema.ts @@ -0,0 +1,8 @@ +import { int, mysqlTable, timestamp, varchar } from 'drizzle-orm/mysql-core' + +export const openaiThreadsTable = mysqlTable('openai_threads', { + id: varchar({ length: 128 }).notNull(), + createdAt: timestamp({ mode: 'date' }).defaultNow().notNull(), + updatedAt: timestamp({ mode: 'date' }).defaultNow().onUpdateNow().notNull(), + userId: int('user_id').notNull(), +}) diff --git a/database/src/schemas/index.ts b/database/src/schemas/index.ts new file mode 100644 index 000000000..dfe6f0bcd --- /dev/null +++ b/database/src/schemas/index.ts @@ -0,0 +1 @@ +export * from './drizzle.schema' diff --git a/dht-node/jest.config.js b/dht-node/jest.config.js index fe5fb923d..e6008da5a 100644 --- a/dht-node/jest.config.js +++ b/dht-node/jest.config.js @@ -19,4 +19,7 @@ module.exports = { transform: { '^.+\\.(t|j)sx?$': '@swc/jest', }, + transformIgnorePatterns: [ + '/node_modules/(?!drizzle-orm/)', + ], } diff --git a/dht-node/src/dht_node/index.test.ts b/dht-node/src/dht_node/index.test.ts index 36a941dbf..9972630d3 100644 --- a/dht-node/src/dht_node/index.test.ts +++ b/dht-node/src/dht_node/index.test.ts @@ -1,7 +1,11 @@ import DHT from '@hyperswarm/dht' import { cleanDB, testEnvironment } from '@test/helpers' import { getLogger } from 'config-schema/test/testSetup' -import { Community as DbCommunity, FederatedCommunity as DbFederatedCommunity } from 'database' +import { + AppDatabase, + Community as DbCommunity, + FederatedCommunity as DbFederatedCommunity, +} from 'database' import { validate as validateUUID, version as versionUUID } from 'uuid' import { CONFIG } from '@/config' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' @@ -94,18 +98,18 @@ DHT.mockImplementation(() => { } }) -let con: any +let db: AppDatabase let testEnv: any beforeAll(async () => { testEnv = await testEnvironment() - con = testEnv.con + db = testEnv.db await cleanDB() }) afterAll(async () => { await cleanDB() - await con.close() + await db.destroy() }) describe('federation', () => { diff --git a/dht-node/test/helpers.ts b/dht-node/test/helpers.ts index 5978f7bb9..bf2953964 100644 --- a/dht-node/test/helpers.ts +++ b/dht-node/test/helpers.ts @@ -25,7 +25,7 @@ export const cleanDB = async () => { export const testEnvironment = async () => { const appDB = AppDatabase.getInstance() await appDB.init() - return { con: appDB.getDataSource() } + return { con: appDB.getDataSource(), db: appDB } } export const resetEntity = async (entity: any) => { diff --git a/federation/jest.config.js b/federation/jest.config.js index 9766c8848..8a2cbf281 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -20,21 +20,22 @@ module.exports = { '@union/(.*)': '/src/graphql/union/$1', '@repository/(.*)': '/src/typeorm/repository/$1', '@test/(.*)': '/test/$1', - '@entity/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/entity/$1' - : '/../database/build/entity/$1', - '@logging/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/logging/$1' - : '/../database/build/logging/$1', - '@dbTools/(.*)': - process.env.NODE_ENV === 'development' - ? '/../database/src/$1' - : '/../database/build/src/$1', - '@config/(.*)': - process.env.NODE_ENV === 'development' - ? '/../config/src/$1' - : '/../config/build/$1', }, + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest', { + jsc: { + parser: { + syntax: 'typescript', + decorators: true, + tsc: true, + }, + transform: { + decoratorMetadata: true, + }, + } + }], + }, + transformIgnorePatterns: [ + '/node_modules/(?!drizzle-orm/)', + ], } diff --git a/federation/package.json b/federation/package.json index cba91ef11..c276929ae 100644 --- a/federation/package.json +++ b/federation/package.json @@ -33,6 +33,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.24", "@swc/helpers": "^0.5.17", + "@swc/jest": "^0.2.38", "@types/cors": "^2.8.19", "@types/express": "4.17.21", "@types/jest": "27.0.2", diff --git a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts index c2e0b5c26..a580106c9 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicCommunityInfoResolver.test.ts @@ -1,26 +1,21 @@ import { createTestClient } from 'apollo-server-testing' -import { Community as DbCommunity } from 'database' +import { AppDatabase, Community as DbCommunity } from 'database' import { getLogger } from 'log4js' -import { DataSource } from 'typeorm' import { CONFIG } from '@/config' import { createServer } from '@/server/createServer' let query: any -// to do: We need a setup for the tests that closes the connection -let con: DataSource - CONFIG.FEDERATION_API = '1_0' beforeAll(async () => { const server = await createServer(getLogger('apollo')) - con = server.con query = createTestClient(server.apollo).query DbCommunity.clear() }) afterAll(async () => { - await con.close() + await AppDatabase.getInstance().destroy() }) describe('PublicCommunityInfoResolver', () => { diff --git a/federation/src/graphql/api/1_0/resolver/PublicKeyResolver.test.ts b/federation/src/graphql/api/1_0/resolver/PublicKeyResolver.test.ts index b72051b9e..6adfbeff3 100644 --- a/federation/src/graphql/api/1_0/resolver/PublicKeyResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/PublicKeyResolver.test.ts @@ -1,26 +1,23 @@ import { createTestClient } from 'apollo-server-testing' -import { FederatedCommunity as DbFederatedCommunity } from 'database' +import { AppDatabase, FederatedCommunity as DbFederatedCommunity } from 'database' import { getLogger } from 'log4js' import { CONFIG } from '@/config' -import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { createServer } from '@/server/createServer' let query: any // to do: We need a setup for the tests that closes the connection -let con: any CONFIG.FEDERATION_API = '1_0' beforeAll(async () => { const server = await createServer(getLogger('apollo')) - con = server.con query = createTestClient(server.apollo).query DbFederatedCommunity.clear() }) afterAll(async () => { - await con.close() + await AppDatabase.getInstance().destroy() }) describe('PublicKeyResolver', () => { diff --git a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts index bdea0ec9a..8e6f9e91a 100644 --- a/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/SendCoinsResolver.test.ts @@ -1,7 +1,12 @@ import { cleanDB, testEnvironment } from '@test/helpers' import { ApolloServerTestClient } from 'apollo-server-testing' import { EncryptedTransferArgs } from 'core' -import { Community as DbCommunity, User as DbUser, UserContact as DbUserContact } from 'database' +import { + AppDatabase, + Community as DbCommunity, + User as DbUser, + UserContact as DbUserContact, +} from 'database' import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' import { getLogger } from 'log4js' @@ -14,7 +19,6 @@ import { } from 'shared' import { DataSource } from 'typeorm' import { CONFIG } from '@/config' -import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { fullName } from '@/graphql/util/fullName' let mutate: ApolloServerTestClient['mutate'] // , con: Connection @@ -46,7 +50,7 @@ beforeAll(async () => { afterAll(async () => { await cleanDB() if (testEnv.con?.isInitialized) { - await testEnv.con.destroy() + await AppDatabase.getInstance().destroy() } }) diff --git a/federation/src/graphql/api/1_1/resolver/PublicKeyResolver.test.ts b/federation/src/graphql/api/1_1/resolver/PublicKeyResolver.test.ts index f28e297b9..f32ffc8c2 100644 --- a/federation/src/graphql/api/1_1/resolver/PublicKeyResolver.test.ts +++ b/federation/src/graphql/api/1_1/resolver/PublicKeyResolver.test.ts @@ -1,5 +1,5 @@ import { createTestClient } from 'apollo-server-testing' -import { FederatedCommunity as DbFederatedCommunity } from 'database' +import { AppDatabase, FederatedCommunity as DbFederatedCommunity } from 'database' import { getLogger } from 'log4js' import { CONFIG } from '@/config' import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' @@ -8,19 +8,17 @@ import { createServer } from '@/server/createServer' let query: any // to do: We need a setup for the tests that closes the connection -let con: any CONFIG.FEDERATION_API = '1_1' beforeAll(async () => { const server = await createServer(getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.apollo`)) - con = server.con query = createTestClient(server.apollo).query DbFederatedCommunity.clear() }) afterAll(async () => { - await con.close() + await AppDatabase.getInstance().destroy() }) describe('PublicKeyResolver', () => {