@lobehub/chat 1.71.5 → 1.72.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/docs/developer/database-schema.dbml +16 -0
  4. package/package.json +3 -3
  5. package/src/database/client/db.ts +14 -8
  6. package/src/database/client/migrations.json +62 -0
  7. package/src/database/migrations/0017_add_user_id_to_tables.sql +225 -0
  8. package/src/database/migrations/meta/0017_snapshot.json +3858 -0
  9. package/src/database/migrations/meta/_journal.json +7 -0
  10. package/src/database/{server/models → models}/__tests__/_test_template.ts +2 -2
  11. package/src/database/models/__tests__/_util.ts +12 -0
  12. package/src/database/{server/models → models}/__tests__/agent.test.ts +6 -5
  13. package/src/database/{server/models → models}/__tests__/aiModel.test.ts +5 -4
  14. package/src/database/{server/models → models}/__tests__/aiProvider.test.ts +5 -4
  15. package/src/database/{server/models → models}/__tests__/asyncTask.test.ts +5 -4
  16. package/src/database/{server/models → models}/__tests__/chunk.test.ts +25 -21
  17. package/src/database/{server/models → models}/__tests__/file.test.ts +19 -5
  18. package/src/database/{server/models → models}/__tests__/knowledgeBase.test.ts +9 -4
  19. package/src/database/{server/models → models}/__tests__/message.test.ts +625 -29
  20. package/src/database/{server/models → models}/__tests__/plugin.test.ts +5 -4
  21. package/src/database/{server/models → models}/__tests__/session.test.ts +23 -20
  22. package/src/database/{server/models → models}/__tests__/sessionGroup.test.ts +5 -4
  23. package/src/database/{server/models → models}/__tests__/topic.test.ts +5 -4
  24. package/src/database/repositories/dataImporter/index.ts +3 -0
  25. package/src/database/schemas/file.ts +38 -32
  26. package/src/database/schemas/message.ts +21 -0
  27. package/src/database/schemas/relations.ts +10 -0
  28. package/src/database/server/models/__tests__/nextauth.test.ts +2 -0
  29. package/src/database/server/models/__tests__/user.test.ts +13 -1
  30. package/src/database/server/models/chunk.ts +5 -1
  31. package/src/database/server/models/file.ts +6 -3
  32. package/src/database/server/models/message.ts +29 -12
  33. package/src/database/server/models/session.ts +1 -0
  34. package/src/services/file/client.test.ts +2 -1
  35. package/src/services/message/client.test.ts +3 -3
  36. package/src/services/session/client.test.ts +5 -3
  37. package/src/types/message/base.ts +7 -0
  38. package/vitest.server.config.ts +1 -1
  39. package/src/database/server/models/user.test.ts +0 -58
  40. /package/src/database/{server/models → models}/__tests__/fixtures/embedding.ts +0 -0
@@ -119,6 +119,13 @@
119
119
  "when": 1741844738677,
120
120
  "tag": "0016_add_message_index",
121
121
  "breakpoints": true
122
+ },
123
+ {
124
+ "idx": 17,
125
+ "version": "7",
126
+ "when": 1742269437903,
127
+ "tag": "0017_add_user_id_to_tables",
128
+ "breakpoints": true
122
129
  }
123
130
  ],
124
131
  "version": "6"
@@ -4,8 +4,8 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
5
  import { getTestDBInstance } from '@/database/server/core/dbForTest';
6
6
 
7
- import { sessionGroups, users } from '../../../schemas';
8
- import { SessionGroupModel } from '../sessionGroup';
7
+ import { sessionGroups, users } from '../../schemas';
8
+ import { SessionGroupModel } from '../../server/models/sessionGroup';
9
9
 
10
10
  let serverDB = await getTestDBInstance();
11
11
 
@@ -0,0 +1,12 @@
1
+ import { clientDB, initializeDB } from '@/database/client/db';
2
+ import { getTestDBInstance } from '@/database/server/core/dbForTest';
3
+ import { LobeChatDatabase } from '@/database/type';
4
+
5
+ export const isServerDBMode = process.env.TEST_SERVER_DB === '1';
6
+
7
+ export const getTestDB = async () => {
8
+ if (isServerDBMode) return await getTestDBInstance();
9
+
10
+ await initializeDB();
11
+ return clientDB as LobeChatDatabase;
12
+ };
@@ -2,7 +2,7 @@
2
2
  import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
 
7
7
  import {
8
8
  agents,
@@ -13,10 +13,11 @@ import {
13
13
  knowledgeBases,
14
14
  sessions,
15
15
  users,
16
- } from '../../../schemas';
17
- import { AgentModel } from '../agent';
16
+ } from '../../schemas';
17
+ import { AgentModel } from '../../server/models/agent';
18
+ import { getTestDB } from './_util';
18
19
 
19
- let serverDB = await getTestDBInstance();
20
+ const serverDB: LobeChatDatabase = await getTestDB();
20
21
 
21
22
  const userId = 'agent-model-test-user-id';
22
23
  const agentModel = new AgentModel(serverDB, userId);
@@ -77,7 +78,7 @@ describe('AgentModel', () => {
77
78
  const sessionId = 'test-session-id';
78
79
  await serverDB.insert(agents).values({ id: agentId, userId });
79
80
  await serverDB.insert(sessions).values({ id: sessionId, userId });
80
- await serverDB.insert(agentsToSessions).values({ agentId, sessionId });
81
+ await serverDB.insert(agentsToSessions).values({ agentId, sessionId, userId });
81
82
 
82
83
  const result = await agentModel.findBySessionId(sessionId);
83
84
 
@@ -2,13 +2,14 @@
2
2
  import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
  import { AiProviderModelListItem } from '@/types/aiModel';
7
7
 
8
- import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../../schemas';
9
- import { AiModelModel } from '../aiModel';
8
+ import { AiModelSelectItem, NewAiModelItem, aiModels, users } from '../../schemas';
9
+ import { AiModelModel } from '../../server/models/aiModel';
10
+ import { getTestDB } from './_util';
10
11
 
11
- let serverDB = await getTestDBInstance();
12
+ const serverDB: LobeChatDatabase = await getTestDB();
12
13
 
13
14
  const userId = 'ai-model-test-user-id';
14
15
  const aiProviderModel = new AiModelModel(serverDB, userId);
@@ -2,13 +2,14 @@
2
2
  import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
  import { ModelProvider } from '@/libs/agent-runtime';
7
7
 
8
- import { aiProviders, users } from '../../../schemas';
9
- import { AiProviderModel } from '../aiProvider';
8
+ import { aiProviders, users } from '../../schemas';
9
+ import { AiProviderModel } from '../../server/models/aiProvider';
10
+ import { getTestDB } from './_util';
10
11
 
11
- let serverDB = await getTestDBInstance();
12
+ const serverDB: LobeChatDatabase = await getTestDB();
12
13
 
13
14
  const userId = 'session-group-model-test-user-id';
14
15
  const aiProviderModel = new AiProviderModel(serverDB, userId);
@@ -2,13 +2,14 @@
2
2
  import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
  import { AsyncTaskStatus, AsyncTaskType } from '@/types/asyncTask';
7
7
 
8
- import { asyncTasks, users } from '../../../schemas';
9
- import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '../asyncTask';
8
+ import { asyncTasks, users } from '../../schemas';
9
+ import { ASYNC_TASK_TIMEOUT, AsyncTaskModel } from '../../server/models/asyncTask';
10
+ import { getTestDB } from './_util';
10
11
 
11
- let serverDB = await getTestDBInstance();
12
+ const serverDB: LobeChatDatabase = await getTestDB();
12
13
 
13
14
  const userId = 'async-task-model-test-user-id';
14
15
  const asyncTaskModel = new AsyncTaskModel(serverDB, userId);
@@ -2,14 +2,15 @@
2
2
  import { eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
  import { uuid } from '@/utils/uuid';
7
7
 
8
- import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../../schemas';
9
- import { ChunkModel } from '../chunk';
8
+ import { chunks, embeddings, fileChunks, files, unstructuredChunks, users } from '../../schemas';
9
+ import { ChunkModel } from '../../server/models/chunk';
10
+ import { getTestDB } from './_util';
10
11
  import { codeEmbedding, designThinkingQuery, designThinkingQuery2 } from './fixtures/embedding';
11
12
 
12
- let serverDB = await getTestDBInstance();
13
+ const serverDB: LobeChatDatabase = await getTestDB();
13
14
 
14
15
  const userId = 'chunk-model-test-user-id';
15
16
  const chunkModel = new ChunkModel(serverDB, userId);
@@ -124,7 +125,9 @@ describe('ChunkModel', () => {
124
125
  .values([{ text: 'Non-Orphan Chunk', userId }])
125
126
  .returning();
126
127
 
127
- await serverDB.insert(fileChunks).values([{ fileId: '1', chunkId: nonOrphanChunk.id }]);
128
+ await serverDB
129
+ .insert(fileChunks)
130
+ .values([{ fileId: '1', chunkId: nonOrphanChunk.id, userId }]);
128
131
 
129
132
  // Execute the method
130
133
  await chunkModel.deleteOrphanChunks();
@@ -146,8 +149,8 @@ describe('ChunkModel', () => {
146
149
  .returning();
147
150
 
148
151
  await serverDB.insert(fileChunks).values([
149
- { fileId: '1', chunkId: chunk1.id },
150
- { fileId: '2', chunkId: chunk2.id },
152
+ { fileId: '1', chunkId: chunk1.id, userId },
153
+ { fileId: '2', chunkId: chunk2.id, userId },
151
154
  ]);
152
155
 
153
156
  // Execute the method
@@ -180,8 +183,8 @@ describe('ChunkModel', () => {
180
183
  .returning();
181
184
 
182
185
  await serverDB.insert(fileChunks).values([
183
- { fileId, chunkId: chunk1.id },
184
- { fileId, chunkId: chunk2.id },
186
+ { fileId, chunkId: chunk1.id, userId },
187
+ { fileId, chunkId: chunk2.id, userId },
185
188
  ]);
186
189
 
187
190
  await serverDB.insert(embeddings).values([
@@ -273,9 +276,9 @@ describe('ChunkModel', () => {
273
276
  .returning();
274
277
 
275
278
  await serverDB.insert(fileChunks).values([
276
- { fileId, chunkId: chunk1.id },
277
- { fileId, chunkId: chunk2.id },
278
- { fileId, chunkId: chunk3.id },
279
+ { fileId, chunkId: chunk1.id, userId },
280
+ { fileId, chunkId: chunk2.id, userId },
281
+ { fileId, chunkId: chunk3.id, userId },
279
282
  ]);
280
283
 
281
284
  const result = await chunkModel.findByFileId(fileId, 0);
@@ -299,8 +302,8 @@ describe('ChunkModel', () => {
299
302
  .returning();
300
303
 
301
304
  await serverDB.insert(fileChunks).values([
302
- { fileId, chunkId: chunk1.id },
303
- { fileId, chunkId: chunk2.id },
305
+ { fileId, chunkId: chunk1.id, userId },
306
+ { fileId, chunkId: chunk2.id, userId },
304
307
  ]);
305
308
 
306
309
  const result = await chunkModel.getChunksTextByFileId(fileId);
@@ -324,9 +327,9 @@ describe('ChunkModel', () => {
324
327
  .returning();
325
328
 
326
329
  await serverDB.insert(fileChunks).values([
327
- { fileId: '1', chunkId: chunk1.id },
328
- { fileId: '1', chunkId: chunk2.id },
329
- { fileId: '2', chunkId: chunk3.id },
330
+ { fileId: '1', chunkId: chunk1.id, userId },
331
+ { fileId: '1', chunkId: chunk2.id, userId },
332
+ { fileId: '2', chunkId: chunk3.id, userId },
330
333
  ]);
331
334
 
332
335
  const result = await chunkModel.countByFileIds(fileIds);
@@ -355,8 +358,8 @@ describe('ChunkModel', () => {
355
358
  .returning();
356
359
 
357
360
  await serverDB.insert(fileChunks).values([
358
- { fileId, chunkId: chunk1.id },
359
- { fileId, chunkId: chunk2.id },
361
+ { fileId, chunkId: chunk1.id, userId },
362
+ { fileId, chunkId: chunk2.id, userId },
360
363
  ]);
361
364
 
362
365
  const result = await chunkModel.countByFileId(fileId);
@@ -383,8 +386,8 @@ describe('ChunkModel', () => {
383
386
  .returning();
384
387
 
385
388
  await serverDB.insert(fileChunks).values([
386
- { fileId, chunkId: chunk1.id },
387
- { fileId, chunkId: chunk2.id },
389
+ { fileId, chunkId: chunk1.id, userId },
390
+ { fileId, chunkId: chunk2.id, userId },
388
391
  ]);
389
392
 
390
393
  await serverDB.insert(embeddings).values([
@@ -511,6 +514,7 @@ content in Table html is below:
511
514
  chunkResult.map((chunk) => ({
512
515
  fileId,
513
516
  chunkId: chunk.id,
517
+ userId,
514
518
  })),
515
519
  );
516
520
 
@@ -2,13 +2,14 @@
2
2
  import { eq, inArray } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
  import { FilesTabs, SortType } from '@/types/files';
7
7
 
8
- import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../../schemas';
9
- import { FileModel } from '../file';
8
+ import { files, globalFiles, knowledgeBaseFiles, knowledgeBases, users } from '../../schemas';
9
+ import { FileModel } from '../../server/models/file';
10
+ import { getTestDB } from './_util';
10
11
 
11
- const serverDB = await getTestDBInstance();
12
+ const serverDB: LobeChatDatabase = await getTestDB();
12
13
 
13
14
  const userId = 'file-model-test-user-id';
14
15
  const fileModel = new FileModel(serverDB, userId);
@@ -95,6 +96,7 @@ describe('FileModel', () => {
95
96
  size: 100,
96
97
  url: 'https://example.com/global-file.txt',
97
98
  metadata: { key: 'value' },
99
+ creator: userId,
98
100
  };
99
101
 
100
102
  const result = await fileModel.createGlobalFile(globalFile);
@@ -115,6 +117,7 @@ describe('FileModel', () => {
115
117
  size: 100,
116
118
  url: 'https://example.com/existing-file.txt',
117
119
  metadata: { key: 'value' },
120
+ creator: userId,
118
121
  };
119
122
 
120
123
  await serverDB.insert(globalFiles).values(globalFile);
@@ -137,6 +140,7 @@ describe('FileModel', () => {
137
140
  url: 'https://example.com/file1.txt',
138
141
  size: 100,
139
142
  fileType: 'text/plain',
143
+ creator: userId,
140
144
  });
141
145
 
142
146
  const { id } = await fileModel.create({
@@ -163,6 +167,7 @@ describe('FileModel', () => {
163
167
  url: 'https://example.com/file1.txt',
164
168
  size: 100,
165
169
  fileType: 'text/plain',
170
+ creator: userId,
166
171
  });
167
172
 
168
173
  const { id } = await fileModel.create({
@@ -192,12 +197,14 @@ describe('FileModel', () => {
192
197
  url: 'https://example.com/file1.txt',
193
198
  size: 100,
194
199
  fileType: 'text/plain',
200
+ creator: userId,
195
201
  });
196
202
  await fileModel.createGlobalFile({
197
203
  hashId: '2',
198
204
  url: 'https://example.com/file2.txt',
199
205
  size: 200,
200
206
  fileType: 'text/plain',
207
+ creator: userId,
201
208
  });
202
209
 
203
210
  const file1 = await fileModel.create({
@@ -240,12 +247,14 @@ describe('FileModel', () => {
240
247
  url: 'https://example.com/file1.txt',
241
248
  size: 100,
242
249
  fileType: 'text/plain',
250
+ creator: userId,
243
251
  });
244
252
  await fileModel.createGlobalFile({
245
253
  hashId: '2',
246
254
  url: 'https://example.com/file2.txt',
247
255
  size: 200,
248
256
  fileType: 'text/plain',
257
+ creator: userId,
249
258
  });
250
259
 
251
260
  const file1 = await fileModel.create({
@@ -450,7 +459,7 @@ describe('FileModel', () => {
450
459
  ]);
451
460
  await serverDB
452
461
  .insert(knowledgeBaseFiles)
453
- .values([{ fileId: 'file1', knowledgeBaseId: 'kb1' }]);
462
+ .values([{ fileId: 'file1', knowledgeBaseId: 'kb1', userId }]);
454
463
  });
455
464
 
456
465
  it('should query files in a specific knowledge base', async () => {
@@ -551,12 +560,14 @@ describe('FileModel', () => {
551
560
  url: 'https://example.com/document.pdf',
552
561
  size: 1000,
553
562
  fileType: 'application/pdf',
563
+ creator: userId,
554
564
  },
555
565
  {
556
566
  hashId: 'hash2',
557
567
  url: 'https://example.com/image.jpg',
558
568
  size: 500,
559
569
  fileType: 'image/jpeg',
570
+ creator: userId,
560
571
  },
561
572
  ]);
562
573
 
@@ -674,6 +685,7 @@ describe('FileModel', () => {
674
685
  size: 100,
675
686
  url: 'https://example.com/global-file.txt',
676
687
  metadata: { key: 'value' },
688
+ creator: userId,
677
689
  };
678
690
 
679
691
  await serverDB.insert(globalFiles).values(globalFile);
@@ -700,12 +712,14 @@ describe('FileModel', () => {
700
712
  fileType: 'text/plain',
701
713
  size: 100,
702
714
  url: 'https://example.com/file1.txt',
715
+ creator: userId,
703
716
  };
704
717
  const globalFiles2 = {
705
718
  hashId: 'hash2',
706
719
  fileType: 'text/plain',
707
720
  size: 200,
708
721
  url: 'https://example.com/file2.txt',
722
+ creator: userId,
709
723
  };
710
724
 
711
725
  await serverDB.insert(globalFiles).values([globalFiles1, globalFiles2]);
@@ -2,7 +2,7 @@
2
2
  import { and, eq } from 'drizzle-orm/expressions';
3
3
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4
4
 
5
- import { getTestDBInstance } from '@/database/server/core/dbForTest';
5
+ import { LobeChatDatabase } from '@/database/type';
6
6
 
7
7
  import {
8
8
  NewKnowledgeBase,
@@ -11,10 +11,11 @@ import {
11
11
  knowledgeBaseFiles,
12
12
  knowledgeBases,
13
13
  users,
14
- } from '../../../schemas';
15
- import { KnowledgeBaseModel } from '../knowledgeBase';
14
+ } from '../../schemas';
15
+ import { KnowledgeBaseModel } from '../../server/models/knowledgeBase';
16
+ import { getTestDB } from './_util';
16
17
 
17
- let serverDB = await getTestDBInstance();
18
+ const serverDB: LobeChatDatabase = await getTestDB();
18
19
 
19
20
  const userId = 'session-group-model-test-user-id';
20
21
  const knowledgeBaseModel = new KnowledgeBaseModel(serverDB, userId);
@@ -160,12 +161,14 @@ describe('KnowledgeBaseModel', () => {
160
161
  url: 'https://example.com/document.pdf',
161
162
  size: 1000,
162
163
  fileType: 'application/pdf',
164
+ creator: userId,
163
165
  },
164
166
  {
165
167
  hashId: 'hash2',
166
168
  url: 'https://example.com/image.jpg',
167
169
  size: 500,
168
170
  fileType: 'image/jpeg',
171
+ creator: userId,
169
172
  },
170
173
  ]);
171
174
 
@@ -198,12 +201,14 @@ describe('KnowledgeBaseModel', () => {
198
201
  url: 'https://example.com/document.pdf',
199
202
  size: 1000,
200
203
  fileType: 'application/pdf',
204
+ creator: userId,
201
205
  },
202
206
  {
203
207
  hashId: 'hash2',
204
208
  url: 'https://example.com/image.jpg',
205
209
  size: 500,
206
210
  fileType: 'image/jpeg',
211
+ creator: userId,
207
212
  },
208
213
  ]);
209
214