@lobehub/lobehub 2.0.0-next.195 → 2.0.0-next.197

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 (110) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/locales/ar/setting.json +0 -3
  4. package/locales/bg-BG/setting.json +0 -3
  5. package/locales/de-DE/setting.json +0 -3
  6. package/locales/en-US/setting.json +0 -3
  7. package/locales/es-ES/setting.json +0 -3
  8. package/locales/fa-IR/setting.json +0 -3
  9. package/locales/fr-FR/setting.json +0 -3
  10. package/locales/it-IT/setting.json +0 -3
  11. package/locales/ja-JP/setting.json +0 -3
  12. package/locales/ko-KR/setting.json +0 -3
  13. package/locales/nl-NL/setting.json +0 -3
  14. package/locales/pl-PL/setting.json +0 -3
  15. package/locales/pt-BR/setting.json +0 -3
  16. package/locales/ru-RU/setting.json +0 -3
  17. package/locales/tr-TR/setting.json +0 -3
  18. package/locales/vi-VN/setting.json +0 -3
  19. package/locales/zh-CN/setting.json +0 -3
  20. package/locales/zh-TW/setting.json +0 -3
  21. package/package.json +2 -3
  22. package/packages/const/src/fetch.ts +1 -4
  23. package/packages/database/src/core/getTestDB.ts +50 -0
  24. package/packages/database/src/models/__tests__/_test_template.ts +1 -1
  25. package/packages/database/src/models/__tests__/agent.test.ts +1 -1
  26. package/packages/database/src/models/__tests__/aiModel.test.ts +1 -1
  27. package/packages/database/src/models/__tests__/aiProvider.test.ts +1 -1
  28. package/packages/database/src/models/__tests__/apiKey.test.ts +1 -1
  29. package/packages/database/src/models/__tests__/asyncTask.test.ts +1 -1
  30. package/packages/database/src/models/__tests__/chatGroup.test.ts +1 -1
  31. package/packages/database/src/models/__tests__/chunk.test.ts +1 -1
  32. package/packages/database/src/models/__tests__/document.test.ts +1 -1
  33. package/packages/database/src/models/__tests__/drizzleMigration.test.ts +1 -1
  34. package/packages/database/src/models/__tests__/embedding.test.ts +1 -1
  35. package/packages/database/src/models/__tests__/file.test.ts +1 -1
  36. package/packages/database/src/models/__tests__/generation.test.ts +1 -1
  37. package/packages/database/src/models/__tests__/generationBatch.test.ts +1 -1
  38. package/packages/database/src/models/__tests__/generationTopic.test.ts +1 -1
  39. package/packages/database/src/models/__tests__/knowledgeBase.test.ts +1 -1
  40. package/packages/database/src/models/__tests__/messages/message.create.test.ts +1 -1
  41. package/packages/database/src/models/__tests__/messages/message.delete.test.ts +1 -1
  42. package/packages/database/src/models/__tests__/messages/message.query.test.ts +1 -1
  43. package/packages/database/src/models/__tests__/messages/message.stats.test.ts +1 -1
  44. package/packages/database/src/models/__tests__/messages/message.thread-query.test.ts +1 -1
  45. package/packages/database/src/models/__tests__/messages/message.update.test.ts +1 -1
  46. package/packages/database/src/models/__tests__/messages/messageWithTask.test.ts +1 -1
  47. package/packages/database/src/models/__tests__/messages/queryWithMessageGroup.perf.test.ts +1 -1
  48. package/packages/database/src/models/__tests__/messages/queryWithMessageGroup.test.ts +1 -1
  49. package/packages/database/src/models/__tests__/oauthHandoff.test.ts +1 -1
  50. package/packages/database/src/models/__tests__/plugin.test.ts +1 -1
  51. package/packages/database/src/models/__tests__/session.test.ts +1 -1
  52. package/packages/database/src/models/__tests__/sessionGroup.test.ts +1 -1
  53. package/packages/database/src/models/__tests__/thread.test.ts +1 -1
  54. package/packages/database/src/models/__tests__/topicDocument.test.ts +1 -1
  55. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +1 -1
  56. package/packages/database/src/models/__tests__/topics/topic.delete.test.ts +1 -1
  57. package/packages/database/src/models/__tests__/topics/topic.query.test.ts +1 -1
  58. package/packages/database/src/models/__tests__/topics/topic.stats.test.ts +1 -1
  59. package/packages/database/src/models/__tests__/topics/topic.update.test.ts +1 -1
  60. package/packages/database/src/models/__tests__/user.test.ts +1 -1
  61. package/packages/database/src/models/__tests__/userMemories.test.ts +1 -1
  62. package/packages/database/src/models/__tests__/userMemoryIdentity.test.ts +1 -1
  63. package/packages/database/src/models/userMemory/__tests__/context.test.ts +1 -1
  64. package/packages/database/src/models/userMemory/__tests__/experience.test.ts +1 -1
  65. package/packages/database/src/models/userMemory/__tests__/identity.test.ts +1 -1
  66. package/packages/database/src/models/userMemory/__tests__/preference.test.ts +1 -1
  67. package/packages/database/src/repositories/agentGroup/index.test.ts +1 -1
  68. package/packages/database/src/repositories/agentMigration/__tests__/agentMigrationRepo.test.ts +1 -1
  69. package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
  70. package/packages/database/src/repositories/compression/index.test.ts +1 -1
  71. package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
  72. package/packages/database/src/repositories/dataImporter/__tests__/index.test.ts +1 -1
  73. package/packages/database/src/repositories/dataImporter/deprecated/__tests__/index.test.ts +2 -2
  74. package/packages/database/src/repositories/home/__tests__/index.test.ts +1 -1
  75. package/packages/database/src/repositories/home/index.test.ts +1 -1
  76. package/packages/database/src/repositories/knowledge/index.test.ts +1 -1
  77. package/packages/database/src/repositories/search/index.test.ts +1 -1
  78. package/packages/database/src/repositories/topicImporter/__tests__/importTopic.test.ts +1 -1
  79. package/packages/database/src/repositories/userMemory/__tests__/UserMemoryTopicRepository.test.ts +1 -1
  80. package/packages/database/src/server/models/__tests__/adapter.test.ts +2 -2
  81. package/packages/database/src/server/models/__tests__/user.test.ts +2 -2
  82. package/packages/database/tests/test-utils.ts +1 -1
  83. package/packages/types/src/auth.ts +0 -4
  84. package/packages/types/src/export.ts +1 -1
  85. package/packages/types/src/index.ts +0 -1
  86. package/packages/utils/src/server/xor.test.ts +1 -2
  87. package/src/app/(backend)/_deprecated/createBizOpenAI/auth.test.ts +7 -41
  88. package/src/app/(backend)/_deprecated/createBizOpenAI/auth.ts +1 -15
  89. package/src/app/(backend)/_deprecated/createBizOpenAI/index.ts +2 -9
  90. package/src/app/(backend)/middleware/auth/index.ts +0 -1
  91. package/src/app/(backend)/middleware/auth/utils.test.ts +2 -42
  92. package/src/app/(backend)/middleware/auth/utils.ts +3 -17
  93. package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +0 -5
  94. package/src/app/(backend)/webapi/models/[provider]/route.test.ts +0 -6
  95. package/src/app/(backend)/webapi/plugin/gateway/route.ts +2 -32
  96. package/src/app/[variants]/(main)/settings/common/features/Common/Common.tsx +1 -16
  97. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +1 -0
  98. package/src/features/LibraryModal/AssignKnowledgeBase/List.tsx +12 -13
  99. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +1 -0
  100. package/src/locales/default/setting.ts +0 -3
  101. package/src/server/routers/lambda/__tests__/file.test.ts +76 -3
  102. package/src/server/routers/lambda/file.ts +13 -1
  103. package/src/services/config.ts +2 -16
  104. package/packages/database/src/core/dbForTest.ts +0 -43
  105. package/packages/database/src/core/migrations.json +0 -1080
  106. package/packages/database/src/models/__tests__/_util.ts +0 -30
  107. package/packages/database/src/repositories/tableViewer/index.test.ts +0 -255
  108. package/packages/database/src/repositories/tableViewer/index.ts +0 -251
  109. package/packages/types/src/tableViewer.ts +0 -30
  110. package/scripts/migrateClientDB/compile-migrations.ts +0 -14
@@ -1,8 +1,6 @@
1
1
  import { type AuthObject } from '@clerk/backend';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
- import { getAppConfig } from '@/envs/app';
5
-
6
4
  import { checkAuthMethod } from './utils';
7
5
 
8
6
  let enableClerkMock = false;
@@ -26,15 +24,9 @@ vi.mock('@/const/auth', async (importOriginal) => {
26
24
  };
27
25
  });
28
26
 
29
- vi.mock('@/envs/app', () => ({
30
- getAppConfig: vi.fn(),
31
- }));
32
-
33
27
  describe('checkAuthMethod', () => {
34
28
  beforeEach(() => {
35
- vi.mocked(getAppConfig).mockReturnValue({
36
- ACCESS_CODES: ['validAccessCode'],
37
- } as any);
29
+ vi.clearAllMocks();
38
30
  });
39
31
 
40
32
  it('should pass with valid Clerk auth', () => {
@@ -91,39 +83,7 @@ describe('checkAuthMethod', () => {
91
83
  ).not.toThrow();
92
84
  });
93
85
 
94
- it('should pass with no access code required', () => {
95
- vi.mocked(getAppConfig).mockReturnValueOnce({
96
- ACCESS_CODES: [],
97
- } as any);
98
-
86
+ it('should pass with no auth params', () => {
99
87
  expect(() => checkAuthMethod({})).not.toThrow();
100
88
  });
101
-
102
- it('should pass with valid access code', () => {
103
- expect(() =>
104
- checkAuthMethod({
105
- accessCode: 'validAccessCode',
106
- }),
107
- ).not.toThrow();
108
- });
109
-
110
- it('should throw error with invalid access code', () => {
111
- try {
112
- checkAuthMethod({
113
- accessCode: 'invalidAccessCode',
114
- });
115
- } catch (e) {
116
- expect(e).toEqual({
117
- errorType: 'InvalidAccessCode',
118
- });
119
- }
120
-
121
- try {
122
- checkAuthMethod({});
123
- } catch (e) {
124
- expect(e).toEqual({
125
- errorType: 'InvalidAccessCode',
126
- });
127
- }
128
- });
129
89
  });
@@ -3,28 +3,25 @@ import { AgentRuntimeError } from '@lobechat/model-runtime';
3
3
  import { ChatErrorType } from '@lobechat/types';
4
4
 
5
5
  import { enableBetterAuth, enableClerk, enableNextAuth } from '@/const/auth';
6
- import { getAppConfig } from '@/envs/app';
7
6
 
8
7
  interface CheckAuthParams {
9
- accessCode?: string;
10
8
  apiKey?: string;
11
9
  betterAuthAuthorized?: boolean;
12
10
  clerkAuth?: AuthObject;
13
11
  nextAuthAuthorized?: boolean;
14
12
  }
15
13
  /**
16
- * Check if the provided access code is valid, a user API key should be used or the OAuth 2 header is provided.
14
+ * Check if authentication is valid based on various auth methods.
17
15
  *
18
16
  * @param {CheckAuthParams} params - Authentication parameters extracted from headers.
19
- * @param {string} [params.accessCode] - The access code to check.
20
17
  * @param {string} [params.apiKey] - The user API key.
21
18
  * @param {boolean} [params.betterAuthAuthorized] - Whether the Better Auth session exists.
22
19
  * @param {AuthObject} [params.clerkAuth] - Clerk authentication payload from middleware.
23
20
  * @param {boolean} [params.nextAuthAuthorized] - Whether the OAuth 2 header is provided.
24
- * @throws {AgentRuntimeError} If the access code is invalid and no user API key is provided.
21
+ * @throws {AgentRuntimeError} If authentication fails.
25
22
  */
26
23
  export const checkAuthMethod = (params: CheckAuthParams) => {
27
- const { apiKey, betterAuthAuthorized, nextAuthAuthorized, accessCode, clerkAuth } = params;
24
+ const { apiKey, betterAuthAuthorized, nextAuthAuthorized, clerkAuth } = params;
28
25
  // clerk auth handler
29
26
  if (enableClerk) {
30
27
  // if there is no userId, means the use is not login, just throw error
@@ -42,15 +39,4 @@ export const checkAuthMethod = (params: CheckAuthParams) => {
42
39
 
43
40
  // if apiKey exist
44
41
  if (apiKey) return;
45
-
46
- const { ACCESS_CODES } = getAppConfig();
47
-
48
- // if accessCode doesn't exist
49
- if (!ACCESS_CODES.length) return;
50
-
51
- if (!accessCode || !ACCESS_CODES.includes(accessCode)) {
52
- // Avoid logging user-provided credentials (access code) for security reasons
53
- console.warn('Invalid access code provided');
54
- throw AgentRuntimeError.createError(ChatErrorType.InvalidAccessCode);
55
- }
56
42
  };
@@ -62,7 +62,6 @@ describe('POST handler', () => {
62
62
 
63
63
  // 设置 getJWTPayload 和 initModelRuntimeWithUserPayload 的模拟返回值
64
64
  vi.mocked(getXorPayload).mockReturnValueOnce({
65
- accessCode: 'test-access-code',
66
65
  apiKey: 'test-api-key',
67
66
  azureApiVersion: 'v1',
68
67
  });
@@ -105,7 +104,6 @@ describe('POST handler', () => {
105
104
  mockState.enableClerk = true;
106
105
 
107
106
  vi.mocked(getXorPayload).mockReturnValueOnce({
108
- accessCode: 'test-access-code',
109
107
  apiKey: 'test-api-key',
110
108
  azureApiVersion: 'v1',
111
109
  });
@@ -133,7 +131,6 @@ describe('POST handler', () => {
133
131
  await POST(request, { params: mockParams });
134
132
 
135
133
  expect(checkAuthMethod).toBeCalledWith({
136
- accessCode: 'test-access-code',
137
134
  apiKey: 'test-api-key',
138
135
  betterAuthAuthorized: false,
139
136
  clerkAuth: {},
@@ -163,7 +160,6 @@ describe('POST handler', () => {
163
160
  describe('chat', () => {
164
161
  it('should correctly handle chat completion with valid payload', async () => {
165
162
  vi.mocked(getXorPayload).mockReturnValueOnce({
166
- accessCode: 'test-access-code',
167
163
  apiKey: 'test-api-key',
168
164
  azureApiVersion: 'v1',
169
165
  userId: 'abc',
@@ -193,7 +189,6 @@ describe('POST handler', () => {
193
189
  it('should return an error response when chat completion fails', async () => {
194
190
  // 设置 getJWTPayload 和 initAgentRuntimeWithUserPayload 的模拟返回值
195
191
  vi.mocked(getXorPayload).mockReturnValueOnce({
196
- accessCode: 'test-access-code',
197
192
  apiKey: 'test-api-key',
198
193
  azureApiVersion: 'v1',
199
194
  });
@@ -41,7 +41,6 @@ describe('GET handler', () => {
41
41
  const mockParams = Promise.resolve({ provider: 'google' });
42
42
 
43
43
  vi.mocked(getXorPayload).mockReturnValueOnce({
44
- accessCode: 'test-access-code',
45
44
  apiKey: 'test-api-key',
46
45
  });
47
46
 
@@ -73,7 +72,6 @@ describe('GET handler', () => {
73
72
  const mockParams = Promise.resolve({ provider: 'google' });
74
73
 
75
74
  vi.mocked(getXorPayload).mockReturnValueOnce({
76
- accessCode: 'test-access-code',
77
75
  apiKey: 'test-api-key',
78
76
  });
79
77
 
@@ -103,7 +101,6 @@ describe('GET handler', () => {
103
101
  const mockParams = Promise.resolve({ provider: 'google' });
104
102
 
105
103
  vi.mocked(getXorPayload).mockReturnValueOnce({
106
- accessCode: 'test-access-code',
107
104
  apiKey: 'test-api-key',
108
105
  });
109
106
 
@@ -128,7 +125,6 @@ describe('GET handler', () => {
128
125
  const mockParams = Promise.resolve({ provider: 'google' });
129
126
 
130
127
  vi.mocked(getXorPayload).mockReturnValueOnce({
131
- accessCode: 'test-access-code',
132
128
  apiKey: 'test-api-key',
133
129
  });
134
130
 
@@ -145,7 +141,6 @@ describe('GET handler', () => {
145
141
  const mockParams = Promise.resolve({ provider: 'openai' });
146
142
 
147
143
  vi.mocked(getXorPayload).mockReturnValueOnce({
148
- accessCode: 'test-access-code',
149
144
  apiKey: 'test-api-key',
150
145
  });
151
146
 
@@ -165,7 +160,6 @@ describe('GET handler', () => {
165
160
  const mockParams = Promise.resolve({ provider: 'openai' });
166
161
 
167
162
  vi.mocked(getXorPayload).mockReturnValueOnce({
168
- accessCode: 'test-access-code',
169
163
  apiKey: 'test-api-key',
170
164
  });
171
165
 
@@ -1,36 +1,15 @@
1
1
  import { AgentRuntimeError } from '@lobechat/model-runtime';
2
- import { ChatErrorType, type ErrorType, TraceNameMap } from '@lobechat/types';
3
- import { getXorPayload } from '@lobechat/utils/server';
2
+ import { ChatErrorType, TraceNameMap } from '@lobechat/types';
4
3
  import type { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
5
4
  import { createGatewayOnEdgeRuntime } from '@lobehub/chat-plugins-gateway';
6
5
 
7
- import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED, enableNextAuth } from '@/const/auth';
6
+ import { LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
8
7
  import { LOBE_CHAT_TRACE_ID } from '@/const/trace';
9
8
  import { getAppConfig } from '@/envs/app';
10
9
  import { TraceClient } from '@/libs/traces';
11
10
  import { parserPluginSettings } from '@/server/services/pluginGateway/settings';
12
- import { createErrorResponse } from '@/utils/errorResponse';
13
11
  import { getTracePayload } from '@/utils/trace';
14
12
 
15
- const checkAuth = (accessCode: string | null, oauthAuthorized: boolean | null) => {
16
- const { ACCESS_CODES, PLUGIN_SETTINGS } = getAppConfig();
17
-
18
- // if there is no plugin settings, just skip the auth
19
- if (!PLUGIN_SETTINGS) return { auth: true };
20
-
21
- // If authorized by oauth
22
- if (oauthAuthorized && enableNextAuth) return { auth: true };
23
-
24
- // if accessCode doesn't exist
25
- if (!ACCESS_CODES.length) return { auth: true };
26
-
27
- if (!accessCode || !ACCESS_CODES.includes(accessCode)) {
28
- return { auth: false, error: ChatErrorType.InvalidAccessCode };
29
- }
30
-
31
- return { auth: true };
32
- };
33
-
34
13
  const { PLUGINS_INDEX_URL: pluginsIndexUrl, PLUGIN_SETTINGS } = getAppConfig();
35
14
 
36
15
  const defaultPluginSettings = parserPluginSettings(PLUGIN_SETTINGS);
@@ -42,15 +21,6 @@ export const POST = async (req: Request) => {
42
21
  const authorization = req.headers.get(LOBE_CHAT_AUTH_HEADER);
43
22
  if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
44
23
 
45
- const oauthAuthorized = !!req.headers.get(OAUTH_AUTHORIZED);
46
- const payload = getXorPayload(authorization);
47
-
48
- const result = checkAuth(payload.accessCode!, oauthAuthorized);
49
-
50
- if (!result.auth) {
51
- return createErrorResponse(result.error as ErrorType);
52
- }
53
-
54
24
  // TODO: need to be replace by better telemetry system
55
25
  // add trace
56
26
  const tracePayload = getTracePayload(req);
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { Form, type FormGroupItemType, Icon, ImageSelect, InputPassword } from '@lobehub/ui';
3
+ import { Form, type FormGroupItemType, Icon, ImageSelect } from '@lobehub/ui';
4
4
  import { Select, Skeleton } from '@lobehub/ui';
5
5
  import { Segmented, Switch } from 'antd';
6
6
  import isEqual from 'fast-deep-equal';
@@ -14,8 +14,6 @@ import { isDesktop } from '@/const/version';
14
14
  import { localeOptions } from '@/locales/resources';
15
15
  import { useGlobalStore } from '@/store/global';
16
16
  import { systemStatusSelectors } from '@/store/global/selectors';
17
- import { useServerConfigStore } from '@/store/serverConfig';
18
- import { serverConfigSelectors } from '@/store/serverConfig/selectors';
19
17
  import { useUserStore } from '@/store/user';
20
18
  import { settingsSelectors } from '@/store/user/selectors';
21
19
  import { type LocaleMode } from '@/types/locale';
@@ -23,7 +21,6 @@ import { type LocaleMode } from '@/types/locale';
23
21
  const Common = memo(() => {
24
22
  const { t } = useTranslation('setting');
25
23
 
26
- const showAccessCodeConfig = useServerConfigStore(serverConfigSelectors.enabledAccessCode);
27
24
  const general = useUserStore((s) => settingsSelectors.currentSettings(s).general, isEqual);
28
25
  const themeMode = useGlobalStore(systemStatusSelectors.themeMode);
29
26
  const language = useGlobalStore(systemStatusSelectors.language);
@@ -137,18 +134,6 @@ const Common = memo(() => {
137
134
  name: 'contextMenuMode',
138
135
  },
139
136
 
140
- {
141
- children: (
142
- <InputPassword
143
- autoComplete={'new-password'}
144
- placeholder={t('settingSystem.accessCode.placeholder')}
145
- />
146
- ),
147
- desc: t('settingSystem.accessCode.desc'),
148
- hidden: !showAccessCodeConfig,
149
- label: t('settingSystem.accessCode.title'),
150
- name: 'password',
151
- },
152
137
  {
153
138
  children: (
154
139
  <Select
@@ -52,6 +52,7 @@ const Token = memo<TokenTagProps>(({ total: messageString }) => {
52
52
  chatConfigByIdSelectors.getEnableHistoryCountById(agentId)(s),
53
53
  // need to re-render by search mode
54
54
  chatConfigByIdSelectors.isEnableSearchById(agentId)(s),
55
+ chatConfigByIdSelectors.getUseModelBuiltinSearchById(agentId)(s),
55
56
  ]);
56
57
 
57
58
  const maxTokens = useModelContextWindowTokens(model, provider);
@@ -114,19 +114,18 @@ export const List = memo(() => {
114
114
  totalCount={data!.length}
115
115
  />
116
116
  ) : (
117
- <div style={{ flex: 1, overflow: 'hidden' }}>
118
- <div style={{ height: '100%', overflowY: 'auto' }}>
119
- <div style={{ paddingInline: 16 }}>
120
- <VirtuosoMasonry
121
- ItemContent={MasonryItemWrapper}
122
- columnCount={columnCount}
123
- context={masonryContext}
124
- data={data || []}
125
- style={{
126
- gap: '16px',
127
- }}
128
- />
129
- </div>
117
+ <div style={{ height: '100%', position: 'relative' }}>
118
+ <div style={{ inset: 0, position: 'absolute' }}>
119
+ <VirtuosoMasonry
120
+ ItemContent={MasonryItemWrapper}
121
+ columnCount={columnCount}
122
+ context={masonryContext}
123
+ data={data || []}
124
+ style={{
125
+ gap: '16px',
126
+ height: '100%',
127
+ }}
128
+ />
130
129
  </div>
131
130
  </div>
132
131
  )}
@@ -87,6 +87,7 @@ const MasonryView = memo<MasonryViewProps>(
87
87
  data={data || []}
88
88
  style={{
89
89
  gap: '16px',
90
+ overflow: 'hidden',
90
91
  }}
91
92
  />
92
93
  {isLoadingMore && (
@@ -442,9 +442,6 @@ export default {
442
442
  'settingOpening.openingQuestions.title': 'Opening Questions',
443
443
  'settingOpening.title': 'Opening Settings',
444
444
  'settingPlugin.title': 'Skill List',
445
- 'settingSystem.accessCode.desc': 'Encryption access is enabled by the administrator',
446
- 'settingSystem.accessCode.placeholder': 'Enter access password',
447
- 'settingSystem.accessCode.title': 'Access Password',
448
445
  'settingSystem.oauth.info.desc': 'Logged in',
449
446
  'settingSystem.oauth.info.title': 'Account Information',
450
447
  'settingSystem.oauth.signin.action': 'Sign In',
@@ -245,7 +245,35 @@ describe('fileRouter', () => {
245
245
  );
246
246
  });
247
247
 
248
- it('should handle getFileMetadata errors', async () => {
248
+ it('should fallback to input size when getFileMetadata fails', async () => {
249
+ mockFileModelCheckHash.mockResolvedValue({ isExist: false });
250
+ mockFileModelCreate.mockResolvedValue({ id: 'new-file-id' });
251
+ mockFileServiceGetFileMetadata.mockRejectedValue(new Error('File not found in S3'));
252
+
253
+ const result = await caller.createFile({
254
+ hash: 'test-hash',
255
+ fileType: 'text',
256
+ name: 'test.txt',
257
+ size: 100,
258
+ url: 'files/non-existent.txt',
259
+ metadata: {},
260
+ });
261
+
262
+ expect(result).toEqual({
263
+ id: 'new-file-id',
264
+ url: 'https://lobehub.com/f/new-file-id',
265
+ });
266
+
267
+ // Verify create was called with input size as fallback
268
+ expect(mockFileModelCreate).toHaveBeenCalledWith(
269
+ expect.objectContaining({
270
+ size: 100,
271
+ }),
272
+ true,
273
+ );
274
+ });
275
+
276
+ it('should throw error when getFileMetadata fails and input size is less than 1', async () => {
249
277
  mockFileModelCheckHash.mockResolvedValue({ isExist: false });
250
278
  mockFileServiceGetFileMetadata.mockRejectedValue(new Error('File not found in S3'));
251
279
 
@@ -254,11 +282,56 @@ describe('fileRouter', () => {
254
282
  hash: 'test-hash',
255
283
  fileType: 'text',
256
284
  name: 'test.txt',
257
- size: 100,
285
+ size: 0,
258
286
  url: 'files/non-existent.txt',
259
287
  metadata: {},
260
288
  }),
261
- ).rejects.toThrow('File not found in S3');
289
+ ).rejects.toThrow('File size must be at least 1 byte');
290
+ });
291
+
292
+ it('should use input size when getFileMetadata returns contentLength less than 1', async () => {
293
+ mockFileModelCheckHash.mockResolvedValue({ isExist: false });
294
+ mockFileModelCreate.mockResolvedValue({ id: 'new-file-id' });
295
+ mockFileServiceGetFileMetadata.mockResolvedValue({
296
+ contentLength: 0,
297
+ contentType: 'text/plain',
298
+ });
299
+
300
+ await caller.createFile({
301
+ hash: 'test-hash',
302
+ fileType: 'text',
303
+ name: 'test.txt',
304
+ size: 100,
305
+ url: 'files/test.txt',
306
+ metadata: {},
307
+ });
308
+
309
+ // Verify create was called with input size since contentLength < 1
310
+ expect(mockFileModelCreate).toHaveBeenCalledWith(
311
+ expect.objectContaining({
312
+ size: 100,
313
+ }),
314
+ true,
315
+ );
316
+ });
317
+
318
+ it('should throw error when both getFileMetadata contentLength and input size are less than 1', async () => {
319
+ mockFileModelCheckHash.mockResolvedValue({ isExist: false });
320
+ mockFileServiceGetFileMetadata.mockResolvedValue({
321
+ contentLength: 0,
322
+ contentType: 'text/plain',
323
+ });
324
+
325
+ await expect(
326
+ caller.createFile({
327
+ hash: 'test-hash',
328
+ fileType: 'text',
329
+ name: 'test.txt',
330
+ size: 0,
331
+ url: 'files/test.txt',
332
+ metadata: {},
333
+ }),
334
+ ).rejects.toThrow('File size must be at least 1 byte');
262
335
  });
263
336
  });
264
337
 
@@ -64,7 +64,19 @@ export const fileRouter = router({
64
64
  }
65
65
  }
66
66
 
67
- const { contentLength: actualSize } = await ctx.fileService.getFileMetadata(input.url);
67
+ let actualSize = input.size;
68
+ try {
69
+ const { contentLength } = await ctx.fileService.getFileMetadata(input.url);
70
+ if (contentLength >= 1) {
71
+ actualSize = contentLength;
72
+ }
73
+ } catch {
74
+ // If metadata fetch fails, use original size from input
75
+ }
76
+
77
+ if (actualSize < 1) {
78
+ throw new TRPCError({ code: 'BAD_REQUEST', message: 'File size must be at least 1 byte' });
79
+ }
68
80
 
69
81
  const { id } = await ctx.fileModel.create(
70
82
  {
@@ -8,7 +8,7 @@ import { exportService } from './export';
8
8
 
9
9
  class ConfigService {
10
10
  exportAll = async () => {
11
- const { data, url } = await exportService.exportData();
11
+ const { data, url, schemaHash } = await exportService.exportData();
12
12
  const filename = `${dayjs().format('YYYY-MM-DD-hh-mm')}_${BRANDING_NAME}-data.json`;
13
13
 
14
14
  // if url exists, means export data from server and upload the data to S3
@@ -18,24 +18,10 @@ class ConfigService {
18
18
  return;
19
19
  }
20
20
 
21
- // or export to file with the data
22
- const result = await this.createDataStructure(data, 'postgres');
21
+ const result: ImportPgDataStructure = { data, mode: 'postgres', schemaHash };
23
22
 
24
23
  exportJSONFile(result, filename);
25
24
  };
26
-
27
- private createDataStructure = async (
28
- data: any,
29
- mode: 'pglite' | 'postgres',
30
- ): Promise<ImportPgDataStructure> => {
31
- const { default: json } = await import('@/database/core/migrations.json');
32
- const latestHash = json.at(-1)?.hash;
33
- if (!latestHash) {
34
- throw new Error('Not find database sql hash');
35
- }
36
-
37
- return { data, mode, schemaHash: latestHash };
38
- };
39
25
  }
40
26
 
41
27
  export const configService = new ConfigService();
@@ -1,43 +0,0 @@
1
- import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
2
- import { drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
3
- import * as migrator from 'drizzle-orm/neon-serverless/migrator';
4
- import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
5
- import * as nodeMigrator from 'drizzle-orm/node-postgres/migrator';
6
- import { join } from 'node:path';
7
- import { Pool as NodePool } from 'pg';
8
- import ws from 'ws';
9
-
10
- import { serverDBEnv } from '@/config/db';
11
-
12
- import * as schema from '../schemas';
13
-
14
- const migrationsFolder = join(__dirname, '../../migrations');
15
-
16
- export const getTestDBInstance = async () => {
17
- let connectionString = serverDBEnv.DATABASE_TEST_URL;
18
-
19
- if (!connectionString) {
20
- throw new Error(`You are try to use database, but "DATABASE_TEST_URL" is not set correctly`);
21
- }
22
-
23
- if (serverDBEnv.DATABASE_DRIVER === 'node') {
24
- const client = new NodePool({ connectionString });
25
-
26
- const db = nodeDrizzle(client, { schema });
27
-
28
- await nodeMigrator.migrate(db, { migrationsFolder });
29
-
30
- return db;
31
- }
32
-
33
- // https://github.com/neondatabase/serverless/blob/main/CONFIG.md#websocketconstructor-typeof-websocket--undefined
34
- neonConfig.webSocketConstructor = ws;
35
-
36
- const client = new NeonPool({ connectionString });
37
-
38
- const db = neonDrizzle(client, { schema });
39
-
40
- await migrator.migrate(db, { migrationsFolder });
41
-
42
- return db;
43
- };