@lobehub/lobehub 2.0.0-next.354 → 2.0.0-next.356
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.
- package/.env.desktop +0 -1
- package/.env.example +16 -20
- package/.env.example.development +1 -4
- package/.github/workflows/e2e.yml +10 -11
- package/CHANGELOG.md +60 -0
- package/Dockerfile +28 -4
- package/changelog/v1.json +18 -0
- package/docker-compose/local/docker-compose.yml +2 -2
- package/docker-compose/local/grafana/docker-compose.yml +2 -2
- package/docker-compose/local/logto/docker-compose.yml +2 -2
- package/docker-compose/local/zitadel/.env.example +2 -2
- package/docker-compose/local/zitadel/.env.zh-CN.example +2 -2
- package/docker-compose/production/grafana/docker-compose.yml +2 -2
- package/docker-compose/production/logto/.env.example +2 -2
- package/docker-compose/production/logto/.env.zh-CN.example +2 -2
- package/docker-compose/production/zitadel/.env.example +2 -2
- package/docker-compose/production/zitadel/.env.zh-CN.example +2 -2
- package/docs/development/basic/add-new-authentication-providers.mdx +144 -136
- package/docs/development/basic/add-new-authentication-providers.zh-CN.mdx +146 -136
- package/docs/self-hosting/advanced/auth/legacy.mdx +4 -0
- package/docs/self-hosting/advanced/auth/legacy.zh-CN.mdx +4 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.mdx +326 -0
- package/docs/self-hosting/advanced/auth/nextauth-to-betterauth.zh-CN.mdx +323 -0
- package/docs/self-hosting/advanced/auth.mdx +43 -16
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +44 -16
- package/docs/self-hosting/advanced/redis/upstash.mdx +69 -0
- package/docs/self-hosting/advanced/redis/upstash.zh-CN.mdx +69 -0
- package/docs/self-hosting/advanced/redis.mdx +128 -0
- package/docs/self-hosting/advanced/redis.zh-CN.mdx +126 -0
- package/docs/self-hosting/environment-variables/auth.mdx +15 -1
- package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +15 -1
- package/docs/self-hosting/environment-variables/basic.mdx +13 -0
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +13 -0
- package/docs/self-hosting/environment-variables/redis.mdx +68 -0
- package/docs/self-hosting/environment-variables/redis.zh-CN.mdx +67 -0
- package/docs/self-hosting/migration/v2/breaking-changes.mdx +23 -23
- package/docs/self-hosting/migration/v2/breaking-changes.zh-CN.mdx +23 -23
- package/docs/self-hosting/server-database/docker-compose.mdx +4 -4
- package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +4 -4
- package/e2e/CLAUDE.md +5 -6
- package/e2e/docs/local-setup.md +9 -12
- package/e2e/scripts/setup.ts +9 -15
- package/e2e/src/support/webServer.ts +6 -5
- package/locales/en-US/plugin.json +3 -0
- package/locales/zh-CN/plugin.json +3 -0
- package/package.json +4 -6
- package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
- package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
- package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
- package/packages/database/src/schemas/nextauth.ts +7 -2
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -63
- package/packages/utils/src/server/auth.ts +8 -24
- package/scripts/_shared/checkDeprecatedAuth.js +99 -0
- package/scripts/clerk-to-betterauth/index.ts +8 -3
- package/scripts/nextauth-to-betterauth/_internal/config.ts +41 -0
- package/scripts/nextauth-to-betterauth/_internal/db.ts +32 -0
- package/scripts/nextauth-to-betterauth/_internal/env.ts +6 -0
- package/scripts/nextauth-to-betterauth/index.ts +226 -0
- package/scripts/nextauth-to-betterauth/verify.ts +188 -0
- package/scripts/prebuild.mts +66 -13
- package/scripts/serverLauncher/startServer.js +5 -5
- package/src/app/(backend)/api/auth/[...all]/route.ts +5 -23
- package/src/app/(backend)/api/webhooks/casdoor/route.ts +5 -5
- package/src/app/(backend)/api/webhooks/logto/route.ts +8 -8
- package/src/app/(backend)/middleware/auth/index.test.ts +8 -1
- package/src/app/(backend)/middleware/auth/index.ts +6 -15
- package/src/app/(backend)/middleware/auth/utils.test.ts +0 -32
- package/src/app/(backend)/middleware/auth/utils.ts +3 -8
- package/src/app/(backend)/webapi/chat/[provider]/route.test.ts +8 -1
- package/src/app/(backend)/webapi/create-image/comfyui/route.ts +0 -1
- package/src/app/(backend)/webapi/models/[provider]/route.test.ts +8 -1
- package/src/app/[variants]/(auth)/signin/SignInEmailStep.tsx +1 -1
- package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +4 -17
- package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/agent/cron/[cronId]/features/CronJobContentEditor.tsx +34 -21
- package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
- package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
- package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
- package/src/app/[variants]/(main)/settings/profile/features/SSOProvidersList/index.tsx +12 -19
- package/src/app/[variants]/(main)/settings/profile/index.tsx +8 -14
- package/src/components/{NextAuth/AuthIcons.tsx → AuthIcons.tsx} +8 -10
- package/src/envs/auth.ts +12 -51
- package/src/envs/email.ts +3 -0
- package/src/envs/redis.ts +12 -54
- package/src/features/ChatInput/ChatInputProvider.tsx +22 -2
- package/src/features/ChatInput/InputEditor/index.tsx +14 -3
- package/src/features/ChatInput/store/initialState.ts +2 -0
- package/src/features/User/__tests__/PanelContent.test.tsx +0 -11
- package/src/features/User/__tests__/UserAvatar.test.tsx +1 -16
- package/src/layout/AuthProvider/index.tsx +1 -6
- package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -4
- package/src/libs/better-auth/define-config.ts +2 -0
- package/src/libs/better-auth/plugins/email-whitelist.test.ts +120 -0
- package/src/libs/better-auth/plugins/email-whitelist.ts +62 -0
- package/src/libs/next/config/define-config.ts +13 -1
- package/src/libs/next/proxy/define-config.ts +2 -75
- package/src/libs/oidc-provider/provider.test.ts +0 -4
- package/src/libs/redis/index.ts +0 -1
- package/src/libs/redis/manager.test.ts +9 -45
- package/src/libs/redis/manager.ts +2 -16
- package/src/libs/redis/redis.test.ts +2 -4
- package/src/libs/redis/redis.ts +2 -4
- package/src/libs/redis/types.ts +2 -24
- package/src/libs/redis/utils.test.ts +0 -10
- package/src/libs/redis/utils.ts +0 -19
- package/src/libs/trpc/lambda/context.test.ts +0 -13
- package/src/libs/trpc/lambda/context.ts +21 -59
- package/src/libs/trpc/middleware/userAuth.ts +1 -7
- package/src/libs/trusted-client/getSessionUser.ts +15 -35
- package/src/locales/default/plugin.ts +3 -0
- package/src/server/globalConfig/index.ts +1 -3
- package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
- package/src/server/routers/lambda/__tests__/user.test.ts +0 -48
- package/src/server/routers/lambda/user.ts +1 -12
- package/src/server/services/email/impls/nodemailer/index.ts +2 -2
- package/src/server/services/webhookUser/index.ts +88 -0
- package/src/services/chat/chat.test.ts +19 -19
- package/src/services/chat/index.ts +8 -3
- package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
- package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
- package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
- package/src/services/chat/mecha/contextEngineering.ts +12 -0
- package/src/services/chat/types.ts +7 -1
- package/src/services/user/index.test.ts +0 -14
- package/src/services/user/index.ts +0 -4
- package/src/store/chat/agents/createAgentExecutors.ts +15 -4
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
- package/src/store/user/slices/auth/action.test.ts +22 -126
- package/src/store/user/slices/auth/action.ts +32 -65
- package/src/store/user/slices/auth/initialState.ts +0 -3
- package/src/store/user/slices/auth/selectors.ts +0 -3
- package/tests/setup.ts +10 -0
- package/scripts/_shared/checkDeprecatedClerkEnv.js +0 -42
- package/src/app/(backend)/api/auth/adapter/route.ts +0 -137
- package/src/app/[variants]/(auth)/next-auth/error/AuthErrorPage.tsx +0 -40
- package/src/app/[variants]/(auth)/next-auth/error/page.tsx +0 -11
- package/src/app/[variants]/(auth)/next-auth/signin/AuthSignInBox.tsx +0 -167
- package/src/app/[variants]/(auth)/next-auth/signin/page.tsx +0 -11
- package/src/app/[variants]/(auth)/reset-password/layout.tsx +0 -12
- package/src/app/[variants]/(auth)/signin/layout.tsx +0 -12
- package/src/app/[variants]/(auth)/verify-email/layout.tsx +0 -12
- package/src/envs/auth.test.ts +0 -47
- package/src/layout/AuthProvider/NextAuth/UserUpdater.tsx +0 -44
- package/src/layout/AuthProvider/NextAuth/index.tsx +0 -17
- package/src/libs/next-auth/adapter/index.ts +0 -177
- package/src/libs/next-auth/auth.config.ts +0 -64
- package/src/libs/next-auth/index.ts +0 -20
- package/src/libs/next-auth/sso-providers/auth0.ts +0 -24
- package/src/libs/next-auth/sso-providers/authelia.ts +0 -39
- package/src/libs/next-auth/sso-providers/authentik.ts +0 -25
- package/src/libs/next-auth/sso-providers/casdoor.ts +0 -50
- package/src/libs/next-auth/sso-providers/cloudflare-zero-trust.ts +0 -34
- package/src/libs/next-auth/sso-providers/cognito.ts +0 -8
- package/src/libs/next-auth/sso-providers/feishu.ts +0 -83
- package/src/libs/next-auth/sso-providers/generic-oidc.ts +0 -38
- package/src/libs/next-auth/sso-providers/github.ts +0 -23
- package/src/libs/next-auth/sso-providers/google.ts +0 -18
- package/src/libs/next-auth/sso-providers/index.ts +0 -35
- package/src/libs/next-auth/sso-providers/keycloak.ts +0 -22
- package/src/libs/next-auth/sso-providers/logto.ts +0 -48
- package/src/libs/next-auth/sso-providers/microsoft-entra-id-helper.ts +0 -29
- package/src/libs/next-auth/sso-providers/microsoft-entra-id.ts +0 -19
- package/src/libs/next-auth/sso-providers/okta.ts +0 -22
- package/src/libs/next-auth/sso-providers/sso.config.ts +0 -8
- package/src/libs/next-auth/sso-providers/wechat.ts +0 -36
- package/src/libs/next-auth/sso-providers/zitadel.ts +0 -21
- package/src/libs/redis/upstash.test.ts +0 -158
- package/src/libs/redis/upstash.ts +0 -136
- package/src/server/services/nextAuthUser/index.ts +0 -318
- package/src/server/services/nextAuthUser/utils.ts +0 -62
- package/src/types/next-auth.d.ts +0 -26
|
@@ -11,7 +11,7 @@ import { Editor, useEditor } from '@lobehub/editor/react';
|
|
|
11
11
|
import { Flexbox, Icon, Text } from '@lobehub/ui';
|
|
12
12
|
import { Card } from 'antd';
|
|
13
13
|
import { Clock } from 'lucide-react';
|
|
14
|
-
import { memo, useCallback, useEffect, useRef } from 'react';
|
|
14
|
+
import { type RefObject, memo, useCallback, useEffect, useRef } from 'react';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
16
|
|
|
17
17
|
interface CronJobContentEditorProps {
|
|
@@ -20,8 +20,12 @@ interface CronJobContentEditorProps {
|
|
|
20
20
|
onChange: (value: string) => void;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
interface CronJobContentEditorInnerProps extends CronJobContentEditorProps {
|
|
24
|
+
contentRef: RefObject<string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const CronJobContentEditorInner = memo<CronJobContentEditorInnerProps>(
|
|
28
|
+
({ enableRichRender, initialValue, onChange, contentRef }) => {
|
|
25
29
|
const { t } = useTranslation('setting');
|
|
26
30
|
const editor = useEditor();
|
|
27
31
|
const currentValueRef = useRef(initialValue);
|
|
@@ -31,23 +35,6 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
|
|
|
31
35
|
currentValueRef.current = initialValue;
|
|
32
36
|
}, [initialValue]);
|
|
33
37
|
|
|
34
|
-
// Initialize editor content when editor is ready
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (!editor) return;
|
|
37
|
-
try {
|
|
38
|
-
setTimeout(() => {
|
|
39
|
-
if (initialValue) {
|
|
40
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
|
|
41
|
-
}
|
|
42
|
-
}, 100);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.error('[CronJobContentEditor] Failed to initialize editor content:', error);
|
|
45
|
-
setTimeout(() => {
|
|
46
|
-
editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
|
|
47
|
-
}, 100);
|
|
48
|
-
}
|
|
49
|
-
}, [editor, enableRichRender, initialValue]);
|
|
50
|
-
|
|
51
38
|
// Handle content changes
|
|
52
39
|
const handleContentChange = useCallback(
|
|
53
40
|
(e: any) => {
|
|
@@ -57,13 +44,18 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
|
|
|
57
44
|
|
|
58
45
|
const finalContent = nextContent || '';
|
|
59
46
|
|
|
47
|
+
// Save to parent ref for restoration
|
|
48
|
+
if (contentRef) {
|
|
49
|
+
(contentRef as { current: string }).current = finalContent;
|
|
50
|
+
}
|
|
51
|
+
|
|
60
52
|
// Only call onChange if content actually changed
|
|
61
53
|
if (finalContent !== currentValueRef.current) {
|
|
62
54
|
currentValueRef.current = finalContent;
|
|
63
55
|
onChange(finalContent);
|
|
64
56
|
}
|
|
65
57
|
},
|
|
66
|
-
[enableRichRender, onChange],
|
|
58
|
+
[enableRichRender, onChange, contentRef],
|
|
67
59
|
);
|
|
68
60
|
|
|
69
61
|
return (
|
|
@@ -82,6 +74,14 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
|
|
|
82
74
|
content={''}
|
|
83
75
|
editor={editor}
|
|
84
76
|
lineEmptyPlaceholder={t('agentCronJobs.form.content.placeholder')}
|
|
77
|
+
onInit={(editor) => {
|
|
78
|
+
// Restore content from parent ref when editor re-initializes
|
|
79
|
+
if (contentRef?.current) {
|
|
80
|
+
editor.setDocument(enableRichRender ? 'markdown' : 'text', contentRef.current);
|
|
81
|
+
} else if (initialValue) {
|
|
82
|
+
editor.setDocument(enableRichRender ? 'markdown' : 'text', initialValue);
|
|
83
|
+
}
|
|
84
|
+
}}
|
|
85
85
|
onTextChange={handleContentChange}
|
|
86
86
|
placeholder={t('agentCronJobs.form.content.placeholder')}
|
|
87
87
|
plugins={
|
|
@@ -108,4 +108,17 @@ const CronJobContentEditor = memo<CronJobContentEditorProps>(
|
|
|
108
108
|
},
|
|
109
109
|
);
|
|
110
110
|
|
|
111
|
+
const CronJobContentEditor = (props: CronJobContentEditorProps) => {
|
|
112
|
+
// Ref to persist content across re-mounts when enableRichRender changes
|
|
113
|
+
const contentRef = useRef<string>(props.initialValue);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<CronJobContentEditorInner
|
|
117
|
+
contentRef={contentRef}
|
|
118
|
+
key={`editor-${props.enableRichRender}`}
|
|
119
|
+
{...props}
|
|
120
|
+
/>
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
111
124
|
export default CronJobContentEditor;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Flexbox } from '@lobehub/ui';
|
|
4
|
+
import debug from 'debug';
|
|
4
5
|
import { Suspense, memo, useMemo } from 'react';
|
|
5
6
|
|
|
6
7
|
import ChatMiniMap from '@/features/ChatMiniMap';
|
|
@@ -18,6 +19,8 @@ import ThreadHydration from './ThreadHydration';
|
|
|
18
19
|
import { useActionsBarConfig } from './useActionsBarConfig';
|
|
19
20
|
import { useAgentContext } from './useAgentContext';
|
|
20
21
|
|
|
22
|
+
const log = debug('lobe-render:agent:ConversationArea');
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* ConversationArea
|
|
23
26
|
*
|
|
@@ -35,6 +38,7 @@ const Conversation = memo(() => {
|
|
|
35
38
|
);
|
|
36
39
|
const replaceMessages = useChatStore((s) => s.replaceMessages);
|
|
37
40
|
const messages = useChatStore((s) => s.dbMessagesMap[chatKey]);
|
|
41
|
+
log('contextKey %s: %o', chatKey, messages);
|
|
38
42
|
|
|
39
43
|
// Get operation state from ChatStore for reactive updates
|
|
40
44
|
const operationState = useOperationState(context);
|
|
@@ -111,6 +111,7 @@ const TopicItem = memo<TopicItemProps>(({ id, title, fav, active, threadId }) =>
|
|
|
111
111
|
fill={fav ? cssVar.colorWarning : 'transparent'}
|
|
112
112
|
icon={Star}
|
|
113
113
|
onClick={(e) => {
|
|
114
|
+
e.preventDefault();
|
|
114
115
|
e.stopPropagation();
|
|
115
116
|
favoriteTopic(id, !fav);
|
|
116
117
|
}}
|
|
@@ -106,7 +106,7 @@ const AgentItem = memo<AgentItemProps>(({ item, style, className }) => {
|
|
|
106
106
|
|
|
107
107
|
return (
|
|
108
108
|
<>
|
|
109
|
-
<Link aria-label={
|
|
109
|
+
<Link aria-label={displayTitle} to={agentUrl}>
|
|
110
110
|
<NavItem
|
|
111
111
|
actions={<Actions dropdownMenu={dropdownMenu} />}
|
|
112
112
|
className={className}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_INBOX_AVATAR, SESSION_CHAT_URL } from '@lobechat/const';
|
|
4
4
|
import { Avatar } from '@lobehub/ui';
|
|
5
|
-
import { type CSSProperties, memo
|
|
6
|
-
import {
|
|
5
|
+
import { type CSSProperties, memo } from 'react';
|
|
6
|
+
import { Link } from 'react-router-dom';
|
|
7
7
|
|
|
8
8
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
9
9
|
import { useAgentStore } from '@/store/agent';
|
|
@@ -17,38 +17,28 @@ interface InboxItemProps {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const InboxItem = memo<InboxItemProps>(({ className, style }) => {
|
|
20
|
-
const navigate = useNavigate();
|
|
21
|
-
|
|
22
20
|
const inboxAgentId = useAgentStore(builtinAgentSelectors.inboxAgentId);
|
|
23
|
-
const activeAgentId = useAgentStore((s) => s.activeAgentId);
|
|
24
|
-
const isActive = !!inboxAgentId && activeAgentId === inboxAgentId;
|
|
25
21
|
|
|
26
|
-
const isLoading = useChatStore(
|
|
27
|
-
useCallback(
|
|
28
|
-
(s) => (isActive ? operationSelectors.isAgentRuntimeRunning(s) : false),
|
|
29
|
-
[isActive],
|
|
30
|
-
),
|
|
31
|
-
);
|
|
22
|
+
const isLoading = useChatStore(operationSelectors.isAgentRuntimeRunning);
|
|
32
23
|
const inboxAgentTitle = 'Lobe AI';
|
|
33
24
|
|
|
34
|
-
const handleClick = useCallback(() => {
|
|
35
|
-
if (inboxAgentId) {
|
|
36
|
-
navigate(SESSION_CHAT_URL(inboxAgentId, false));
|
|
37
|
-
}
|
|
38
|
-
}, [inboxAgentId, navigate]);
|
|
39
|
-
|
|
40
25
|
return (
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
26
|
+
<Link aria-label={inboxAgentTitle} to={SESSION_CHAT_URL(inboxAgentId, false)}>
|
|
27
|
+
<NavItem
|
|
28
|
+
className={className}
|
|
29
|
+
icon={
|
|
30
|
+
<Avatar
|
|
31
|
+
avatar={DEFAULT_INBOX_AVATAR}
|
|
32
|
+
emojiScaleWithBackground
|
|
33
|
+
shape={'square'}
|
|
34
|
+
size={24}
|
|
35
|
+
/>
|
|
36
|
+
}
|
|
37
|
+
loading={isLoading}
|
|
38
|
+
style={style}
|
|
39
|
+
title={inboxAgentTitle}
|
|
40
|
+
/>
|
|
41
|
+
</Link>
|
|
52
42
|
);
|
|
53
43
|
});
|
|
54
44
|
|
|
@@ -4,6 +4,7 @@ import { MoreHorizontal } from 'lucide-react';
|
|
|
4
4
|
import { type CSSProperties, memo, useMemo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
|
+
import EmptyNavItem from '@/features/NavPanel/components/EmptyNavItem';
|
|
7
8
|
import NavItem from '@/features/NavPanel/components/NavItem';
|
|
8
9
|
import { useGlobalStore } from '@/store/global';
|
|
9
10
|
import { systemStatusSelectors } from '@/store/global/selectors';
|
|
@@ -11,7 +12,6 @@ import { useHomeStore } from '@/store/home';
|
|
|
11
12
|
import { homeAgentListSelectors } from '@/store/home/selectors';
|
|
12
13
|
import { SessionDefaultGroup } from '@/types/session';
|
|
13
14
|
|
|
14
|
-
import EmptyNavItem from '../../../../../../../../features/NavPanel/components/EmptyNavItem';
|
|
15
15
|
import { useCreateMenuItems } from '../../../hooks';
|
|
16
16
|
import GroupItem from './AgentGroupItem';
|
|
17
17
|
import AgentItem from './AgentItem';
|
|
@@ -5,8 +5,8 @@ import { type ReactNode, createContext, memo, useContext, useMemo, useState } fr
|
|
|
5
5
|
import { ChatGroupWizard } from '@/components/ChatGroupWizard';
|
|
6
6
|
import { MemberSelectionModal } from '@/components/MemberSelectionModal';
|
|
7
7
|
|
|
8
|
-
import CreateGroupModal from '../../CreateGroupModal';
|
|
9
8
|
import ConfigGroupModal from './Modals/ConfigGroupModal';
|
|
9
|
+
import CreateGroupModal from './Modals/CreateGroupModal';
|
|
10
10
|
|
|
11
11
|
interface AgentModalContextValue {
|
|
12
12
|
closeAllModals: () => void;
|
|
@@ -5,8 +5,7 @@ import { type CSSProperties, memo, useMemo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { modal, notification } from '@/components/AntdStaticMethods';
|
|
8
|
-
import AuthIcons from '@/components/
|
|
9
|
-
import { userService } from '@/services/user';
|
|
8
|
+
import AuthIcons from '@/components/AuthIcons';
|
|
10
9
|
import { useServerConfigStore } from '@/store/serverConfig';
|
|
11
10
|
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
|
12
11
|
import { useUserStore } from '@/store/user';
|
|
@@ -17,7 +16,7 @@ const providerNameStyle: CSSProperties = {
|
|
|
17
16
|
};
|
|
18
17
|
|
|
19
18
|
export const SSOProvidersList = memo(() => {
|
|
20
|
-
const
|
|
19
|
+
const isLogin = useUserStore(authSelectors.isLogin);
|
|
21
20
|
const providers = useUserStore(authSelectors.authProviders);
|
|
22
21
|
const hasPasswordAccount = useUserStore(authSelectors.hasPasswordAccount);
|
|
23
22
|
const refreshAuthProviders = useUserStore((s) => s.refreshAuthProviders);
|
|
@@ -26,7 +25,7 @@ export const SSOProvidersList = memo(() => {
|
|
|
26
25
|
|
|
27
26
|
// Allow unlink if user has multiple SSO providers OR has email/password login
|
|
28
27
|
const allowUnlink = providers.length > 1 || hasPasswordAccount;
|
|
29
|
-
const
|
|
28
|
+
const enableAuthActions = !isDesktop && isLogin;
|
|
30
29
|
|
|
31
30
|
// Get linked provider IDs for filtering
|
|
32
31
|
const linkedProviderIds = useMemo(() => {
|
|
@@ -38,9 +37,9 @@ export const SSOProvidersList = memo(() => {
|
|
|
38
37
|
return (oAuthSSOProviders || []).filter((provider) => !linkedProviderIds.has(provider));
|
|
39
38
|
}, [oAuthSSOProviders, linkedProviderIds]);
|
|
40
39
|
|
|
41
|
-
const handleUnlinkSSO = async (provider: string
|
|
40
|
+
const handleUnlinkSSO = async (provider: string) => {
|
|
42
41
|
// Better-auth link/unlink operations are not available on desktop
|
|
43
|
-
if (isDesktop
|
|
42
|
+
if (isDesktop) return;
|
|
44
43
|
|
|
45
44
|
// Prevent unlink if this is the only login method
|
|
46
45
|
if (!allowUnlink) {
|
|
@@ -55,14 +54,8 @@ export const SSOProvidersList = memo(() => {
|
|
|
55
54
|
danger: true,
|
|
56
55
|
},
|
|
57
56
|
onOk: async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const { unlinkAccount } = await import('@/libs/better-auth/auth-client');
|
|
61
|
-
await unlinkAccount({ providerId: provider });
|
|
62
|
-
} else {
|
|
63
|
-
// Fallback for NextAuth
|
|
64
|
-
await userService.unlinkSSOProvider(provider, providerAccountId);
|
|
65
|
-
}
|
|
57
|
+
const { unlinkAccount } = await import('@/libs/better-auth/auth-client');
|
|
58
|
+
await unlinkAccount({ providerId: provider });
|
|
66
59
|
refreshAuthProviders();
|
|
67
60
|
},
|
|
68
61
|
title: <span style={providerNameStyle}>{t('profile.sso.unlink.title', { provider })}</span>,
|
|
@@ -70,7 +63,7 @@ export const SSOProvidersList = memo(() => {
|
|
|
70
63
|
};
|
|
71
64
|
|
|
72
65
|
const handleLinkSSO = async (provider: string) => {
|
|
73
|
-
if (
|
|
66
|
+
if (enableAuthActions) {
|
|
74
67
|
// Use better-auth native linkSocial API
|
|
75
68
|
const { linkSocial } = await import('@/libs/better-auth/auth-client');
|
|
76
69
|
await linkSocial({
|
|
@@ -107,19 +100,19 @@ export const SSOProvidersList = memo(() => {
|
|
|
107
100
|
</Text>
|
|
108
101
|
)}
|
|
109
102
|
</Flexbox>
|
|
110
|
-
{!
|
|
103
|
+
{!isDesktop && (
|
|
111
104
|
<ActionIcon
|
|
112
105
|
disabled={!allowUnlink}
|
|
113
106
|
icon={Unlink}
|
|
114
|
-
onClick={() => handleUnlinkSSO(item.provider
|
|
107
|
+
onClick={() => handleUnlinkSSO(item.provider)}
|
|
115
108
|
size={'small'}
|
|
116
109
|
/>
|
|
117
110
|
)}
|
|
118
111
|
</Flexbox>
|
|
119
112
|
))}
|
|
120
113
|
|
|
121
|
-
{/* Link Account Button - Only show for
|
|
122
|
-
{
|
|
114
|
+
{/* Link Account Button - Only show for logged in users with available providers */}
|
|
115
|
+
{enableAuthActions && availableProviders.length > 0 && (
|
|
123
116
|
<DropdownMenu items={linkMenuItems} popupProps={{ style: { maxWidth: '200px' } }}>
|
|
124
117
|
<Flexbox align={'center'} gap={6} horizontal style={{ cursor: 'pointer', fontSize: 12 }}>
|
|
125
118
|
<Plus size={14} />
|
|
@@ -51,10 +51,7 @@ interface ProfileSettingProps {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
|
54
|
-
const
|
|
55
|
-
authSelectors.isLoginWithNextAuth(s),
|
|
56
|
-
authSelectors.isLoginWithBetterAuth(s),
|
|
57
|
-
]);
|
|
54
|
+
const isLogin = useUserStore(authSelectors.isLogin);
|
|
58
55
|
const [userProfile, isUserLoaded] = useUserStore((s) => [
|
|
59
56
|
userProfileSelectors.userProfile(s),
|
|
60
57
|
s.isLoaded,
|
|
@@ -72,17 +69,14 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
|
|
72
69
|
// Fetch Klavis servers
|
|
73
70
|
useFetchUserKlavisServers(enableKlavis);
|
|
74
71
|
|
|
75
|
-
const isLoginWithAuth = isLoginWithNextAuth || isLoginWithBetterAuth;
|
|
76
72
|
const isLoading =
|
|
77
|
-
!isUserLoaded ||
|
|
78
|
-
(isLoginWithAuth && !isLoadedAuthProviders) ||
|
|
79
|
-
(enableKlavis && !isServersInit);
|
|
73
|
+
!isUserLoaded || (isLogin && !isLoadedAuthProviders) || (enableKlavis && !isServersInit);
|
|
80
74
|
|
|
81
75
|
useEffect(() => {
|
|
82
|
-
if (
|
|
76
|
+
if (isLogin) {
|
|
83
77
|
fetchAuthProviders();
|
|
84
78
|
}
|
|
85
|
-
}, [
|
|
79
|
+
}, [isLogin, fetchAuthProviders]);
|
|
86
80
|
|
|
87
81
|
const { t } = useTranslation('auth');
|
|
88
82
|
|
|
@@ -118,8 +112,8 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
|
|
118
112
|
{/* Interests Row - Editable */}
|
|
119
113
|
<InterestsRow mobile={mobile} />
|
|
120
114
|
|
|
121
|
-
{/* Password Row - For
|
|
122
|
-
{!isDesktop &&
|
|
115
|
+
{/* Password Row - For logged in users to change or set password */}
|
|
116
|
+
{!isDesktop && isLogin && (
|
|
123
117
|
<>
|
|
124
118
|
<Divider style={{ margin: 0 }} />
|
|
125
119
|
<PasswordRow mobile={mobile} />
|
|
@@ -127,7 +121,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
|
|
127
121
|
)}
|
|
128
122
|
|
|
129
123
|
{/* Email Row - Read Only */}
|
|
130
|
-
{
|
|
124
|
+
{isLogin && userProfile?.email && (
|
|
131
125
|
<>
|
|
132
126
|
<Divider style={{ margin: 0 }} />
|
|
133
127
|
<ProfileRow label={t('profile.email')} mobile={mobile}>
|
|
@@ -137,7 +131,7 @@ const ProfileSetting = ({ mobile }: ProfileSettingProps) => {
|
|
|
137
131
|
)}
|
|
138
132
|
|
|
139
133
|
{/* SSO Providers Row */}
|
|
140
|
-
{
|
|
134
|
+
{isLogin && (
|
|
141
135
|
<>
|
|
142
136
|
<Divider style={{ margin: 0 }} />
|
|
143
137
|
<ProfileRow label={t('profile.sso.providers')} mobile={mobile}>
|
|
@@ -8,12 +8,10 @@ import {
|
|
|
8
8
|
Github,
|
|
9
9
|
Logto,
|
|
10
10
|
MicrosoftEntra,
|
|
11
|
-
NextAuth,
|
|
12
11
|
Zitadel,
|
|
13
12
|
} from '@lobehub/ui/icons';
|
|
14
|
-
import
|
|
13
|
+
import { User } from 'lucide-react';
|
|
15
14
|
|
|
16
|
-
// TODO: check this
|
|
17
15
|
const iconComponents: { [key: string]: any } = {
|
|
18
16
|
'apple': Apple,
|
|
19
17
|
'auth0': Auth0,
|
|
@@ -22,7 +20,6 @@ const iconComponents: { [key: string]: any } = {
|
|
|
22
20
|
'casdoor': Casdoor.Color,
|
|
23
21
|
'cloudflare': Cloudflare.Color,
|
|
24
22
|
'cognito': Aws.Color,
|
|
25
|
-
'default': NextAuth.Color,
|
|
26
23
|
'github': Github,
|
|
27
24
|
'google': Google.Color,
|
|
28
25
|
'logto': Logto.Color,
|
|
@@ -32,14 +29,15 @@ const iconComponents: { [key: string]: any } = {
|
|
|
32
29
|
};
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
|
-
* Get the auth icons component for the given id
|
|
36
|
-
* @param id
|
|
37
|
-
* @param size default is 36
|
|
38
|
-
* @returns
|
|
32
|
+
* Get the auth icons component for the given provider id
|
|
39
33
|
*/
|
|
40
34
|
const AuthIcons = (id: string, size = 36) => {
|
|
41
|
-
const IconComponent = iconComponents[id]
|
|
42
|
-
|
|
35
|
+
const IconComponent = iconComponents[id];
|
|
36
|
+
if (IconComponent) {
|
|
37
|
+
return <IconComponent size={size} />;
|
|
38
|
+
}
|
|
39
|
+
// Fallback to generic user icon for unknown providers
|
|
40
|
+
return <User size={size} />;
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
export default AuthIcons;
|
package/src/envs/auth.ts
CHANGED
|
@@ -6,23 +6,15 @@ declare global {
|
|
|
6
6
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
7
7
|
namespace NodeJS {
|
|
8
8
|
interface ProcessEnv {
|
|
9
|
-
// =====
|
|
9
|
+
// ===== Better Auth ===== //
|
|
10
10
|
AUTH_SECRET?: string;
|
|
11
11
|
AUTH_EMAIL_VERIFICATION?: string;
|
|
12
12
|
ENABLE_MAGIC_LINK?: string;
|
|
13
13
|
AUTH_SSO_PROVIDERS?: string;
|
|
14
14
|
AUTH_TRUSTED_ORIGINS?: string;
|
|
15
|
+
AUTH_ALLOWED_EMAILS?: string;
|
|
15
16
|
|
|
16
|
-
// =====
|
|
17
|
-
NEXT_AUTH_SECRET?: string;
|
|
18
|
-
|
|
19
|
-
NEXT_AUTH_SSO_PROVIDERS?: string;
|
|
20
|
-
|
|
21
|
-
NEXT_AUTH_DEBUG?: string;
|
|
22
|
-
|
|
23
|
-
NEXT_AUTH_SSO_SESSION_STRATEGY?: string;
|
|
24
|
-
|
|
25
|
-
// ===== Next Auth Provider Credentials ===== //
|
|
17
|
+
// ===== Auth Provider Credentials ===== //
|
|
26
18
|
AUTH_GOOGLE_ID?: string;
|
|
27
19
|
AUTH_GOOGLE_SECRET?: string;
|
|
28
20
|
|
|
@@ -130,26 +122,14 @@ declare global {
|
|
|
130
122
|
|
|
131
123
|
export const getAuthConfig = () => {
|
|
132
124
|
return createEnv({
|
|
133
|
-
client: {
|
|
134
|
-
// ---------------------------------- better auth ----------------------------------
|
|
135
|
-
NEXT_PUBLIC_ENABLE_BETTER_AUTH: z.boolean().optional(),
|
|
136
|
-
|
|
137
|
-
// ---------------------------------- next auth ----------------------------------
|
|
138
|
-
NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
|
|
139
|
-
},
|
|
125
|
+
client: {},
|
|
140
126
|
server: {
|
|
141
|
-
// ---------------------------------- better auth ----------------------------------
|
|
142
127
|
AUTH_SECRET: z.string().optional(),
|
|
143
128
|
AUTH_SSO_PROVIDERS: z.string().optional().default(''),
|
|
144
129
|
AUTH_TRUSTED_ORIGINS: z.string().optional(),
|
|
145
130
|
AUTH_EMAIL_VERIFICATION: z.boolean().optional().default(false),
|
|
146
131
|
ENABLE_MAGIC_LINK: z.boolean().optional().default(false),
|
|
147
|
-
|
|
148
|
-
// ---------------------------------- next auth ----------------------------------
|
|
149
|
-
NEXT_AUTH_SECRET: z.string().optional(),
|
|
150
|
-
NEXT_AUTH_SSO_PROVIDERS: z.string().optional().default('auth0'),
|
|
151
|
-
NEXT_AUTH_DEBUG: z.boolean().optional().default(false),
|
|
152
|
-
NEXT_AUTH_SSO_SESSION_STRATEGY: z.enum(['jwt', 'database']).optional().default('jwt'),
|
|
132
|
+
AUTH_ALLOWED_EMAILS: z.string().optional(),
|
|
153
133
|
|
|
154
134
|
AUTH_GOOGLE_ID: z.string().optional(),
|
|
155
135
|
AUTH_GOOGLE_SECRET: z.string().optional(),
|
|
@@ -248,33 +228,19 @@ export const getAuthConfig = () => {
|
|
|
248
228
|
},
|
|
249
229
|
|
|
250
230
|
runtimeEnv: {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
process.env.AUTH_EMAIL_VERIFICATION === '1' ||
|
|
256
|
-
process.env.NEXT_PUBLIC_AUTH_EMAIL_VERIFICATION === '1',
|
|
257
|
-
ENABLE_MAGIC_LINK:
|
|
258
|
-
process.env.ENABLE_MAGIC_LINK === '1' || process.env.NEXT_PUBLIC_ENABLE_MAGIC_LINK === '1',
|
|
259
|
-
// Fallback to NEXT_AUTH_SECRET for seamless migration from next-auth
|
|
260
|
-
AUTH_SECRET: process.env.AUTH_SECRET || process.env.NEXT_AUTH_SECRET,
|
|
261
|
-
// Fallback to NEXT_AUTH_SSO_PROVIDERS for seamless migration from next-auth
|
|
262
|
-
AUTH_SSO_PROVIDERS: process.env.AUTH_SSO_PROVIDERS || process.env.NEXT_AUTH_SSO_PROVIDERS,
|
|
231
|
+
AUTH_EMAIL_VERIFICATION: process.env.AUTH_EMAIL_VERIFICATION === '1',
|
|
232
|
+
ENABLE_MAGIC_LINK: process.env.ENABLE_MAGIC_LINK === '1',
|
|
233
|
+
AUTH_SECRET: process.env.AUTH_SECRET,
|
|
234
|
+
AUTH_SSO_PROVIDERS: process.env.AUTH_SSO_PROVIDERS,
|
|
263
235
|
AUTH_TRUSTED_ORIGINS: process.env.AUTH_TRUSTED_ORIGINS,
|
|
236
|
+
AUTH_ALLOWED_EMAILS: process.env.AUTH_ALLOWED_EMAILS,
|
|
264
237
|
|
|
265
|
-
//
|
|
238
|
+
// Cognito provider specific env vars
|
|
266
239
|
AUTH_COGNITO_DOMAIN: process.env.AUTH_COGNITO_DOMAIN,
|
|
267
240
|
AUTH_COGNITO_REGION: process.env.AUTH_COGNITO_REGION,
|
|
268
241
|
AUTH_COGNITO_USERPOOL_ID: process.env.AUTH_COGNITO_USERPOOL_ID,
|
|
269
242
|
|
|
270
|
-
//
|
|
271
|
-
NEXT_PUBLIC_ENABLE_NEXT_AUTH: process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1',
|
|
272
|
-
NEXT_AUTH_SSO_PROVIDERS: process.env.NEXT_AUTH_SSO_PROVIDERS,
|
|
273
|
-
NEXT_AUTH_SECRET: process.env.NEXT_AUTH_SECRET,
|
|
274
|
-
NEXT_AUTH_DEBUG: !!process.env.NEXT_AUTH_DEBUG,
|
|
275
|
-
NEXT_AUTH_SSO_SESSION_STRATEGY: process.env.NEXT_AUTH_SSO_SESSION_STRATEGY || 'jwt',
|
|
276
|
-
|
|
277
|
-
// Next Auth Provider Credentials
|
|
243
|
+
// Auth Provider Credentials
|
|
278
244
|
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
|
|
279
245
|
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
|
|
280
246
|
|
|
@@ -374,11 +340,6 @@ export const getAuthConfig = () => {
|
|
|
374
340
|
|
|
375
341
|
export const authEnv = getAuthConfig();
|
|
376
342
|
|
|
377
|
-
// Auth flags - use process.env directly for build-time dead code elimination
|
|
378
|
-
// Better Auth is the default auth solution when NextAuth is not explicitly enabled
|
|
379
|
-
export const enableNextAuth = process.env.NEXT_PUBLIC_ENABLE_NEXT_AUTH === '1';
|
|
380
|
-
export const enableBetterAuth = !enableNextAuth;
|
|
381
|
-
|
|
382
343
|
// Auth headers and constants
|
|
383
344
|
export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
|
|
384
345
|
export const LOBE_CHAT_OIDC_AUTH_HEADER = 'Oidc-Auth';
|
package/src/envs/email.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare global {
|
|
|
9
9
|
EMAIL_SERVICE_PROVIDER?: string;
|
|
10
10
|
RESEND_API_KEY?: string;
|
|
11
11
|
RESEND_FROM?: string;
|
|
12
|
+
SMTP_FROM?: string;
|
|
12
13
|
SMTP_HOST?: string;
|
|
13
14
|
SMTP_PASS?: string;
|
|
14
15
|
SMTP_PORT?: string;
|
|
@@ -24,6 +25,7 @@ export const getEmailConfig = () => {
|
|
|
24
25
|
EMAIL_SERVICE_PROVIDER: z.enum(['nodemailer', 'resend']).optional(),
|
|
25
26
|
RESEND_API_KEY: z.string().optional(),
|
|
26
27
|
RESEND_FROM: z.string().optional(),
|
|
28
|
+
SMTP_FROM: z.string().optional(),
|
|
27
29
|
SMTP_HOST: z.string().optional(),
|
|
28
30
|
SMTP_PORT: z.coerce.number().optional(),
|
|
29
31
|
SMTP_SECURE: z.boolean().optional(),
|
|
@@ -31,6 +33,7 @@ export const getEmailConfig = () => {
|
|
|
31
33
|
SMTP_PASS: z.string().optional(),
|
|
32
34
|
},
|
|
33
35
|
runtimeEnv: {
|
|
36
|
+
SMTP_FROM: process.env.SMTP_FROM,
|
|
34
37
|
SMTP_HOST: process.env.SMTP_HOST,
|
|
35
38
|
SMTP_PORT: process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined,
|
|
36
39
|
SMTP_SECURE: process.env.SMTP_SECURE === 'true',
|
package/src/envs/redis.ts
CHANGED
|
@@ -4,8 +4,6 @@ import { z } from 'zod';
|
|
|
4
4
|
|
|
5
5
|
import type { RedisConfig } from '@/libs/redis';
|
|
6
6
|
|
|
7
|
-
type UpstashRedisConfig = { token: string; url: string };
|
|
8
|
-
|
|
9
7
|
const parseNumber = (value?: string) => {
|
|
10
8
|
const parsed = Number.parseInt(value ?? '', 10);
|
|
11
9
|
|
|
@@ -30,8 +28,6 @@ export const getRedisEnv = () => {
|
|
|
30
28
|
REDIS_TLS: parseRedisTls(process.env.REDIS_TLS),
|
|
31
29
|
REDIS_URL: process.env.REDIS_URL,
|
|
32
30
|
REDIS_USERNAME: process.env.REDIS_USERNAME,
|
|
33
|
-
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
|
|
34
|
-
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
|
|
35
31
|
},
|
|
36
32
|
server: {
|
|
37
33
|
REDIS_DATABASE: z.number().int().optional(),
|
|
@@ -40,67 +36,29 @@ export const getRedisEnv = () => {
|
|
|
40
36
|
REDIS_TLS: z.boolean().default(false),
|
|
41
37
|
REDIS_URL: z.string().url().optional(),
|
|
42
38
|
REDIS_USERNAME: z.string().optional(),
|
|
43
|
-
UPSTASH_REDIS_REST_TOKEN: z.string().optional(),
|
|
44
|
-
UPSTASH_REDIS_REST_URL: z.string().url().optional(),
|
|
45
39
|
},
|
|
46
40
|
});
|
|
47
41
|
};
|
|
48
42
|
|
|
49
43
|
export const redisEnv = getRedisEnv();
|
|
50
44
|
|
|
51
|
-
export const getUpstashRedisConfig = (): UpstashRedisConfig | null => {
|
|
52
|
-
const upstashConfigSchema = z.union([
|
|
53
|
-
z.object({
|
|
54
|
-
token: z.string(),
|
|
55
|
-
url: z.string().url(),
|
|
56
|
-
}),
|
|
57
|
-
z.object({
|
|
58
|
-
token: z.undefined().optional(),
|
|
59
|
-
url: z.undefined().optional(),
|
|
60
|
-
}),
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
const parsed = upstashConfigSchema.safeParse({
|
|
64
|
-
token: redisEnv.UPSTASH_REDIS_REST_TOKEN,
|
|
65
|
-
url: redisEnv.UPSTASH_REDIS_REST_URL,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (!parsed.success) throw parsed.error;
|
|
69
|
-
if (!parsed.data.token || !parsed.data.url) return null;
|
|
70
|
-
|
|
71
|
-
return parsed.data;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
45
|
export const getRedisConfig = (): RedisConfig => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (redisEnv.REDIS_URL) {
|
|
78
|
-
return {
|
|
79
|
-
database: redisEnv.REDIS_DATABASE,
|
|
80
|
-
enabled: true,
|
|
81
|
-
password: redisEnv.REDIS_PASSWORD,
|
|
82
|
-
prefix,
|
|
83
|
-
provider: 'redis',
|
|
84
|
-
tls: redisEnv.REDIS_TLS,
|
|
85
|
-
url: redisEnv.REDIS_URL,
|
|
86
|
-
username: redisEnv.REDIS_USERNAME,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const upstashConfig = getUpstashRedisConfig();
|
|
91
|
-
if (upstashConfig) {
|
|
46
|
+
if (!redisEnv.REDIS_URL) {
|
|
92
47
|
return {
|
|
93
|
-
enabled:
|
|
94
|
-
prefix,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
url: upstashConfig.url,
|
|
48
|
+
enabled: false,
|
|
49
|
+
prefix: redisEnv.REDIS_PREFIX,
|
|
50
|
+
tls: false,
|
|
51
|
+
url: '',
|
|
98
52
|
};
|
|
99
53
|
}
|
|
100
54
|
|
|
101
55
|
return {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
56
|
+
database: redisEnv.REDIS_DATABASE,
|
|
57
|
+
enabled: true,
|
|
58
|
+
password: redisEnv.REDIS_PASSWORD,
|
|
59
|
+
prefix: redisEnv.REDIS_PREFIX,
|
|
60
|
+
tls: redisEnv.REDIS_TLS,
|
|
61
|
+
url: redisEnv.REDIS_URL,
|
|
62
|
+
username: redisEnv.REDIS_USERNAME,
|
|
105
63
|
};
|
|
106
64
|
};
|