mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
♻️ refactor: remove client db and refactor test (#11123)
* ♻️ refactor: refactor to remove client db * remove tableViewer * ✅ tests: remove tests
This commit is contained in:
parent
bc44cba10a
commit
bb2799dc75
71 changed files with 116 additions and 1785 deletions
|
|
@ -42,8 +42,7 @@
|
|||
"build:electron": "cross-env NODE_OPTIONS=--max-old-space-size=8192 NEXT_PUBLIC_IS_DESKTOP_APP=1 tsx scripts/electronWorkflow/buildNextApp.mts",
|
||||
"build:vercel": "npm run prebuild && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack",
|
||||
"clean:node_modules": "bash -lc 'set -e; echo \"Removing all node_modules...\"; rm -rf node_modules; pnpm -r exec rm -rf node_modules; rm -rf apps/desktop/node_modules; echo \"All node_modules removed.\"'",
|
||||
"db:generate": "drizzle-kit generate && npm run db:generate-client && npm run workflow:dbml",
|
||||
"db:generate-client": "tsx ./scripts/migrateClientDB/compile-migrations.ts",
|
||||
"db:generate": "drizzle-kit generate && npm run workflow:dbml",
|
||||
"db:migrate": "MIGRATION_DB=1 tsx ./scripts/migrateServerDB/index.ts",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"db:visualize": "dbdocs build docs/development/database-schema.dbml --project lobe-chat",
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
|
||||
import { drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
|
||||
import * as migrator from 'drizzle-orm/neon-serverless/migrator';
|
||||
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
|
||||
import * as nodeMigrator from 'drizzle-orm/node-postgres/migrator';
|
||||
import { join } from 'node:path';
|
||||
import { Pool as NodePool } from 'pg';
|
||||
import ws from 'ws';
|
||||
|
||||
import { serverDBEnv } from '@/config/db';
|
||||
|
||||
import * as schema from '../schemas';
|
||||
|
||||
const migrationsFolder = join(__dirname, '../../migrations');
|
||||
|
||||
export const getTestDBInstance = async () => {
|
||||
let connectionString = serverDBEnv.DATABASE_TEST_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error(`You are try to use database, but "DATABASE_TEST_URL" is not set correctly`);
|
||||
}
|
||||
|
||||
if (serverDBEnv.DATABASE_DRIVER === 'node') {
|
||||
const client = new NodePool({ connectionString });
|
||||
|
||||
const db = nodeDrizzle(client, { schema });
|
||||
|
||||
await nodeMigrator.migrate(db, { migrationsFolder });
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
// https://github.com/neondatabase/serverless/blob/main/CONFIG.md#websocketconstructor-typeof-websocket--undefined
|
||||
neonConfig.webSocketConstructor = ws;
|
||||
|
||||
const client = new NeonPool({ connectionString });
|
||||
|
||||
const db = neonDrizzle(client, { schema });
|
||||
|
||||
await migrator.migrate(db, { migrationsFolder });
|
||||
|
||||
return db;
|
||||
};
|
||||
50
packages/database/src/core/getTestDB.ts
Normal file
50
packages/database/src/core/getTestDB.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { PGlite } from '@electric-sql/pglite';
|
||||
import { vector } from '@electric-sql/pglite/vector';
|
||||
import { drizzle as pgliteDrizzle } from 'drizzle-orm/pglite';
|
||||
import { migrate as pgliteMigrate } from 'drizzle-orm/pglite/migrator';
|
||||
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate as nodeMigrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import { join } from 'node:path';
|
||||
import { Pool as NodePool } from 'pg';
|
||||
|
||||
import { serverDBEnv } from '@/config/db';
|
||||
|
||||
import * as schema from '../schemas';
|
||||
import { LobeChatDatabase } from '../type';
|
||||
|
||||
const migrationsFolder = join(__dirname, '../../migrations');
|
||||
|
||||
const isServerDBMode = process.env.TEST_SERVER_DB === '1';
|
||||
|
||||
let testClientDB: ReturnType<typeof pgliteDrizzle<typeof schema>> | null = null;
|
||||
let testServerDB: ReturnType<typeof nodeDrizzle<typeof schema>> | null = null;
|
||||
|
||||
export const getTestDB = async (): Promise<LobeChatDatabase> => {
|
||||
// Server DB mode (node-postgres)
|
||||
if (isServerDBMode) {
|
||||
if (testServerDB) return testServerDB as unknown as LobeChatDatabase;
|
||||
|
||||
const connectionString = serverDBEnv.DATABASE_TEST_URL;
|
||||
|
||||
if (!connectionString) {
|
||||
throw new Error('DATABASE_TEST_URL is not set');
|
||||
}
|
||||
|
||||
const client = new NodePool({ connectionString });
|
||||
testServerDB = nodeDrizzle(client, { schema });
|
||||
|
||||
await nodeMigrate(testServerDB, { migrationsFolder });
|
||||
|
||||
return testServerDB as unknown as LobeChatDatabase;
|
||||
}
|
||||
|
||||
// Client DB mode (PGlite)
|
||||
if (testClientDB) return testClientDB as unknown as LobeChatDatabase;
|
||||
|
||||
const pglite = new PGlite({ extensions: { vector } });
|
||||
testClientDB = pgliteDrizzle({ client: pglite, schema });
|
||||
|
||||
await pgliteMigrate(testClientDB, { migrationsFolder });
|
||||
|
||||
return testClientDB as unknown as LobeChatDatabase;
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,10 +2,10 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { sessionGroups, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { SessionGroupModel } from '../sessionGroup';
|
||||
import { getTestDB } from './_util';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import { PGlite } from '@electric-sql/pglite';
|
||||
import { vector } from '@electric-sql/pglite/vector';
|
||||
import { drizzle } from 'drizzle-orm/pglite';
|
||||
|
||||
import migrations from '../../core/migrations.json';
|
||||
import * as schema from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
|
||||
const isServerDBMode = process.env.TEST_SERVER_DB === '1';
|
||||
|
||||
let testClientDB: ReturnType<typeof drizzle<typeof schema>> | null = null;
|
||||
|
||||
export const getTestDB = async () => {
|
||||
if (isServerDBMode) {
|
||||
const { getTestDBInstance } = await import('../../core/dbForTest');
|
||||
return await getTestDBInstance();
|
||||
}
|
||||
|
||||
if (testClientDB) return testClientDB as unknown as LobeChatDatabase;
|
||||
|
||||
// 直接使用 pglite 内置资源,不需要从 CDN 下载
|
||||
const pglite = new PGlite({ extensions: { vector } });
|
||||
|
||||
testClientDB = drizzle({ client: pglite, schema });
|
||||
|
||||
// @ts-expect-error - migrate internal API
|
||||
await testClientDB.dialect.migrate(migrations, testClientDB.session, {});
|
||||
|
||||
return testClientDB as unknown as LobeChatDatabase;
|
||||
};
|
||||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { AgentModel } from '../agent';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { AiModelModel } from '../aiModel';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { sleep } from '@/utils/sleep';
|
|||
import { aiProviders, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { AiProviderModel } from '../aiProvider';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { apiKeys, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { ApiKeyModel } from '../apiKey';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { asyncTasks, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { AsyncTaskModel } from '../asyncTask';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
users,
|
||||
} from '../../schemas';
|
||||
import { ChatGroupModel } from '../chatGroup';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const userId = 'test-user';
|
||||
const otherUserId = 'other-user';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { LobeChatDatabase } from '../../type';import { uuid } from '@/utils/uuid
|
|||
|
||||
import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../schemas';
|
||||
import { ChunkModel } from '../chunk';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { codeEmbedding, designThinkingQuery, designThinkingQuery2 } from './fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { documents, files, users } from '../../schemas';
|
|||
import { LobeChatDatabase } from '../../type';
|
||||
import { DocumentModel } from '../document';
|
||||
import { FileModel } from '../file';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest';
|
|||
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { DrizzleMigrationModel } from '../drizzleMigration';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { chunks, embeddings, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { EmbeddingModel } from '../embedding';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { designThinkingQuery } from './fixtures/embedding';
|
||||
|
||||
const userId = 'embedding-user-test';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { FileModel } from '../file';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { GenerationModel } from '../generation';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { GenerationBatchModel } from '../generationBatch';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { generationBatches, generationTopics, generations, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { GenerationTopicModel } from '../generationTopic';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
// Mock FileService
|
||||
const mockGetFullFileUrl = vi.fn();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { KnowledgeBaseModel } from '../knowledgeBase';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { codeEmbedding } from '../fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { codeEmbedding } from '../fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { codeEmbedding } from '../fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { uuid } from '@/utils/uuid';
|
|||
import { embeddings, files, messageQueries, messages, sessions, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { codeEmbedding } from '../fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { agents, messages, sessions, threads, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { codeEmbedding } from '../fixtures/embedding';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ThreadStatus, ThreadType } from '@/types/index';
|
|||
import { messages, sessions, threads, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'message-task-user-test';
|
||||
const sessionId = 'message-task-session';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { messageGroups, messages, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'message-query-perf-test-user';
|
||||
const topicId = 'perf-test-topic-1';
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { messageGroups, messages, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { MessageModel } from '../../message';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'message-query-test-user';
|
||||
const topicId = 'test-topic-1';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { oauthHandoffs } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { OAuthHandoffModel } from '../oauthHandoff';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
const oauthHandoffModel = new OAuthHandoffModel(serverDB);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { LobeChatDatabase } from '../../type';
|
||||
import { NewInstalledPlugin, userInstalledPlugins, users } from '../../schemas';
|
||||
import { PluginModel } from '../plugin';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { LobeChatDatabase } from '../../type';
|
||||
import { idGenerator } from '../../utils/idGenerator';
|
||||
import { SessionModel } from '../session';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { sessionGroups, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { SessionGroupModel } from '../sessionGroup';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { sessions, threads, topics, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { ThreadModel } from '../thread';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const userId = 'thread-user-test';
|
||||
const otherUserId = 'other-user-test';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { documents, sessions, topicDocuments, topics, users } from '../../schema
|
|||
import { LobeChatDatabase } from '../../type';
|
||||
import { DocumentModel } from '../document';
|
||||
import { TopicDocumentModel } from '../topicDocument';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { agents, messages, sessions, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { CreateTopicParams, TopicModel } from '../../topic';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'topic-create-user';
|
||||
const userId2 = 'topic-create-user-2';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { TopicModel } from '../../topic';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'topic-delete-user';
|
||||
const userId2 = 'topic-delete-user-2';
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { TopicModel } from '../../topic';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'topic-query-user';
|
||||
const userId2 = 'topic-query-user-2';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { agents, agentsToSessions, messages, sessions, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { TopicModel } from '../../topic';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'topic-stats-user';
|
||||
const userId2 = 'topic-stats-user-2';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
import { sessions, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { TopicModel } from '../../topic';
|
||||
import { getTestDB } from '../_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
|
||||
const userId = 'topic-update-user';
|
||||
const sessionId = 'topic-update-session';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
import { nextauthAccounts, userSettings, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { ListUsersForMemoryExtractorCursor, UserModel, UserNotFoundError } from '../user';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const userId = 'user-model-test';
|
||||
const otherUserId = 'other-user-test';
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import {
|
|||
CreateUserMemoryPreferenceParams,
|
||||
UserMemoryModel,
|
||||
} from '../userMemory';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
const serverDB: LobeChatDatabase = await getTestDB();
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { idGenerator } from '@/database/utils/idGenerator';
|
|||
import { userMemoriesIdentities, users } from '../../schemas';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { UserMemoryIdentityModel } from '../userMemory/identity';
|
||||
import { getTestDB } from './_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
|
||||
// Helper to generate unique identity IDs
|
||||
const genIdentityId = () => `mem_${nanoid(12)}`;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest';
|
|||
|
||||
import { NewUserMemoryContext, userMemories, userMemoriesContexts, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { getTestDB } from '../../__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { UserMemoryContextModel } from '../context';
|
||||
|
||||
const userId = 'context-test-user';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
users,
|
||||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { getTestDB } from '../../__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { UserMemoryExperienceModel } from '../experience';
|
||||
|
||||
const userId = 'experience-test-user';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
users,
|
||||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { getTestDB } from '../../__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { UserMemoryIdentityModel } from '../identity';
|
||||
|
||||
const userId = 'identity-test-user';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
users,
|
||||
} from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { getTestDB } from '../../__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { UserMemoryPreferenceModel } from '../preference';
|
||||
|
||||
const userId = 'preference-test-user';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { BUILTIN_AGENT_SLUGS } from '@lobechat/builtin-agents';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { agents } from '../../schemas/agent';
|
||||
import { chatGroups, chatGroupsAgents } from '../../schemas/chatGroup';
|
||||
import { users } from '../../schemas/user';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { agents, messages, sessions, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { AgentMigrationRepo } from '../index';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { AiProviderModelListItem, EnabledAiModel } from 'model-bank';
|
|||
import { DEFAULT_MODEL_PROVIDER_LIST } from 'model-bank/modelProviders';
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { AiInfraRepos } from './index';
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { MessageGroupType } from '@lobechat/types';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { messageGroups, messages } from '../../schemas/message';
|
||||
import { topics } from '../../schemas/topic';
|
||||
import { users } from '../../schemas/user';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import {
|
||||
agents,
|
||||
agentsKnowledgeBases,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { ImportPgDataStructure } from '@lobechat/types';
|
|||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import * as Schema from '../../../schemas';
|
||||
import { DataImporterRepos } from '../index';
|
||||
import agentsData from './fixtures/agents.json';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { ImporterEntryData } from '@lobechat/types';
|
|||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getTestDBInstance } from '@/database/core/dbForTest';
|
||||
import { getTestDB } from '../../../../core/getTestDB';
|
||||
import {
|
||||
agents,
|
||||
agentsToSessions,
|
||||
|
|
@ -19,7 +19,7 @@ import mockImportData from './fixtures/messages.json';
|
|||
|
||||
const CURRENT_CONFIG_VERSION = 7;
|
||||
|
||||
const serverDB = await getTestDBInstance();
|
||||
const serverDB = await getTestDB();
|
||||
|
||||
const userId = 'test-user-id';
|
||||
let importer: DataImporterRepos;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { eq } from 'drizzle-orm';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import * as Schema from '../../../schemas';
|
||||
import { HomeRepository } from '../index';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @vitest-environment node
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { NewAgent, agents } from '../../schemas/agent';
|
||||
import { NewChatGroup, chatGroups } from '../../schemas/chatGroup';
|
||||
import { agentsToSessions } from '../../schemas/relations';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { FilesTabs } from '@lobechat/types';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { NewDocument, documents } from '../../schemas/file';
|
||||
import { NewFile, files } from '../../schemas/file';
|
||||
import { users } from '../../schemas/user';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @vitest-environment node
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../core/getTestDB';
|
||||
import { NewAgent, agents } from '../../schemas/agent';
|
||||
import { NewFile, files } from '../../schemas/file';
|
||||
import { messages } from '../../schemas/message';
|
||||
|
|
|
|||
|
|
@ -1,255 +0,0 @@
|
|||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../models/__tests__/_util';
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
import { TableViewerRepo } from './index';
|
||||
|
||||
const userId = 'user-table-viewer';
|
||||
|
||||
// Mock database execution
|
||||
const mockExecute = vi.fn();
|
||||
const mockDB = {
|
||||
execute: mockExecute,
|
||||
};
|
||||
|
||||
let serverDB: LobeChatDatabase;
|
||||
let repo: TableViewerRepo;
|
||||
|
||||
beforeAll(async () => {
|
||||
serverDB = await getTestDB();
|
||||
repo = new TableViewerRepo(serverDB, userId);
|
||||
}, 30000);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('TableViewerRepo', () => {
|
||||
describe('getAllTables', () => {
|
||||
it('should handle custom schema', async () => {
|
||||
const result = await repo.getAllTables('custom_schema');
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTableDetails', () => {
|
||||
it('should return table column details', async () => {
|
||||
const tableName = 'test_table';
|
||||
const mockColumns = {
|
||||
rows: [
|
||||
{
|
||||
column_name: 'id',
|
||||
data_type: 'uuid',
|
||||
is_nullable: 'NO',
|
||||
column_default: null,
|
||||
is_primary_key: true,
|
||||
foreign_key: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockColumns);
|
||||
|
||||
const result = await testRepo.getTableDetails(tableName);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
name: 'id',
|
||||
type: 'uuid',
|
||||
nullable: false,
|
||||
defaultValue: null,
|
||||
isPrimaryKey: true,
|
||||
foreignKey: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTableData', () => {
|
||||
it('should return paginated data with filters', async () => {
|
||||
const tableName = 'test_table';
|
||||
const pagination = {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
sortBy: 'id',
|
||||
sortOrder: 'desc' as const,
|
||||
};
|
||||
const filters = [
|
||||
{
|
||||
column: 'name',
|
||||
operator: 'contains' as const,
|
||||
value: 'test',
|
||||
},
|
||||
];
|
||||
|
||||
const mockData = {
|
||||
rows: [{ id: 1, name: 'test' }],
|
||||
};
|
||||
const mockCount = {
|
||||
rows: [{ total: 1 }],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockData).mockResolvedValueOnce(mockCount);
|
||||
|
||||
const result = await testRepo.getTableData(tableName, pagination, filters);
|
||||
|
||||
expect(result).toEqual({
|
||||
data: mockData.rows,
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
total: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRow', () => {
|
||||
it('should update and return row data', async () => {
|
||||
const tableName = 'test_table';
|
||||
const id = '123';
|
||||
const primaryKeyColumn = 'id';
|
||||
const data = { name: 'updated' };
|
||||
|
||||
const mockResult = {
|
||||
rows: [{ id: '123', name: 'updated' }],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockResult);
|
||||
|
||||
const result = await testRepo.updateRow(tableName, id, primaryKeyColumn, data);
|
||||
|
||||
expect(result).toEqual(mockResult.rows[0]);
|
||||
expect(mockExecute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteRow', () => {
|
||||
it('should delete a row', async () => {
|
||||
const tableName = 'test_table';
|
||||
const id = '123';
|
||||
const primaryKeyColumn = 'id';
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce({ rows: [] });
|
||||
|
||||
await testRepo.deleteRow(tableName, id, primaryKeyColumn);
|
||||
|
||||
expect(mockExecute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertRow', () => {
|
||||
it('should insert and return new row data', async () => {
|
||||
const tableName = 'test_table';
|
||||
const data = { name: 'new row' };
|
||||
|
||||
const mockResult = {
|
||||
rows: [{ id: '123', name: 'new row' }],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockResult);
|
||||
|
||||
const result = await testRepo.insertRow(tableName, data);
|
||||
|
||||
expect(result).toEqual(mockResult.rows[0]);
|
||||
expect(mockExecute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTableCount', () => {
|
||||
it('should return table count', async () => {
|
||||
const tableName = 'test_table';
|
||||
const mockResult = {
|
||||
rows: [{ total: 42 }],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockResult);
|
||||
|
||||
const result = await testRepo.getTableCount(tableName);
|
||||
|
||||
expect(result).toBe(42);
|
||||
expect(mockExecute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchDelete', () => {
|
||||
it('should delete multiple rows', async () => {
|
||||
const tableName = 'test_table';
|
||||
const ids = ['1', '2', '3'];
|
||||
const primaryKeyColumn = 'id';
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce({ rows: [] });
|
||||
|
||||
await testRepo.batchDelete(tableName, ids, primaryKeyColumn);
|
||||
|
||||
expect(mockExecute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('exportTableData', () => {
|
||||
it('should export table data with default pagination', async () => {
|
||||
const tableName = 'test_table';
|
||||
const mockData = {
|
||||
rows: [{ id: 1, name: 'test' }],
|
||||
};
|
||||
const mockCount = {
|
||||
rows: [{ total: 1 }],
|
||||
};
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
mockExecute.mockResolvedValueOnce(mockData).mockResolvedValueOnce(mockCount);
|
||||
|
||||
const result = await testRepo.exportTableData(tableName);
|
||||
|
||||
expect(result).toEqual({
|
||||
data: mockData.rows,
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 1000,
|
||||
total: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should export table data with custom pagination and filters', async () => {
|
||||
const tableName = 'test_table';
|
||||
const pagination = { page: 2, pageSize: 50 };
|
||||
const filters = [
|
||||
{
|
||||
column: 'status',
|
||||
operator: 'equals' as const,
|
||||
value: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
const mockData = {
|
||||
rows: [{ id: 1, status: 'active' }],
|
||||
};
|
||||
const mockCount = {
|
||||
rows: [{ total: 1 }],
|
||||
};
|
||||
|
||||
mockExecute.mockResolvedValueOnce(mockData).mockResolvedValueOnce(mockCount);
|
||||
|
||||
const testRepo = new TableViewerRepo(mockDB as any, userId);
|
||||
|
||||
const result = await testRepo.exportTableData(tableName, pagination, filters);
|
||||
|
||||
expect(result).toEqual({
|
||||
data: mockData.rows,
|
||||
pagination: {
|
||||
page: 2,
|
||||
pageSize: 50,
|
||||
total: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
import type {
|
||||
FilterCondition,
|
||||
PaginationParams,
|
||||
TableBasicInfo,
|
||||
TableColumnInfo,
|
||||
} from '@lobechat/types';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import { LobeChatDatabase } from '../../type';
|
||||
|
||||
export class TableViewerRepo {
|
||||
private userId: string;
|
||||
private db: LobeChatDatabase;
|
||||
|
||||
constructor(db: LobeChatDatabase, userId: string) {
|
||||
this.userId = userId;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tables in the database
|
||||
*/
|
||||
async getAllTables(schema = 'public'): Promise<TableBasicInfo[]> {
|
||||
const query = sql`
|
||||
SELECT
|
||||
table_name as name,
|
||||
table_type as type
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = ${schema}
|
||||
ORDER BY table_name;
|
||||
`;
|
||||
|
||||
const tables = await this.db.execute(query);
|
||||
|
||||
const tableNames = tables.rows.map((row) => row.name) as string[];
|
||||
|
||||
const counts = await pMap(tableNames, async (name) => this.getTableCount(name), {
|
||||
concurrency: 10,
|
||||
});
|
||||
|
||||
return tables.rows.map((row, index) => ({
|
||||
count: counts[index],
|
||||
name: row.name,
|
||||
type: row.type,
|
||||
})) as TableBasicInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed structure information for a specified table
|
||||
*/
|
||||
async getTableDetails(tableName: string): Promise<TableColumnInfo[]> {
|
||||
const query = sql`
|
||||
SELECT
|
||||
c.column_name,
|
||||
c.data_type,
|
||||
c.is_nullable,
|
||||
c.column_default,
|
||||
-- Primary key information
|
||||
(
|
||||
SELECT true
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
WHERE tc.table_name = c.table_name
|
||||
AND kcu.column_name = c.column_name
|
||||
AND tc.constraint_type = 'PRIMARY KEY'
|
||||
) is_primary_key,
|
||||
-- Foreign key information
|
||||
(
|
||||
SELECT json_build_object(
|
||||
'table', ccu.table_name,
|
||||
'column', ccu.column_name
|
||||
)
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
JOIN information_schema.constraint_column_usage ccu
|
||||
ON ccu.constraint_name = tc.constraint_name
|
||||
WHERE tc.table_name = c.table_name
|
||||
AND kcu.column_name = c.column_name
|
||||
AND tc.constraint_type = 'FOREIGN KEY'
|
||||
) foreign_key
|
||||
FROM information_schema.columns c
|
||||
WHERE c.table_name = ${tableName}
|
||||
AND c.table_schema = 'public'
|
||||
ORDER BY c.ordinal_position;
|
||||
`;
|
||||
|
||||
const columns = await this.db.execute(query);
|
||||
|
||||
return columns.rows.map((col: any) => ({
|
||||
defaultValue: col.column_default,
|
||||
foreignKey: col.foreign_key,
|
||||
isPrimaryKey: !!col.is_primary_key,
|
||||
name: col.column_name,
|
||||
nullable: col.is_nullable === 'YES',
|
||||
type: col.data_type,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table data with support for pagination, sorting, and filtering
|
||||
*/
|
||||
async getTableData(tableName: string, pagination: PaginationParams, filters?: FilterCondition[]) {
|
||||
const offset = (pagination.page - 1) * pagination.pageSize;
|
||||
|
||||
// Build base query
|
||||
let baseQuery = sql`SELECT * FROM ${sql.identifier(tableName)}`;
|
||||
|
||||
// Add filter conditions
|
||||
if (filters && filters.length > 0) {
|
||||
const whereConditions = filters.map((filter) => {
|
||||
const column = sql.identifier(filter.column);
|
||||
|
||||
switch (filter.operator) {
|
||||
case 'equals': {
|
||||
return sql`${column} = ${filter.value}`;
|
||||
}
|
||||
case 'contains': {
|
||||
return sql`${column} ILIKE ${`%${filter.value}%`}`;
|
||||
}
|
||||
case 'startsWith': {
|
||||
return sql`${column} ILIKE ${`${filter.value}%`}`;
|
||||
}
|
||||
case 'endsWith': {
|
||||
return sql`${column} ILIKE ${`%${filter.value}`}`;
|
||||
}
|
||||
default: {
|
||||
return sql`1=1`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
baseQuery = sql`${baseQuery} WHERE ${sql.join(whereConditions, sql` AND `)}`;
|
||||
}
|
||||
|
||||
// Add sorting
|
||||
if (pagination.sortBy) {
|
||||
const direction = pagination.sortOrder === 'desc' ? sql`DESC` : sql`ASC`;
|
||||
baseQuery = sql`${baseQuery} ORDER BY ${sql.identifier(pagination.sortBy)} ${direction}`;
|
||||
}
|
||||
|
||||
// Add pagination
|
||||
const query = sql`${baseQuery} LIMIT ${pagination.pageSize} OFFSET ${offset}`;
|
||||
|
||||
// Get total count
|
||||
const countQuery = sql`SELECT COUNT(*) as total FROM ${sql.identifier(tableName)}`;
|
||||
|
||||
// Execute queries in parallel
|
||||
const [data, count] = await Promise.all([this.db.execute(query), this.db.execute(countQuery)]);
|
||||
|
||||
return {
|
||||
data: data.rows,
|
||||
pagination: {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
total: Number(count.rows[0].total),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a row in the table
|
||||
*/
|
||||
async updateRow(
|
||||
tableName: string,
|
||||
id: string,
|
||||
primaryKeyColumn: string,
|
||||
data: Record<string, any>,
|
||||
) {
|
||||
const setColumns = Object.entries(data).map(([key, value]) => {
|
||||
return sql`${sql.identifier(key)} = ${value}`;
|
||||
});
|
||||
|
||||
const query = sql`
|
||||
UPDATE ${sql.identifier(tableName)}
|
||||
SET ${sql.join(setColumns, sql`, `)}
|
||||
WHERE ${sql.identifier(primaryKeyColumn)} = ${id}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await this.db.execute(query);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a row from the table
|
||||
*/
|
||||
async deleteRow(tableName: string, id: string, primaryKeyColumn: string) {
|
||||
const query = sql`
|
||||
DELETE FROM ${sql.identifier(tableName)}
|
||||
WHERE ${sql.identifier(primaryKeyColumn)} = ${id}
|
||||
`;
|
||||
|
||||
await this.db.execute(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new row data
|
||||
*/
|
||||
async insertRow(tableName: string, data: Record<string, any>) {
|
||||
const columns = Object.keys(data).map((key) => sql.identifier(key));
|
||||
const values = Object.values(data);
|
||||
|
||||
const query = sql`
|
||||
INSERT INTO ${sql.identifier(tableName)}
|
||||
(${sql.join(columns, sql`, `)})
|
||||
VALUES (${sql.join(
|
||||
values.map((v) => sql`${v}`),
|
||||
sql`, `,
|
||||
)})
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await this.db.execute(query);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total record count of a table
|
||||
*/
|
||||
async getTableCount(tableName: string): Promise<number> {
|
||||
const query = sql`SELECT COUNT(*) as total FROM ${sql.identifier(tableName)}`;
|
||||
const result = await this.db.execute(query);
|
||||
return Number(result.rows[0].total);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch delete data
|
||||
*/
|
||||
async batchDelete(tableName: string, ids: string[], primaryKeyColumn: string) {
|
||||
const query = sql`
|
||||
DELETE FROM ${sql.identifier(tableName)}
|
||||
WHERE ${sql.identifier(primaryKeyColumn)} = ANY(${ids})
|
||||
`;
|
||||
|
||||
await this.db.execute(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export table data (supports paginated export)
|
||||
*/
|
||||
async exportTableData(
|
||||
tableName: string,
|
||||
pagination?: PaginationParams,
|
||||
filters?: FilterCondition[],
|
||||
) {
|
||||
return this.getTableData(tableName, pagination || { page: 1, pageSize: 1000 }, filters);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
|
|||
import path from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { agents, messagePlugins, messages, topics, users } from '../../../schemas';
|
||||
import { LobeChatDatabase } from '../../../type';
|
||||
import { TopicImporterRepo } from '../index';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// @vitest-environment node
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { getTestDB } from '../../../models/__tests__/_util';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { messages } from '../../../schemas/message';
|
||||
import { topics } from '../../../schemas/topic';
|
||||
import { users } from '../../../schemas/user';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|||
|
||||
import { DrizzleAdapter } from '@/libs/oidc-provider/adapter';
|
||||
|
||||
import { getTestDBInstance } from '../../../core/dbForTest';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { users } from '../../../schemas';
|
||||
import {
|
||||
oidcAccessTokens,
|
||||
|
|
@ -14,7 +14,7 @@ import {
|
|||
oidcSessions,
|
||||
} from '../../../schemas/oidc';
|
||||
|
||||
let serverDB = await getTestDBInstance();
|
||||
let serverDB = await getTestDB();
|
||||
|
||||
// Test data
|
||||
const testModelName = 'Session';
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|||
|
||||
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
||||
|
||||
import { getTestDBInstance } from '../../../core/dbForTest';
|
||||
import { getTestDB } from '../../../core/getTestDB';
|
||||
import { SessionModel } from '../../../models/session';
|
||||
import { UserModel, UserNotFoundError } from '../../../models/user';
|
||||
import { UserSettingsItem, nextauthAccounts, userSettings, users } from '../../../schemas';
|
||||
|
||||
let serverDB = await getTestDBInstance();
|
||||
let serverDB = await getTestDB();
|
||||
|
||||
const userId = 'user-db';
|
||||
const userEmail = 'user@example.com';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export * from '../src/models/__tests__/_util';
|
||||
export * from '../src/core/getTestDB';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export interface ExportDatabaseData {
|
||||
data: Record<string, object[]>;
|
||||
schemaHash?: string;
|
||||
schemaHash: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export * from './serverConfig';
|
|||
export * from './service';
|
||||
export * from './session';
|
||||
export * from './stepContext';
|
||||
export * from './tableViewer';
|
||||
export * from './tool';
|
||||
export * from './topic';
|
||||
export * from './user';
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
export interface TableBasicInfo {
|
||||
count: number;
|
||||
name: string;
|
||||
type: 'BASE TABLE' | 'VIEW';
|
||||
}
|
||||
|
||||
export interface TableColumnInfo {
|
||||
defaultValue?: string;
|
||||
foreignKey?: {
|
||||
column: string;
|
||||
table: string;
|
||||
};
|
||||
isPrimaryKey: boolean;
|
||||
name: string;
|
||||
nullable: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface PaginationParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface FilterCondition {
|
||||
column: string;
|
||||
operator: 'equals' | 'contains' | 'startsWith' | 'endsWith';
|
||||
value: any;
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { readMigrationFiles } from 'drizzle-orm/migrator';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const dbBase = join(__dirname, '../../packages/database');
|
||||
const migrationsFolder = join(dbBase, './migrations');
|
||||
const migrations = readMigrationFiles({ migrationsFolder: migrationsFolder });
|
||||
|
||||
writeFileSync(
|
||||
join(dbBase, './src/core/migrations.json'),
|
||||
JSON.stringify(migrations, null, 2), // null, 2 adds indentation for better readability
|
||||
);
|
||||
|
||||
console.log('🏁 client migrations.json compiled!');
|
||||
|
|
@ -8,7 +8,7 @@ import { exportService } from './export';
|
|||
|
||||
class ConfigService {
|
||||
exportAll = async () => {
|
||||
const { data, url } = await exportService.exportData();
|
||||
const { data, url, schemaHash } = await exportService.exportData();
|
||||
const filename = `${dayjs().format('YYYY-MM-DD-hh-mm')}_${BRANDING_NAME}-data.json`;
|
||||
|
||||
// if url exists, means export data from server and upload the data to S3
|
||||
|
|
@ -18,24 +18,10 @@ class ConfigService {
|
|||
return;
|
||||
}
|
||||
|
||||
// or export to file with the data
|
||||
const result = await this.createDataStructure(data, 'postgres');
|
||||
const result: ImportPgDataStructure = { data, mode: 'postgres', schemaHash };
|
||||
|
||||
exportJSONFile(result, filename);
|
||||
};
|
||||
|
||||
private createDataStructure = async (
|
||||
data: any,
|
||||
mode: 'pglite' | 'postgres',
|
||||
): Promise<ImportPgDataStructure> => {
|
||||
const { default: json } = await import('@/database/core/migrations.json');
|
||||
const latestHash = json.at(-1)?.hash;
|
||||
if (!latestHash) {
|
||||
throw new Error('Not find database sql hash');
|
||||
}
|
||||
|
||||
return { data, mode, schemaHash: latestHash };
|
||||
};
|
||||
}
|
||||
|
||||
export const configService = new ConfigService();
|
||||
|
|
|
|||
Loading…
Reference in a new issue