@lobehub/lobehub 2.0.0-next.144 → 2.0.0-next.146
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/.cursor/rules/project-introduce.mdc +1 -1
- package/.github/workflows/test.yml +3 -0
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +2 -0
- package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +8 -12
- package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +1 -0
- package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +2 -2
- package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +1 -1
- package/apps/desktop/src/main/utils/__tests__/logger.test.ts +7 -7
- package/apps/desktop/src/main/utils/next-electron-rsc.ts +3 -1
- package/apps/desktop/src/preload/invoke.test.ts +4 -2
- package/apps/desktop/src/preload/routeInterceptor.test.ts +54 -9
- package/apps/desktop/src/preload/streamer.test.ts +32 -31
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +2 -1
- package/docs/self-hosting/advanced/auth.mdx +21 -10
- package/docs/self-hosting/advanced/auth.zh-CN.mdx +21 -10
- package/package.json +4 -3
- package/packages/database/migrations/0056_update_agent_slug_index.sql +2 -0
- package/packages/database/migrations/meta/0056_snapshot.json +8411 -0
- package/packages/database/migrations/meta/_journal.json +7 -0
- package/packages/database/src/core/migrations.json +11 -2
- package/packages/database/src/schemas/agent.ts +2 -3
- package/packages/electron-client-ipc/src/events/system.ts +1 -3
- package/packages/electron-client-ipc/src/types/system.ts +1 -0
- package/src/envs/email.ts +11 -0
- package/src/libs/better-auth/email-templates/magic-link.ts +5 -5
- package/src/libs/better-auth/email-templates/reset-password.ts +4 -4
- package/src/libs/better-auth/email-templates/verification.ts +4 -4
- package/src/libs/mcp/__tests__/__snapshots__/index.test.ts.snap +9 -0
- package/src/server/services/email/README.md +19 -0
- package/src/server/services/email/impls/index.ts +5 -1
- package/src/server/services/email/impls/resend/index.ts +120 -0
- package/src/server/services/email/index.test.ts +1 -1
- package/src/server/services/email/index.ts +9 -1
- package/src/server/services/file/impls/index.ts +3 -3
- package/src/server/services/file/impls/local.ts +35 -35
- package/src/server/services/file/impls/s3.ts +1 -1
- package/src/server/services/file/impls/type.ts +11 -11
- package/src/server/services/file/index.ts +12 -12
|
@@ -12,12 +12,12 @@ import { extractKeyFromUrlOrReturnOriginal } from './utils';
|
|
|
12
12
|
const log = debug('lobe-file:desktop-local');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Desktop application local file service implementation
|
|
16
16
|
*/
|
|
17
17
|
export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Get local file URL
|
|
20
|
+
* Retrieve HTTP URL from main process via IPC
|
|
21
21
|
*/
|
|
22
22
|
private async getLocalFileUrl(key: string): Promise<string> {
|
|
23
23
|
try {
|
|
@@ -29,16 +29,16 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Create pre-signed upload URL (local version directly returns file path, may need further extension)
|
|
33
33
|
*/
|
|
34
34
|
async createPreSignedUrl(key: string): Promise<string> {
|
|
35
|
-
//
|
|
36
|
-
//
|
|
35
|
+
// In desktop application local file implementation, pre-signed URL is not needed
|
|
36
|
+
// Directly return the file path
|
|
37
37
|
return key;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Create pre-signed preview URL (local version accesses local files via HTTP path)
|
|
42
42
|
*/
|
|
43
43
|
async createPreSignedUrlForPreview(key: string): Promise<string> {
|
|
44
44
|
return this.getLocalFileUrl(key);
|
|
@@ -49,13 +49,13 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* Delete files in batch
|
|
53
53
|
*/
|
|
54
54
|
async deleteFiles(keys: string[]): Promise<any> {
|
|
55
55
|
try {
|
|
56
56
|
if (!keys || keys.length === 0) return { success: true };
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// Ensure all paths are valid desktop:// paths
|
|
59
59
|
const invalidKeys = keys.filter((key) => !key.startsWith('desktop://'));
|
|
60
60
|
if (invalidKeys.length > 0) {
|
|
61
61
|
console.error('Invalid desktop file paths:', invalidKeys);
|
|
@@ -65,7 +65,7 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// Use electronIpcClient's dedicated method
|
|
69
69
|
return await electronIpcClient.deleteFiles(keys);
|
|
70
70
|
} catch (error) {
|
|
71
71
|
console.error('Failed to delete files:', error);
|
|
@@ -82,20 +82,20 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
85
|
+
* Get file byte array
|
|
86
86
|
*/
|
|
87
87
|
async getFileByteArray(key: string): Promise<Uint8Array> {
|
|
88
88
|
try {
|
|
89
|
-
//
|
|
89
|
+
// Get absolute file path from Electron
|
|
90
90
|
const filePath = await electronIpcClient.getFilePathById(key);
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// Check if file exists
|
|
93
93
|
if (!existsSync(filePath)) {
|
|
94
94
|
console.error(`File not found: ${filePath}`);
|
|
95
95
|
return new Uint8Array();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
//
|
|
98
|
+
// Read file content and convert to Uint8Array
|
|
99
99
|
const buffer = readFileSync(filePath);
|
|
100
100
|
return new Uint8Array(buffer);
|
|
101
101
|
} catch (e) {
|
|
@@ -105,20 +105,20 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
108
|
+
* Get file content
|
|
109
109
|
*/
|
|
110
110
|
async getFileContent(key: string): Promise<string> {
|
|
111
111
|
try {
|
|
112
|
-
//
|
|
112
|
+
// Get absolute file path from Electron
|
|
113
113
|
const filePath = await electronIpcClient.getFilePathById(key);
|
|
114
114
|
|
|
115
|
-
//
|
|
115
|
+
// Check if file exists
|
|
116
116
|
if (!existsSync(filePath)) {
|
|
117
117
|
console.error(`File not found: ${filePath}`);
|
|
118
118
|
return '';
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
//
|
|
121
|
+
// Read file content and convert to string
|
|
122
122
|
return readFileSync(filePath, 'utf8');
|
|
123
123
|
} catch (e) {
|
|
124
124
|
console.error('Failed to get file content:', e);
|
|
@@ -127,7 +127,7 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
130
|
+
* Get full file URL
|
|
131
131
|
*/
|
|
132
132
|
async getFullFileUrl(url?: string | null): Promise<string> {
|
|
133
133
|
if (!url) return '';
|
|
@@ -139,32 +139,32 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
142
|
+
* Upload content
|
|
143
|
+
* Note: This feature may require extension of Electron IPC interface
|
|
144
144
|
*/
|
|
145
145
|
async uploadContent(filePath: string, content: string): Promise<any> {
|
|
146
|
-
//
|
|
147
|
-
//
|
|
146
|
+
// Need to extend electronIpcClient to support uploading file content
|
|
147
|
+
// For example: return electronIpcClient.uploadContent(filePath, content);
|
|
148
148
|
console.warn('uploadContent not implemented for Desktop local file service', filePath, content);
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
153
|
+
* Extract key from full URL
|
|
154
|
+
* Extract desktop:// format path from HTTP URL
|
|
155
155
|
*/
|
|
156
156
|
getKeyFromFullUrl(url: string): string {
|
|
157
157
|
try {
|
|
158
158
|
const urlObj = new URL(url);
|
|
159
159
|
const pathSegments = urlObj.pathname.split('/').filter((segment) => segment !== '');
|
|
160
160
|
|
|
161
|
-
//
|
|
161
|
+
// Remove first path segment (desktop-file)
|
|
162
162
|
pathSegments.shift();
|
|
163
163
|
|
|
164
|
-
//
|
|
164
|
+
// Recombine remaining path segments
|
|
165
165
|
const filePath = pathSegments.join('/');
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// Return desktop:// format path
|
|
168
168
|
return `desktop://${filePath}`;
|
|
169
169
|
} catch (e) {
|
|
170
170
|
console.error('[DesktopLocalFileImpl] Failed to extract key from URL:', e);
|
|
@@ -173,23 +173,23 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
|
-
*
|
|
176
|
+
* Upload media file
|
|
177
177
|
*/
|
|
178
178
|
async uploadMedia(key: string, buffer: Buffer): Promise<{ key: string }> {
|
|
179
179
|
try {
|
|
180
|
-
//
|
|
180
|
+
// Convert Buffer to Base64 string
|
|
181
181
|
const content = buffer.toString('base64');
|
|
182
182
|
|
|
183
|
-
//
|
|
183
|
+
// Extract filename from key
|
|
184
184
|
const filename = path.basename(key);
|
|
185
185
|
|
|
186
|
-
//
|
|
186
|
+
// Calculate SHA256 hash of the file
|
|
187
187
|
const hash = sha256(buffer);
|
|
188
188
|
|
|
189
|
-
//
|
|
189
|
+
// Infer MIME type from file URL
|
|
190
190
|
const type = inferContentTypeFromImageUrl(key)!;
|
|
191
191
|
|
|
192
|
-
//
|
|
192
|
+
// Construct upload parameters
|
|
193
193
|
const uploadParams = {
|
|
194
194
|
content,
|
|
195
195
|
filename,
|
|
@@ -198,7 +198,7 @@ export class DesktopLocalFileImpl implements FileServiceImpl {
|
|
|
198
198
|
type,
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
//
|
|
201
|
+
// Call electronIpcClient to upload file
|
|
202
202
|
const result = await electronIpcClient.createFile(uploadParams);
|
|
203
203
|
|
|
204
204
|
if (!result.success) {
|
|
@@ -7,7 +7,7 @@ import { FileServiceImpl } from './type';
|
|
|
7
7
|
import { extractKeyFromUrlOrReturnOriginal } from './utils';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* S3-based file service implementation
|
|
11
11
|
*/
|
|
12
12
|
export class S3StaticFileImpl implements FileServiceImpl {
|
|
13
13
|
private readonly s3: S3;
|
|
@@ -1,54 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* File service implementation interface
|
|
3
3
|
*/
|
|
4
4
|
export interface FileServiceImpl {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Create pre-signed upload URL
|
|
7
7
|
*/
|
|
8
8
|
createPreSignedUrl(key: string): Promise<string>;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Create pre-signed preview URL
|
|
12
12
|
*/
|
|
13
13
|
createPreSignedUrlForPreview(key: string, expiresIn?: number): Promise<string>;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Delete file
|
|
17
17
|
*/
|
|
18
18
|
deleteFile(key: string): Promise<any>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Delete files in batch
|
|
22
22
|
*/
|
|
23
23
|
deleteFiles(keys: string[]): Promise<any>;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Get file byte array
|
|
27
27
|
*/
|
|
28
28
|
getFileByteArray(key: string): Promise<Uint8Array>;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Get file content
|
|
32
32
|
*/
|
|
33
33
|
getFileContent(key: string): Promise<string>;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Get full file URL
|
|
37
37
|
*/
|
|
38
38
|
getFullFileUrl(url?: string | null, expiresIn?: number): Promise<string>;
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Extract key from full URL
|
|
42
42
|
*/
|
|
43
43
|
getKeyFromFullUrl(url: string): string;
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Upload content
|
|
47
47
|
*/
|
|
48
48
|
uploadContent(path: string, content: string): Promise<any>;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
51
|
+
* Upload media file
|
|
52
52
|
*/
|
|
53
53
|
uploadMedia(key: string, buffer: Buffer): Promise<{ key: string }>;
|
|
54
54
|
}
|
|
@@ -11,8 +11,8 @@ import { TempFileManager } from '@/server/utils/tempFileManager';
|
|
|
11
11
|
import { FileServiceImpl, createFileServiceModule } from './impls';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* File service class
|
|
15
|
+
* Provides file operation services using a modular implementation approach
|
|
16
16
|
*/
|
|
17
17
|
export class FileService {
|
|
18
18
|
private userId: string;
|
|
@@ -26,70 +26,70 @@ export class FileService {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Delete file
|
|
30
30
|
*/
|
|
31
31
|
public async deleteFile(key: string) {
|
|
32
32
|
return this.impl.deleteFile(key);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Delete files in batch
|
|
37
37
|
*/
|
|
38
38
|
public async deleteFiles(keys: string[]) {
|
|
39
39
|
return this.impl.deleteFiles(keys);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Get file content
|
|
44
44
|
*/
|
|
45
45
|
public async getFileContent(key: string): Promise<string> {
|
|
46
46
|
return this.impl.getFileContent(key);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Get file byte array
|
|
51
51
|
*/
|
|
52
52
|
public async getFileByteArray(key: string): Promise<Uint8Array> {
|
|
53
53
|
return this.impl.getFileByteArray(key);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* Create pre-signed upload URL
|
|
58
58
|
*/
|
|
59
59
|
public async createPreSignedUrl(key: string): Promise<string> {
|
|
60
60
|
return this.impl.createPreSignedUrl(key);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
64
|
+
* Create pre-signed preview URL
|
|
65
65
|
*/
|
|
66
66
|
public async createPreSignedUrlForPreview(key: string, expiresIn?: number): Promise<string> {
|
|
67
67
|
return this.impl.createPreSignedUrlForPreview(key, expiresIn);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* Upload content
|
|
72
72
|
*/
|
|
73
73
|
public async uploadContent(path: string, content: string) {
|
|
74
74
|
return this.impl.uploadContent(path, content);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* Get full file URL
|
|
79
79
|
*/
|
|
80
80
|
public async getFullFileUrl(url?: string | null, expiresIn?: number): Promise<string> {
|
|
81
81
|
return this.impl.getFullFileUrl(url, expiresIn);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
85
|
+
* Extract key from full URL
|
|
86
86
|
*/
|
|
87
87
|
public getKeyFromFullUrl(url: string): string {
|
|
88
88
|
return this.impl.getKeyFromFullUrl(url);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* Upload media file
|
|
93
93
|
*/
|
|
94
94
|
public async uploadMedia(key: string, buffer: Buffer): Promise<{ key: string }> {
|
|
95
95
|
return this.impl.uploadMedia(key, buffer);
|