@lobehub/chat 0.162.25 → 0.164.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.
- package/.github/workflows/release.yml +21 -2
- package/.github/workflows/sync.yml +1 -1
- package/.github/workflows/test.yml +35 -4
- package/CHANGELOG.md +63 -0
- package/LICENSE +38 -21
- package/codecov.yml +11 -0
- package/drizzle.config.ts +29 -0
- package/next.config.mjs +3 -0
- package/package.json +24 -4
- package/scripts/migrateServerDB/index.ts +30 -0
- package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +2 -1
- package/src/app/(main)/chat/@session/features/SessionListContent/List/Item/Actions.tsx +95 -88
- package/src/app/(main)/chat/settings/features/HeaderContent.tsx +37 -31
- package/src/app/api/webhooks/clerk/__tests__/fixtures/createUser.json +73 -0
- package/src/app/api/webhooks/clerk/route.ts +159 -0
- package/src/app/api/webhooks/clerk/validateRequest.ts +22 -0
- package/src/app/trpc/edge/[trpc]/route.ts +1 -1
- package/src/app/trpc/lambda/[trpc]/route.ts +26 -0
- package/src/config/auth.ts +2 -0
- package/src/config/db.ts +13 -1
- package/src/database/server/core/db.ts +44 -0
- package/src/database/server/core/dbForTest.ts +45 -0
- package/src/database/server/index.ts +1 -0
- package/src/database/server/migrations/0000_init.sql +439 -0
- package/src/database/server/migrations/0001_add_client_id.sql +9 -0
- package/src/database/server/migrations/0002_amusing_puma.sql +9 -0
- package/src/database/server/migrations/meta/0000_snapshot.json +1583 -0
- package/src/database/server/migrations/meta/0001_snapshot.json +1636 -0
- package/src/database/server/migrations/meta/0002_snapshot.json +1630 -0
- package/src/database/server/migrations/meta/_journal.json +27 -0
- package/src/database/server/models/__tests__/file.test.ts +140 -0
- package/src/database/server/models/__tests__/message.test.ts +847 -0
- package/src/database/server/models/__tests__/plugin.test.ts +172 -0
- package/src/database/server/models/__tests__/session.test.ts +595 -0
- package/src/database/server/models/__tests__/topic.test.ts +623 -0
- package/src/database/server/models/__tests__/user.test.ts +173 -0
- package/src/database/server/models/_template.ts +44 -0
- package/src/database/server/models/file.ts +51 -0
- package/src/database/server/models/message.ts +378 -0
- package/src/database/server/models/plugin.ts +63 -0
- package/src/database/server/models/session.ts +290 -0
- package/src/database/server/models/sessionGroup.ts +69 -0
- package/src/database/server/models/topic.ts +265 -0
- package/src/database/server/models/user.ts +138 -0
- package/src/database/server/modules/DataImporter/__tests__/fixtures/messages.json +1101 -0
- package/src/database/server/modules/DataImporter/__tests__/index.test.ts +954 -0
- package/src/database/server/modules/DataImporter/index.ts +333 -0
- package/src/database/server/schemas/_id.ts +15 -0
- package/src/database/server/schemas/lobechat.ts +601 -0
- package/src/database/server/utils/idGenerator.test.ts +39 -0
- package/src/database/server/utils/idGenerator.ts +26 -0
- package/src/features/User/UserPanel/useMenu.tsx +43 -37
- package/src/libs/trpc/client.ts +52 -3
- package/src/server/files/s3.ts +21 -1
- package/src/server/keyVaultsEncrypt/index.test.ts +62 -0
- package/src/server/keyVaultsEncrypt/index.ts +93 -0
- package/src/server/mock.ts +1 -1
- package/src/server/routers/{index.ts → edge/index.ts} +3 -3
- package/src/server/routers/lambda/file.ts +49 -0
- package/src/server/routers/lambda/importer.ts +54 -0
- package/src/server/routers/lambda/index.ts +28 -0
- package/src/server/routers/lambda/message.ts +165 -0
- package/src/server/routers/lambda/plugin.ts +100 -0
- package/src/server/routers/lambda/session.ts +194 -0
- package/src/server/routers/lambda/sessionGroup.ts +77 -0
- package/src/server/routers/lambda/topic.ts +134 -0
- package/src/server/routers/lambda/user.ts +57 -0
- package/src/services/file/index.ts +4 -7
- package/src/services/file/server.ts +45 -0
- package/src/services/import/index.ts +4 -1
- package/src/services/import/server.ts +115 -0
- package/src/services/message/index.ts +4 -8
- package/src/services/message/server.ts +93 -0
- package/src/services/plugin/index.ts +4 -9
- package/src/services/plugin/server.ts +46 -0
- package/src/services/session/index.ts +4 -8
- package/src/services/session/server.ts +148 -0
- package/src/services/topic/index.ts +4 -9
- package/src/services/topic/server.ts +68 -0
- package/src/services/user/index.ts +4 -9
- package/src/services/user/server.ts +28 -0
- package/tests/setup-db.ts +7 -0
- package/vitest.config.ts +2 -1
- package/vitest.server.config.ts +23 -0
|
@@ -5,6 +5,7 @@ import { memo, useMemo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
|
8
|
+
import { isServerMode } from '@/const/version';
|
|
8
9
|
import { configService } from '@/services/config';
|
|
9
10
|
import { useServerConfigStore } from '@/store/serverConfig';
|
|
10
11
|
import { useSessionStore } from '@/store/session';
|
|
@@ -18,45 +19,50 @@ export const HeaderContent = memo<{ mobile?: boolean; modal?: boolean }>(({ moda
|
|
|
18
19
|
const mobile = useServerConfigStore((s) => s.isMobile);
|
|
19
20
|
|
|
20
21
|
const items = useMemo<MenuProps['items']>(
|
|
21
|
-
() =>
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
() =>
|
|
23
|
+
isServerMode
|
|
24
|
+
? []
|
|
25
|
+
: [
|
|
26
|
+
{
|
|
27
|
+
key: 'agent',
|
|
28
|
+
label: <div>{t('exportType.agent', { ns: 'common' })}</div>,
|
|
29
|
+
onClick: () => {
|
|
30
|
+
if (!id) return;
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
configService.exportSingleAgent(id);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'agentWithMessage',
|
|
37
|
+
label: <div>{t('exportType.agentWithMessage', { ns: 'common' })}</div>,
|
|
38
|
+
onClick: () => {
|
|
39
|
+
if (!id) return;
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
configService.exportSingleSession(id);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
41
45
|
[],
|
|
42
46
|
);
|
|
43
47
|
|
|
44
48
|
return (
|
|
45
49
|
<>
|
|
46
50
|
<SubmitAgentButton modal={modal} />
|
|
47
|
-
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
{!isServerMode && (
|
|
52
|
+
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
|
|
53
|
+
{modal ? (
|
|
54
|
+
<Button block icon={<Icon icon={HardDriveDownload} />}>
|
|
55
|
+
{t('export', { ns: 'common' })}
|
|
56
|
+
</Button>
|
|
57
|
+
) : (
|
|
58
|
+
<ActionIcon
|
|
59
|
+
icon={HardDriveDownload}
|
|
60
|
+
size={HEADER_ICON_SIZE(mobile)}
|
|
61
|
+
title={t('export', { ns: 'common' })}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</Dropdown>
|
|
65
|
+
)}
|
|
60
66
|
</>
|
|
61
67
|
);
|
|
62
68
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"backup_code_enabled": false,
|
|
3
|
+
"banned": false,
|
|
4
|
+
"create_organization_enabled": true,
|
|
5
|
+
"created_at": 1713709987911,
|
|
6
|
+
"delete_self_enabled": true,
|
|
7
|
+
"email_addresses": [
|
|
8
|
+
{
|
|
9
|
+
"created_at": 1713709977919,
|
|
10
|
+
"email_address": "arvinx@foxmail.com",
|
|
11
|
+
"id": "idn_2fPkD9X1lfzSn5lJVDGyochYq8k",
|
|
12
|
+
"linked_to": [],
|
|
13
|
+
"object": "email_address",
|
|
14
|
+
"reserved": false,
|
|
15
|
+
"updated_at": 1713709987951,
|
|
16
|
+
"verification": []
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"external_accounts": [
|
|
20
|
+
{
|
|
21
|
+
"approved_scopes": "read:user user:email",
|
|
22
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/28616219?v=4",
|
|
23
|
+
"created_at": 1713709542104,
|
|
24
|
+
"email_address": "arvinx@foxmail.com",
|
|
25
|
+
"first_name": "Arvin",
|
|
26
|
+
"id": "eac_2fPjKROeJ1bBs8Uxa6RFMxKogTB",
|
|
27
|
+
"identification_id": "idn_2fPjyV3sqtQJZUbEzdK2y23a1bq",
|
|
28
|
+
"image_url": "https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2F2YXRhcnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3UvMjg2MTYyMTk/dj00IiwicyI6IkhCeHE5NmdlRk85ekRxMjJlR05EalUrbVFBbmVDZjRVQkpwNGYxcW5JajQifQ",
|
|
29
|
+
"label": null,
|
|
30
|
+
"last_name": "Xu",
|
|
31
|
+
"object": "external_account",
|
|
32
|
+
"provider": "oauth_github",
|
|
33
|
+
"provider_user_id": "28616219",
|
|
34
|
+
"public_metadata": {},
|
|
35
|
+
"updated_at": 1713709542104,
|
|
36
|
+
"username": "arvinxx",
|
|
37
|
+
"verification": {
|
|
38
|
+
"attempts": null,
|
|
39
|
+
"expire_at": 1713710140131,
|
|
40
|
+
"status": "verified",
|
|
41
|
+
"strategy": "oauth_github"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"external_id": null,
|
|
46
|
+
"first_name": "Arvin",
|
|
47
|
+
"has_image": true,
|
|
48
|
+
"id": "user_2fPkELglwI48WpZVwwdAxBKBPK6",
|
|
49
|
+
"image_url": "https://img.clerk.com/eyJ0eXBlIjoicHJveHkiLCJzcmMiOiJodHRwczovL2ltYWdlcy5jbGVyay5kZXYvb2F1dGhfZ2l0aHViL2ltZ18yZlBrRU1adVpwdlpvZFBHcVREdHJnTzJJM3cifQ",
|
|
50
|
+
"last_active_at": 1713709987902,
|
|
51
|
+
"last_name": "Xu",
|
|
52
|
+
"last_sign_in_at": null,
|
|
53
|
+
"locked": false,
|
|
54
|
+
"lockout_expires_in_seconds": null,
|
|
55
|
+
"object": "user",
|
|
56
|
+
"passkeys": [],
|
|
57
|
+
"password_enabled": false,
|
|
58
|
+
"phone_numbers": [],
|
|
59
|
+
"primary_email_address_id": "idn_2fPkD9X1lfzSn5lJVDGyochYq8k",
|
|
60
|
+
"primary_phone_number_id": null,
|
|
61
|
+
"primary_web3_wallet_id": null,
|
|
62
|
+
"private_metadata": {},
|
|
63
|
+
"profile_image_url": "https://images.clerk.dev/oauth_github/img_2fPkEMZuZpvZodPGqTDtrgO2I3w",
|
|
64
|
+
"public_metadata": {},
|
|
65
|
+
"saml_accounts": [],
|
|
66
|
+
"totp_enabled": false,
|
|
67
|
+
"two_factor_enabled": false,
|
|
68
|
+
"unsafe_metadata": {},
|
|
69
|
+
"updated_at": 1713709987972,
|
|
70
|
+
"username": "arvinxx",
|
|
71
|
+
"verification_attempts_remaining": 100,
|
|
72
|
+
"web3_wallets": []
|
|
73
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { UserJSON } from '@clerk/backend';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
|
|
4
|
+
import { authEnv } from '@/config/auth';
|
|
5
|
+
import { isServerMode } from '@/const/version';
|
|
6
|
+
import { UserModel } from '@/database/server/models/user';
|
|
7
|
+
import { pino } from '@/libs/logger';
|
|
8
|
+
|
|
9
|
+
import { validateRequest } from './validateRequest';
|
|
10
|
+
|
|
11
|
+
if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH && isServerMode && !authEnv.CLERK_WEBHOOK_SECRET) {
|
|
12
|
+
throw new Error('`CLERK_WEBHOOK_SECRET` environment variable is missing');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const createUser = async (id: string, params: UserJSON) => {
|
|
16
|
+
pino.info('creating user due to clerk webhook');
|
|
17
|
+
|
|
18
|
+
const userModel = new UserModel();
|
|
19
|
+
|
|
20
|
+
// Check if user already exists
|
|
21
|
+
const res = await userModel.findById(id);
|
|
22
|
+
|
|
23
|
+
// If user already exists, skip creating a new user
|
|
24
|
+
if (res)
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ message: 'user not created due to user already existing in the database', success: false },
|
|
27
|
+
{ status: 200 },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const email = params.email_addresses.find((e) => e.id === params.primary_email_address_id);
|
|
31
|
+
const phone = params.phone_numbers.find((e) => e.id === params.primary_phone_number_id);
|
|
32
|
+
|
|
33
|
+
await userModel.createUser({
|
|
34
|
+
avatar: params.image_url,
|
|
35
|
+
clerkCreatedAt: new Date(params.created_at),
|
|
36
|
+
email: email?.email_address,
|
|
37
|
+
firstName: params.first_name,
|
|
38
|
+
id,
|
|
39
|
+
lastName: params.last_name,
|
|
40
|
+
phone: phone?.phone_number,
|
|
41
|
+
username: params.username,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return NextResponse.json({ message: 'user created', success: true }, { status: 200 });
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const deleteUser = async (id?: string) => {
|
|
48
|
+
if (id) {
|
|
49
|
+
pino.info('delete user due to clerk webhook');
|
|
50
|
+
const userModel = new UserModel();
|
|
51
|
+
|
|
52
|
+
await userModel.deleteUser(id);
|
|
53
|
+
|
|
54
|
+
return NextResponse.json({ message: 'user deleted' }, { status: 200 });
|
|
55
|
+
} else {
|
|
56
|
+
pino.warn('clerk sent a delete user request, but no user ID was included in the payload');
|
|
57
|
+
return NextResponse.json({ message: 'ok' }, { status: 200 });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const updateUser = async (id: string, params: UserJSON) => {
|
|
62
|
+
pino.info('updating user due to clerk webhook');
|
|
63
|
+
|
|
64
|
+
const userModel = new UserModel();
|
|
65
|
+
|
|
66
|
+
// Check if user already exists
|
|
67
|
+
const res = await userModel.findById(id);
|
|
68
|
+
|
|
69
|
+
// If user not exists, skip update the user
|
|
70
|
+
if (!res)
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{
|
|
73
|
+
message: "user not updated due to the user don't existing in the database",
|
|
74
|
+
success: false,
|
|
75
|
+
},
|
|
76
|
+
{ status: 200 },
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const email = params.email_addresses.find((e) => e.id === params.primary_email_address_id);
|
|
80
|
+
const phone = params.phone_numbers.find((e) => e.id === params.primary_phone_number_id);
|
|
81
|
+
|
|
82
|
+
await userModel.updateUser(id, {
|
|
83
|
+
avatar: params.image_url,
|
|
84
|
+
email: email?.email_address,
|
|
85
|
+
firstName: params.first_name,
|
|
86
|
+
id,
|
|
87
|
+
lastName: params.last_name,
|
|
88
|
+
phone: phone?.phone_number,
|
|
89
|
+
username: params.username,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return NextResponse.json({ message: 'user updated', success: true }, { status: 200 });
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const POST = async (req: Request): Promise<NextResponse> => {
|
|
96
|
+
const payload = await validateRequest(req, authEnv.CLERK_WEBHOOK_SECRET!);
|
|
97
|
+
|
|
98
|
+
if (!payload) {
|
|
99
|
+
return NextResponse.json(
|
|
100
|
+
{ error: 'webhook verification failed or payload was malformed' },
|
|
101
|
+
{ status: 400 },
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { type, data } = payload;
|
|
106
|
+
|
|
107
|
+
pino.trace(`clerk webhook payload: ${{ data, type }}`);
|
|
108
|
+
|
|
109
|
+
switch (type) {
|
|
110
|
+
case 'user.created': {
|
|
111
|
+
return createUser(data.id, data);
|
|
112
|
+
}
|
|
113
|
+
case 'user.deleted': {
|
|
114
|
+
return deleteUser(data.id);
|
|
115
|
+
}
|
|
116
|
+
case 'user.updated': {
|
|
117
|
+
return updateUser(data.id, data);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
default: {
|
|
121
|
+
pino.warn(
|
|
122
|
+
`${req.url} received event type "${type}", but no handler is defined for this type`,
|
|
123
|
+
);
|
|
124
|
+
return NextResponse.json({ error: `uncreognised payload type: ${type}` }, { status: 400 });
|
|
125
|
+
}
|
|
126
|
+
// case 'user.updated':
|
|
127
|
+
// break;
|
|
128
|
+
// case 'session.created':
|
|
129
|
+
// break;
|
|
130
|
+
// case 'session.ended':
|
|
131
|
+
// break;
|
|
132
|
+
// case 'session.removed':
|
|
133
|
+
// break;
|
|
134
|
+
// case 'session.revoked':
|
|
135
|
+
// break;
|
|
136
|
+
// case 'email.created':
|
|
137
|
+
// break;
|
|
138
|
+
// case 'sms.created':
|
|
139
|
+
// break;
|
|
140
|
+
// case 'organization.created':
|
|
141
|
+
// break;
|
|
142
|
+
// case 'organization.updated':
|
|
143
|
+
// break;
|
|
144
|
+
// case 'organization.deleted':
|
|
145
|
+
// break;
|
|
146
|
+
// case 'organizationMembership.created':
|
|
147
|
+
// break;
|
|
148
|
+
// case 'organizationMembership.deleted':
|
|
149
|
+
// break;
|
|
150
|
+
// case 'organizationMembership.updated':
|
|
151
|
+
// break;
|
|
152
|
+
// case 'organizationInvitation.accepted':
|
|
153
|
+
// break;
|
|
154
|
+
// case 'organizationInvitation.created':
|
|
155
|
+
// break;
|
|
156
|
+
// case 'organizationInvitation.revoked':
|
|
157
|
+
// break;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { WebhookEvent } from '@clerk/nextjs/server';
|
|
2
|
+
import { headers } from 'next/headers';
|
|
3
|
+
import { Webhook } from 'svix';
|
|
4
|
+
|
|
5
|
+
export const validateRequest = async (request: Request, secret: string) => {
|
|
6
|
+
const payloadString = await request.text();
|
|
7
|
+
const headerPayload = headers();
|
|
8
|
+
|
|
9
|
+
const svixHeaders = {
|
|
10
|
+
'svix-id': headerPayload.get('svix-id')!,
|
|
11
|
+
'svix-signature': headerPayload.get('svix-signature')!,
|
|
12
|
+
'svix-timestamp': headerPayload.get('svix-timestamp')!,
|
|
13
|
+
};
|
|
14
|
+
const wh = new Webhook(secret);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
return wh.verify(payloadString, svixHeaders) as WebhookEvent;
|
|
18
|
+
} catch {
|
|
19
|
+
console.error('incoming webhook failed verification');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -3,7 +3,7 @@ import type { NextRequest } from 'next/server';
|
|
|
3
3
|
|
|
4
4
|
import { pino } from '@/libs/logger';
|
|
5
5
|
import { createContext } from '@/server/context';
|
|
6
|
-
import { edgeRouter } from '@/server/routers';
|
|
6
|
+
import { edgeRouter } from '@/server/routers/edge';
|
|
7
7
|
|
|
8
8
|
export const runtime = 'edge';
|
|
9
9
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
|
2
|
+
import type { NextRequest } from 'next/server';
|
|
3
|
+
|
|
4
|
+
import { pino } from '@/libs/logger';
|
|
5
|
+
import { createContext } from '@/server/context';
|
|
6
|
+
import { lambdaRouter } from '@/server/routers/lambda';
|
|
7
|
+
|
|
8
|
+
const handler = (req: NextRequest) =>
|
|
9
|
+
fetchRequestHandler({
|
|
10
|
+
/**
|
|
11
|
+
* @link https://trpc.io/docs/v11/context
|
|
12
|
+
*/
|
|
13
|
+
createContext: () => createContext(req),
|
|
14
|
+
|
|
15
|
+
endpoint: '/trpc/lambda',
|
|
16
|
+
|
|
17
|
+
onError: ({ error, path, type }) => {
|
|
18
|
+
pino.info(`Error in tRPC handler (lambda) on path: ${path}, type: ${type}`);
|
|
19
|
+
console.error(error);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
req,
|
|
23
|
+
router: lambdaRouter,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export { handler as GET, handler as POST };
|
package/src/config/auth.ts
CHANGED
|
@@ -75,6 +75,7 @@ export const getAuthConfig = () => {
|
|
|
75
75
|
server: {
|
|
76
76
|
// Clerk
|
|
77
77
|
CLERK_SECRET_KEY: z.string().optional(),
|
|
78
|
+
CLERK_WEBHOOK_SECRET: z.string().optional(),
|
|
78
79
|
|
|
79
80
|
// NEXT-AUTH
|
|
80
81
|
NEXT_AUTH_SECRET: z.string().optional(),
|
|
@@ -110,6 +111,7 @@ export const getAuthConfig = () => {
|
|
|
110
111
|
NEXT_PUBLIC_ENABLE_CLERK_AUTH: !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
|
111
112
|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
|
112
113
|
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
|
114
|
+
CLERK_WEBHOOK_SECRET: process.env.CLERK_WEBHOOK_SECRET,
|
|
113
115
|
|
|
114
116
|
// Next Auth
|
|
115
117
|
NEXT_PUBLIC_ENABLE_NEXT_AUTH:
|
package/src/config/db.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
|
|
2
1
|
import { createEnv } from '@t3-oss/env-nextjs';
|
|
3
2
|
import { z } from 'zod';
|
|
4
3
|
|
|
@@ -8,8 +7,21 @@ export const getServerDBConfig = () => {
|
|
|
8
7
|
NEXT_PUBLIC_ENABLED_SERVER_SERVICE: z.boolean(),
|
|
9
8
|
},
|
|
10
9
|
runtimeEnv: {
|
|
10
|
+
DATABASE_DRIVER: process.env.DATABASE_DRIVER || 'neon',
|
|
11
|
+
DATABASE_TEST_URL: process.env.DATABASE_TEST_URL,
|
|
12
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
13
|
+
|
|
14
|
+
KEY_VAULTS_SECRET: process.env.KEY_VAULTS_SECRET,
|
|
15
|
+
|
|
11
16
|
NEXT_PUBLIC_ENABLED_SERVER_SERVICE: process.env.NEXT_PUBLIC_SERVICE_MODE === 'server',
|
|
12
17
|
},
|
|
18
|
+
server: {
|
|
19
|
+
DATABASE_DRIVER: z.enum(['neon', 'node']),
|
|
20
|
+
DATABASE_TEST_URL: z.string().optional(),
|
|
21
|
+
DATABASE_URL: z.string().optional(),
|
|
22
|
+
|
|
23
|
+
KEY_VAULTS_SECRET: z.string().optional(),
|
|
24
|
+
},
|
|
13
25
|
});
|
|
14
26
|
};
|
|
15
27
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
|
|
2
|
+
import { NeonDatabase, drizzle as neonDrizzle } from 'drizzle-orm/neon-serverless';
|
|
3
|
+
import { drizzle as nodeDrizzle } from 'drizzle-orm/node-postgres';
|
|
4
|
+
import { Pool as NodePool } from 'pg';
|
|
5
|
+
import ws from 'ws';
|
|
6
|
+
|
|
7
|
+
import { serverDBEnv } from '@/config/db';
|
|
8
|
+
import { isServerMode } from '@/const/version';
|
|
9
|
+
|
|
10
|
+
import * as schema from '../schemas/lobechat';
|
|
11
|
+
|
|
12
|
+
export const getDBInstance = (): NeonDatabase<typeof schema> => {
|
|
13
|
+
if (!isServerMode) return {} as any;
|
|
14
|
+
|
|
15
|
+
if (!serverDBEnv.KEY_VAULTS_SECRET) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
` \`KEY_VAULTS_SECRET\` is not set, please set it in your environment variables.
|
|
18
|
+
|
|
19
|
+
If you don't have it, please run \`openssl rand -base64 32\` to create one.
|
|
20
|
+
`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let connectionString = serverDBEnv.DATABASE_URL;
|
|
25
|
+
|
|
26
|
+
if (!connectionString) {
|
|
27
|
+
throw new Error(`You are try to use database, but "DATABASE_URL" is not set correctly`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (serverDBEnv.DATABASE_DRIVER === 'node') {
|
|
31
|
+
const client = new NodePool({ connectionString });
|
|
32
|
+
return nodeDrizzle(client, { schema });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.env.MIGRATION_DB === '1') {
|
|
36
|
+
// https://github.com/neondatabase/serverless/blob/main/CONFIG.md#websocketconstructor-typeof-websocket--undefined
|
|
37
|
+
neonConfig.webSocketConstructor = ws;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const client = new NeonPool({ connectionString });
|
|
41
|
+
return neonDrizzle(client, { schema });
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const serverDB = getDBInstance();
|
|
@@ -0,0 +1,45 @@
|
|
|
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/lobechat';
|
|
13
|
+
|
|
14
|
+
export const getTestDBInstance = async () => {
|
|
15
|
+
let connectionString = serverDBEnv.DATABASE_TEST_URL;
|
|
16
|
+
|
|
17
|
+
if (!connectionString) {
|
|
18
|
+
throw new Error(`You are try to use database, but "DATABASE_TEST_URL" is not set correctly`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (serverDBEnv.DATABASE_DRIVER === 'node') {
|
|
22
|
+
const client = new NodePool({ connectionString });
|
|
23
|
+
|
|
24
|
+
const db = nodeDrizzle(client, { schema });
|
|
25
|
+
|
|
26
|
+
await nodeMigrator.migrate(db, {
|
|
27
|
+
migrationsFolder: join(__dirname, '../migrations'),
|
|
28
|
+
});
|
|
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, {
|
|
41
|
+
migrationsFolder: join(__dirname, '../migrations'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return db;
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { serverDB } from './core/db';
|