|
1 | 1 | import { db, generateId } from '../index'; |
2 | 2 | import { userSettings, type UserSettings, type NewUserSettings } from '../schema'; |
3 | 3 | import { eq } from 'drizzle-orm'; |
| 4 | +import { decryptApiKey, encryptApiKey, isEncrypted } from '$lib/encryption'; |
| 5 | + |
| 6 | +type UserSettingsUpdate = Partial< |
| 7 | + Omit<NewUserSettings, 'id' | 'userId' | 'createdAt' | 'updatedAt'> |
| 8 | +>; |
| 9 | + |
| 10 | +export type PublicUserSettings = Omit<UserSettings, 'karakeepApiKey'> & { |
| 11 | + hasKarakeepApiKey: boolean; |
| 12 | +}; |
| 13 | + |
| 14 | +function decryptUserSettingsSecrets(settings: UserSettings): UserSettings { |
| 15 | + if (!settings.karakeepApiKey) { |
| 16 | + return settings; |
| 17 | + } |
| 18 | + |
| 19 | + return { |
| 20 | + ...settings, |
| 21 | + karakeepApiKey: isEncrypted(settings.karakeepApiKey) |
| 22 | + ? decryptApiKey(settings.karakeepApiKey) |
| 23 | + : settings.karakeepApiKey, |
| 24 | + }; |
| 25 | +} |
| 26 | + |
| 27 | +function prepareUserSettingsWrite(data: UserSettingsUpdate): UserSettingsUpdate { |
| 28 | + if (!('karakeepApiKey' in data)) { |
| 29 | + return data; |
| 30 | + } |
| 31 | + |
| 32 | + const { karakeepApiKey, ...rest } = data; |
| 33 | + |
| 34 | + return { |
| 35 | + ...rest, |
| 36 | + karakeepApiKey: |
| 37 | + typeof karakeepApiKey === 'string' && karakeepApiKey.length > 0 |
| 38 | + ? encryptApiKey(karakeepApiKey) |
| 39 | + : karakeepApiKey, |
| 40 | + }; |
| 41 | +} |
| 42 | + |
| 43 | +export function toPublicUserSettings(settings: UserSettings): PublicUserSettings { |
| 44 | + const { karakeepApiKey, ...rest } = settings; |
| 45 | + |
| 46 | + return { |
| 47 | + ...rest, |
| 48 | + hasKarakeepApiKey: Boolean(karakeepApiKey), |
| 49 | + }; |
| 50 | +} |
4 | 51 |
|
5 | 52 | export async function getUserSettings(userId: string): Promise<UserSettings | null> { |
6 | 53 | const result = await db.query.userSettings.findFirst({ |
7 | 54 | where: eq(userSettings.userId, userId), |
8 | 55 | }); |
9 | | - return result ?? null; |
| 56 | + return result ? decryptUserSettingsSecrets(result) : null; |
10 | 57 | } |
11 | 58 |
|
12 | 59 | export async function createUserSettings( |
13 | 60 | userId: string, |
14 | | - data?: Partial<Omit<NewUserSettings, 'id' | 'userId' | 'createdAt' | 'updatedAt'>> |
| 61 | + data?: UserSettingsUpdate |
15 | 62 | ): Promise<UserSettings> { |
16 | 63 | const now = new Date(); |
| 64 | + const preparedData = prepareUserSettingsWrite(data ?? {}); |
17 | 65 | const [result] = await db |
18 | 66 | .insert(userSettings) |
19 | 67 | .values({ |
20 | 68 | id: generateId(), |
21 | 69 | userId, |
22 | | - timezone: data?.timezone ?? 'UTC', |
23 | | - privacyMode: data?.privacyMode ?? false, |
24 | | - contextMemoryEnabled: data?.contextMemoryEnabled ?? false, |
25 | | - persistentMemoryEnabled: data?.persistentMemoryEnabled ?? false, |
26 | | - youtubeTranscriptsEnabled: data?.youtubeTranscriptsEnabled ?? false, |
27 | | - webScrapingEnabled: data?.webScrapingEnabled ?? false, |
28 | | - mcpEnabled: data?.mcpEnabled ?? false, |
29 | | - followUpQuestionsEnabled: data?.followUpQuestionsEnabled ?? true, |
30 | | - suggestedPromptsEnabled: data?.suggestedPromptsEnabled ?? true, |
31 | | - freeMessagesUsed: data?.freeMessagesUsed ?? 0, |
32 | | - karakeepUrl: data?.karakeepUrl ?? null, |
33 | | - karakeepApiKey: data?.karakeepApiKey ?? null, |
34 | | - theme: data?.theme ?? null, |
35 | | - themePrimaryColor: data?.themePrimaryColor ?? null, |
36 | | - themeAccentColor: data?.themeAccentColor ?? null, |
37 | | - titleModelId: data?.titleModelId ?? null, |
38 | | - followUpModelId: data?.followUpModelId ?? null, |
39 | | - createdAt: now, |
| 70 | + timezone: preparedData.timezone ?? 'UTC', |
| 71 | + privacyMode: preparedData.privacyMode ?? false, |
| 72 | + contextMemoryEnabled: preparedData.contextMemoryEnabled ?? false, |
| 73 | + persistentMemoryEnabled: preparedData.persistentMemoryEnabled ?? false, |
| 74 | + youtubeTranscriptsEnabled: preparedData.youtubeTranscriptsEnabled ?? false, |
| 75 | + webScrapingEnabled: preparedData.webScrapingEnabled ?? false, |
| 76 | + mcpEnabled: preparedData.mcpEnabled ?? false, |
| 77 | + followUpQuestionsEnabled: preparedData.followUpQuestionsEnabled ?? true, |
| 78 | + suggestedPromptsEnabled: preparedData.suggestedPromptsEnabled ?? true, |
| 79 | + freeMessagesUsed: preparedData.freeMessagesUsed ?? 0, |
| 80 | + karakeepUrl: preparedData.karakeepUrl ?? null, |
| 81 | + karakeepApiKey: preparedData.karakeepApiKey ?? null, |
| 82 | + theme: preparedData.theme ?? null, |
| 83 | + themePrimaryColor: preparedData.themePrimaryColor ?? null, |
| 84 | + themeAccentColor: preparedData.themeAccentColor ?? null, |
| 85 | + titleModelId: preparedData.titleModelId ?? null, |
| 86 | + followUpModelId: preparedData.followUpModelId ?? null, |
| 87 | + createdAt: now, |
40 | 88 | updatedAt: now, |
41 | 89 | }) |
42 | 90 | .returning(); |
43 | | - return result!; |
| 91 | + return decryptUserSettingsSecrets(result!); |
44 | 92 | } |
45 | 93 |
|
46 | 94 | export async function updateUserSettings( |
47 | 95 | userId: string, |
48 | | - data: Partial<Omit<NewUserSettings, 'id' | 'userId' | 'createdAt' | 'updatedAt'>> |
| 96 | + data: UserSettingsUpdate |
49 | 97 | ): Promise<UserSettings | null> { |
| 98 | + const preparedData = prepareUserSettingsWrite(data); |
50 | 99 | const [result] = await db |
51 | 100 | .update(userSettings) |
52 | 101 | .set({ |
53 | | - ...data, |
| 102 | + ...preparedData, |
54 | 103 | updatedAt: new Date(), |
55 | 104 | }) |
56 | 105 | .where(eq(userSettings.userId, userId)) |
57 | 106 | .returning(); |
58 | | - return result ?? null; |
| 107 | + return result ? decryptUserSettingsSecrets(result) : null; |
59 | 108 | } |
60 | 109 |
|
61 | 110 | export async function incrementFreeMessageCount(userId: string): Promise<void> { |
|
0 commit comments