Skip to content

Commit 36bcaf3

Browse files
authored
πŸ› fix: remove orphan chunks if there is no related file (lobehub#3578)
* βœ… test: add more tests * πŸ› fix: add `DISABLE_REMOVE_GLOBAL_FILE` in dbENV * πŸ› fix: remove orphan chunks if there is related file * βœ… test: fix test * πŸ‘· fix: throw error when not set `APP_URL` at server side * βœ… test: fix test
1 parent 6d96a41 commit 36bcaf3

File tree

11 files changed

+479
-81
lines changed

11 files changed

+479
-81
lines changed

β€Ž.github/workflows/release.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
1919
ports:
2020
- 5432:5432
21-
21+
2222
steps:
2323
- uses: actions/checkout@v4
2424

@@ -40,7 +40,8 @@ jobs:
4040
DATABASE_DRIVER: node
4141
NEXT_PUBLIC_SERVICE_MODE: server
4242
KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
43-
NEXT_PUBLIC_S3_DOMAIN: https://example.com
43+
S3_PUBLIC_DOMAIN: https://example.com
44+
APP_URL: https://home.com
4445

4546
- name: Test App Coverage
4647
run: bun run test-app:coverage

β€Ž.github/workflows/test.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ jobs:
3939
DATABASE_DRIVER: node
4040
NEXT_PUBLIC_SERVICE_MODE: server
4141
KEY_VAULTS_SECRET: LA7n9k3JdEcbSgml2sxfw+4TV1AzaaFU5+R176aQz4s=
42-
NEXT_PUBLIC_S3_DOMAIN: https://example.com
42+
S3_PUBLIC_DOMAIN: https://example.com
43+
APP_URL: https://home.com
4344

4445
- name: Upload Server coverage to Codecov
4546
uses: codecov/codecov-action@v4

β€Žsrc/config/app.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import { createEnv } from '@t3-oss/env-nextjs';
33
import { z } from 'zod';
44

5+
import { isServerMode } from '@/const/version';
6+
57
declare global {
68
// eslint-disable-next-line @typescript-eslint/no-namespace
79
namespace NodeJS {
@@ -10,14 +12,19 @@ declare global {
1012
}
1113
}
1214
}
15+
const isInVercel = process.env.VERCEL === '1';
1316

14-
export const getAppConfig = () => {
15-
const ACCESS_CODES = process.env.ACCESS_CODE?.split(',').filter(Boolean) || [];
16-
const isInVercel = process.env.VERCEL === '1';
17+
const vercelUrl = `https://${process.env.VERCEL_URL}`;
1718

18-
const vercelUrl = `https://${process.env.VERCEL_URL}`;
19+
const APP_URL = process.env.APP_URL ? process.env.APP_URL : isInVercel ? vercelUrl : undefined;
1920

20-
const APP_URL = process.env.APP_URL ? process.env.APP_URL : isInVercel ? vercelUrl : undefined;
21+
// only throw error in server mode and server side
22+
if (typeof window === 'undefined' && isServerMode && !APP_URL) {
23+
throw new Error('`APP_URL` is required in server mode');
24+
}
25+
26+
export const getAppConfig = () => {
27+
const ACCESS_CODES = process.env.ACCESS_CODE?.split(',').filter(Boolean) || [];
2128

2229
return createEnv({
2330
client: {

β€Žsrc/config/db.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const getServerDBConfig = () => {
1111
DATABASE_TEST_URL: process.env.DATABASE_TEST_URL,
1212
DATABASE_URL: process.env.DATABASE_URL,
1313

14+
DISABLE_REMOVE_GLOBAL_FILE: process.env.DISABLE_REMOVE_GLOBAL_FILE === '1',
15+
1416
KEY_VAULTS_SECRET: process.env.KEY_VAULTS_SECRET,
1517

1618
NEXT_PUBLIC_ENABLED_SERVER_SERVICE: process.env.NEXT_PUBLIC_SERVICE_MODE === 'server',
@@ -20,6 +22,8 @@ export const getServerDBConfig = () => {
2022
DATABASE_TEST_URL: z.string().optional(),
2123
DATABASE_URL: z.string().optional(),
2224

25+
DISABLE_REMOVE_GLOBAL_FILE: z.boolean().optional(),
26+
2327
KEY_VAULTS_SECRET: z.string().optional(),
2428
},
2529
});

β€Žsrc/database/server/models/__tests__/chunk.test.ts

+60-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,66 @@ describe('ChunkModel', () => {
9898
});
9999
});
100100

101-
// Add more test cases for other methods...
101+
describe('deleteOrphanChunks', () => {
102+
it('should delete orphaned chunks', async () => {
103+
// Create orphaned chunks
104+
await serverDB
105+
.insert(chunks)
106+
.values([
107+
{ text: 'Orphan Chunk 1', userId },
108+
{ text: 'Orphan Chunk 2', userId },
109+
])
110+
.returning();
111+
112+
// Create a non-orphaned chunk
113+
const [nonOrphanChunk] = await serverDB
114+
.insert(chunks)
115+
.values([{ text: 'Non-Orphan Chunk', userId }])
116+
.returning();
117+
118+
await serverDB.insert(fileChunks).values([{ fileId: '1', chunkId: nonOrphanChunk.id }]);
119+
120+
// Execute the method
121+
await chunkModel.deleteOrphanChunks();
122+
123+
// Check if orphaned chunks are deleted
124+
const remainingChunks = await serverDB.query.chunks.findMany();
125+
expect(remainingChunks).toHaveLength(1);
126+
expect(remainingChunks[0].id).toBe(nonOrphanChunk.id);
127+
});
128+
129+
it('should not delete any chunks when there are no orphans', async () => {
130+
// Create non-orphaned chunks
131+
const [chunk1, chunk2] = await serverDB
132+
.insert(chunks)
133+
.values([
134+
{ text: 'Chunk 1', userId },
135+
{ text: 'Chunk 2', userId },
136+
])
137+
.returning();
138+
139+
await serverDB.insert(fileChunks).values([
140+
{ fileId: '1', chunkId: chunk1.id },
141+
{ fileId: '2', chunkId: chunk2.id },
142+
]);
143+
144+
// Execute the method
145+
await chunkModel.deleteOrphanChunks();
146+
147+
// Check if all chunks are still present
148+
const remainingChunks = await serverDB.query.chunks.findMany();
149+
expect(remainingChunks).toHaveLength(2);
150+
});
151+
152+
it('should not throw an error when the database is empty', async () => {
153+
// Ensure the database is empty
154+
await serverDB.delete(chunks);
155+
await serverDB.delete(fileChunks);
156+
157+
// Execute the method and expect it not to throw
158+
await expect(chunkModel.deleteOrphanChunks()).resolves.not.toThrow();
159+
});
160+
});
102161

103162
describe('semanticSearch', () => {
104163
it('should perform semantic search and return results', async () => {

0 commit comments

Comments
Β (0)